Skip to content
Closed
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ feat(configuration): parse config for rc 3 [#6304](https://github.com/open-telem

* fix(exporter-prometheus): add missing `@opentelemetry/semantic-conventions` dependency [#6330](https://github.com/open-telemetry/opentelemetry-js/pull/6330) @omizha
* fix(otlp-transformer): correctly handle Uint8Array attribute values when serializing to JSON [#6348](https://github.com/open-telemetry/opentelemetry-js/pull/6348) @pichlermarc
* fix(otlp-exporter-base): handle 64KiB limit for browser transports [#6358](https://github.com/open-telemetry/opentelemetry-js/pull/6358) @YangJonghun

### :books: Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('OTLPLogExporter', function () {
describe('when sendBeacon is available', function () {
it('should successfully send data using sendBeacon', async function () {
// arrange
const stubBeacon = sinon.stub(navigator, 'sendBeacon');
const stubBeacon = sinon.stub(navigator, 'sendBeacon').returns(true);
const loggerProvider = new LoggerProvider({
processors: [new SimpleLogRecordProcessor(new OTLPLogExporter())],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('OTLPLogExporter', function () {
describe('when sendBeacon is available', function () {
it('should successfully send data using sendBeacon', async function () {
// arrange
const stubBeacon = sinon.stub(navigator, 'sendBeacon');
const stubBeacon = sinon.stub(navigator, 'sendBeacon').returns(true);
const loggerProvider = new LoggerProvider({
processors: [new SimpleLogRecordProcessor(new OTLPLogExporter())],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('OTLPTraceExporter', () => {
describe('when sendBeacon is available', function () {
it('should successfully send data using sendBeacon', async function () {
// arrange
const stubBeacon = sinon.stub(navigator, 'sendBeacon');
const stubBeacon = sinon.stub(navigator, 'sendBeacon').returns(true);
const tracerProvider = new BasicTracerProvider({
spanProcessors: [new SimpleSpanProcessor(new OTLPTraceExporter())],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('OTLPTraceExporter', () => {
describe('when sendBeacon is available', function () {
it('should successfully send data using sendBeacon', async function () {
// arrange
const stubBeacon = sinon.stub(navigator, 'sendBeacon');
const stubBeacon = sinon.stub(navigator, 'sendBeacon').returns(true);
const tracerProvider = new BasicTracerProvider({
spanProcessors: [new SimpleSpanProcessor(new OTLPTraceExporter())],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('OTLPMetricExporter', function () {
describe('when sendBeacon is available', function () {
it('should successfully send data using sendBeacon', async function () {
// arrange
const stubBeacon = sinon.stub(navigator, 'sendBeacon');
const stubBeacon = sinon.stub(navigator, 'sendBeacon').returns(true);
const meterProvider = new MeterProvider({
readers: [
new PeriodicExportingMetricReader({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('OTLPTraceExporter', () => {
describe('when sendBeacon is available', function () {
it('should successfully send data using sendBeacon', async function () {
// arrange
const stubBeacon = sinon.stub(navigator, 'sendBeacon');
const stubBeacon = sinon.stub(navigator, 'sendBeacon').returns(true);
const meterProvider = new MeterProvider({
readers: [
new PeriodicExportingMetricReader({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { createRetryingTransport } from './retrying-transport';
import { createSendBeaconTransport } from './transport/send-beacon-transport';
import { createOtlpNetworkExportDelegate } from './otlp-network-export-delegate';
import { createFetchTransport } from './transport/fetch-transport';
import { createFailoverTransport } from './transport/failover-transport';

export function createOtlpFetchExportDelegate<Internal, Response>(
options: OtlpHttpConfiguration,
Expand All @@ -42,9 +43,12 @@ export function createOtlpSendBeaconExportDelegate<Internal, Response>(
options,
serializer,
createRetryingTransport({
transport: createSendBeaconTransport({
url: options.url,
headers: options.headers,
transport: createFailoverTransport({
primary: createSendBeaconTransport({
url: options.url,
headers: options.headers,
}),
failover: createFetchTransport(options),
}),
})
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { IExporterTransport } from '../exporter-transport';
import { ExportResponse } from '../export-response';
import { diag } from '@opentelemetry/api';

export interface FailoverTransportParameters {
primary: IExporterTransport;
failover: IExporterTransport;
}

class FailoverTransport implements IExporterTransport {
private _primary: IExporterTransport;
private _failover: IExporterTransport;

constructor(parameters: FailoverTransportParameters) {
this._primary = parameters.primary;
this._failover = parameters.failover;
}

async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
const result = await this._primary.send(data, timeoutMillis);
if (result.status === 'failure') {
diag.debug('Primary transport failed, switching to failover transport');
return this._failover.send(data, timeoutMillis);
}
return result;
}

shutdown(): void {
this._primary.shutdown();
this._failover.shutdown();
}
}

/**
* Creates a transport that tries the primary transport first,
* and switches to the failover transport if the primary fails.
*/
export function createFailoverTransport(
parameters: FailoverTransportParameters
): IExporterTransport {
return new FailoverTransport(parameters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ import {
} from '../is-export-retryable';
import { HeadersFactory } from '../configuration/otlp-http-configuration';

/**
* Maximum body size for using keepalive flag.
* Browsers limit keepalive requests to 64KiB total. We use 60KiB for body
* to leave room for headers.
*/
const KEEPALIVE_BODY_SIZE_LIMIT = 60 * 1024;

export interface FetchTransportParameters {
url: string;
headers: HeadersFactory;
Expand All @@ -46,7 +53,8 @@ class FetchTransport implements IExporterTransport {
headers: await this._parameters.headers(),
body: data,
signal: abortController.signal,
keepalive: isBrowserEnvironment,
keepalive:
isBrowserEnvironment && data.byteLength < KEEPALIVE_BODY_SIZE_LIMIT,
mode: isBrowserEnvironment
? globalThis.location?.origin === url.origin
? 'same-origin'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,49 @@ describe('FetchTransport', function () {
done();
}, done /* catch any rejections */);
});

it('uses keepalive when body size is less than 60KiB', function (done) {
// arrange
const smallPayload = new Uint8Array(61439);
const fetchStub = sinon
.stub(globalThis, 'fetch')
.resolves(new Response('', { status: 200 }));
const transport = createFetchTransport(testTransportParameters);

// act
transport.send(smallPayload, requestTimeout).then(() => {
// assert
try {
sinon.assert.calledOnce(fetchStub);
const callArgs = fetchStub.firstCall.args[1];
assert.strictEqual(callArgs?.keepalive, true);
} catch (e) {
done(e);
}
done();
}, done);
});

it('does not use keepalive when body size is 60KiB or larger', function (done) {
// arrange
const largePayload = new Uint8Array(61440);
const fetchStub = sinon
.stub(globalThis, 'fetch')
.resolves(new Response('', { status: 200 }));
const transport = createFetchTransport(testTransportParameters);

// act
transport.send(largePayload, requestTimeout).then(() => {
// assert
try {
sinon.assert.calledOnce(fetchStub);
const callArgs = fetchStub.firstCall.args[1];
assert.strictEqual(callArgs?.keepalive, false);
} catch (e) {
done(e);
}
done();
}, done);
});
});
});
Loading