Skip to content

Commit

Permalink
feat(otlp-exporter-base)!: use transport interface in web exporters (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc authored Aug 21, 2024
1 parent cd4e2bf commit f2a6bcc
Show file tree
Hide file tree
Showing 12 changed files with 627 additions and 452 deletions.
2 changes: 2 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ All notable changes to experimental packages in this project will be documented
* feat(exporter-*-otlp*)!: remove environment-variable specific code from browser exporters
* (user-facing) removes the ability to configure browser exporters by using `process.env` polyfills
* feat(sdk-node)!: Automatically configure logs exporter [#4740](https://github.com/open-telemetry/opentelemetry-js/pull/4740)
* feat(exporter-*-otlp-*)!: use transport interface in browser exporters [#4895](https://github.com/open-telemetry/opentelemetry-js/pull/4895) @pichlermarc
* (user-facing) protected `headers` property was intended for internal use has been removed from all exporters

### :rocket: (Enhancement)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,34 @@ describe('OTLPTraceExporter - web', () => {

collectorTraceExporter.export(spans, () => {});

setTimeout(() => {
const response: any = spyLoggerDebug.args[2][0];
assert.strictEqual(response, 'sendBeacon - can send');
assert.strictEqual(spyLoggerError.args.length, 0);
queueMicrotask(() => {
try {
const response: any = spyLoggerDebug.args[2][0];
assert.strictEqual(response, 'SendBeacon success');
assert.strictEqual(spyLoggerError.args.length, 0);

done();
done();
} catch (e) {
done(e);
}
});
});

it('should log the error message', done => {
stubBeacon.returns(false);

collectorTraceExporter.export(spans, result => {
assert.deepStrictEqual(result.code, ExportResultCode.FAILED);
assert.ok(result.error?.message.includes('cannot send'));
done();
try {
assert.deepStrictEqual(result.code, ExportResultCode.FAILED);
assert.ok(
result.error,
'Expected Error, but no Error was present on the result'
);
assert.match(result.error?.message, /SendBeacon failed/);
done();
} catch (e) {
done(e);
}
});
});
});
Expand All @@ -179,8 +191,8 @@ describe('OTLPTraceExporter - web', () => {
clock = sinon.useFakeTimers();

(window.navigator as any).sendBeacon = false;
collectorTraceExporter = new OTLPTraceExporter(collectorExporterConfig);
server = sinon.fakeServer.create();
collectorTraceExporter = new OTLPTraceExporter(collectorExporterConfig);
});
afterEach(() => {
server.restore();
Expand All @@ -189,15 +201,15 @@ describe('OTLPTraceExporter - web', () => {
it('should successfully send the spans using XMLHttpRequest', done => {
collectorTraceExporter.export(spans, () => {});

queueMicrotask(() => {
queueMicrotask(async () => {
const request = server.requests[0];
assert.strictEqual(request.method, 'POST');
assert.strictEqual(request.url, 'http://foo.bar.com');

const body = request.requestBody;
const body = request.requestBody as Blob;
const decoder = new TextDecoder();
const json = JSON.parse(
decoder.decode(body)
decoder.decode(await body.arrayBuffer())
) as IExportTraceServiceRequest;
const span1 = json.resourceSpans?.[0].scopeSpans?.[0].spans?.[0];

Expand Down Expand Up @@ -235,28 +247,36 @@ describe('OTLPTraceExporter - web', () => {
queueMicrotask(() => {
const request = server.requests[0];
request.respond(200);
const response: any = spyLoggerDebug.args[2][0];
assert.strictEqual(response, 'xhr success');
assert.strictEqual(spyLoggerError.args.length, 0);
assert.strictEqual(stubBeacon.callCount, 0);

clock.restore();
done();
try {
const response: any = spyLoggerDebug.args[2][0];
assert.strictEqual(response, 'XHR success');
assert.strictEqual(spyLoggerError.args.length, 0);
assert.strictEqual(stubBeacon.callCount, 0);
clock.restore();
done();
} catch (e) {
done(e);
}
});
});

it('should log the error message', done => {
collectorTraceExporter.export(spans, result => {
assert.deepStrictEqual(result.code, ExportResultCode.FAILED);
assert.ok(result.error?.message.includes('Failed to export'));
try {
assert.deepStrictEqual(result.code, ExportResultCode.FAILED);
assert.deepStrictEqual(
result.error?.message,
'XHR request failed with non-retryable status'
);
} catch (e) {
done(e);
}
done();
});

queueMicrotask(() => {
const request = server.requests[0];
request.respond(400);
clock.restore();
done();
});
});

Expand Down Expand Up @@ -421,17 +441,20 @@ describe('OTLPTraceExporter - web', () => {
it('should log the timeout request error message', done => {
const responseSpy = sinon.spy();
collectorTraceExporter.export(spans, responseSpy);
clock.tick(10000);
clock.tick(20000);
clock.restore();

setTimeout(() => {
const result = responseSpy.args[0][0] as core.ExportResult;
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(error.message, 'Request Timeout');

done();
try {
const result = responseSpy.args[0][0] as core.ExportResult;
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(error.message, 'XHR request timed out');
done();
} catch (e) {
done(e);
}
});
});
});
Expand All @@ -455,15 +478,19 @@ describe('OTLPTraceExporter - web', () => {

setTimeout(() => {
// Expect 4 failures
assert.strictEqual(failures.length, 4);
failures.forEach(([result]) => {
assert.strictEqual(result.code, ExportResultCode.FAILED);
assert.strictEqual(
result.error!.message,
'Concurrent export limit reached'
);
});
done();
try {
assert.strictEqual(failures.length, 4);
failures.forEach(([result]) => {
assert.strictEqual(result.code, ExportResultCode.FAILED);
assert.strictEqual(
result.error!.message,
'Concurrent export limit reached'
);
});
done();
} catch (e) {
done(e);
}
});
});
});
Expand Down Expand Up @@ -514,49 +541,63 @@ describe('export with retry - real http request destroyed', () => {
(window.navigator as any).sendBeacon = false;
collectorTraceExporter = new OTLPTraceExporter(collectorExporterConfig);
});
it('should log the timeout request error message when retrying with exponential backoff with jitter', done => {
it('should log the retryable request error message when retrying with exponential backoff with jitter', done => {
spans = [];
spans.push(Object.assign({}, mockedReadableSpan));

let retry = 0;
let calls = 0;
server.respondWith(
'http://localhost:4318/v1/traces',
function (xhr: any) {
retry++;
calls++;
xhr.respond(503);
}
);

collectorTraceExporter.export(spans, result => {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(error.message, 'Request Timeout');
assert.strictEqual(retry, 1);
done();
try {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(
error.message,
'Export failed with retryable status'
);
assert.strictEqual(calls, 6);
done();
} catch (e) {
done(e);
}
});
}).timeout(3000);

it('should log the timeout request error message when retry-after header is set to 3 seconds', done => {
spans = [];
spans.push(Object.assign({}, mockedReadableSpan));

let retry = 0;
let calls = 0;
server.respondWith(
'http://localhost:4318/v1/traces',
function (xhr: any) {
retry++;
xhr.respond(503, { 'Retry-After': 3 });
calls++;
xhr.respond(503, { 'Retry-After': 0.1 });
}
);

collectorTraceExporter.export(spans, result => {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(error.message, 'Request Timeout');
assert.strictEqual(retry, 1);
done();
try {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(
error.message,
'Export failed with retryable status'
);
assert.strictEqual(calls, 6);
done();
} catch (e) {
done(e);
}
});
}).timeout(3000);
it('should log the timeout request error message when retry-after header is a date', done => {
Expand All @@ -569,18 +610,25 @@ describe('export with retry - real http request destroyed', () => {
function (xhr: any) {
retry++;
const d = new Date();
d.setSeconds(d.getSeconds() + 1);
d.setSeconds(d.getSeconds() + 0.1);
xhr.respond(503, { 'Retry-After': d });
}
);

collectorTraceExporter.export(spans, result => {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(error.message, 'Request Timeout');
assert.strictEqual(retry, 2);
done();
try {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(
error.message,
'Export failed with retryable status'
);
assert.strictEqual(retry, 6);
done();
} catch (e) {
done(e);
}
});
}).timeout(3000);
it('should log the timeout request error message when retry-after header is a date with long delay', done => {
Expand All @@ -599,12 +647,19 @@ describe('export with retry - real http request destroyed', () => {
);

collectorTraceExporter.export(spans, result => {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(error.message, 'Request Timeout');
assert.strictEqual(retry, 1);
done();
try {
assert.strictEqual(result.code, core.ExportResultCode.FAILED);
const error = result.error as OTLPExporterError;
assert.ok(error !== undefined);
assert.strictEqual(
error.message,
'Export failed with retryable status'
);
assert.strictEqual(retry, 1);
done();
} catch (e) {
done(e);
}
});
}).timeout(3000);
});
Expand Down
Loading

0 comments on commit f2a6bcc

Please sign in to comment.