diff --git a/frontend/src/lib/otel/otel.client.ts b/frontend/src/lib/otel/otel.client.ts index 948a4d983..470d040e9 100644 --- a/frontend/src/lib/otel/otel.client.ts +++ b/frontend/src/lib/otel/otel.client.ts @@ -1,8 +1,8 @@ import { BatchSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web' import { SERVICE_NAME, ensureErrorIsTraced, tracer } from '.' -import {APP_VERSION} from '$lib/util/version'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' +import { APP_VERSION } from '$lib/util/version'; +import { OTLPTraceExporterBrowserWithXhrRetry } from './trace-exporter-browser-with-xhr-retry'; import { Resource } from '@opentelemetry/resources' import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { ZoneContextManager } from '@opentelemetry/context-zone' @@ -47,15 +47,15 @@ const resource = Resource.default().merge( ) const provider = new WebTracerProvider({ resource: resource, -}) -const exporter = new OTLPTraceExporter({ +}); +const exporter = new OTLPTraceExporterBrowserWithXhrRetry({ url: '/v1/traces' }); provider.addSpanProcessor( new BatchSpanProcessor(exporter, { // max number of spans pulled from the qeuue and exported in a single batch - // this can't be much higher or the export will be too big for the sendBeacon() API - maxExportBatchSize: 15, + // 30 is often too big for the sendBeacon() API, but we have a fallback to XHR. + maxExportBatchSize: 30, // minimum time between exports scheduledDelayMillis: 1000, maxQueueSize: 5000, // default: 2048 diff --git a/frontend/src/lib/otel/trace-exporter-browser-with-xhr-retry.ts b/frontend/src/lib/otel/trace-exporter-browser-with-xhr-retry.ts new file mode 100644 index 000000000..380d9d408 --- /dev/null +++ b/frontend/src/lib/otel/trace-exporter-browser-with-xhr-retry.ts @@ -0,0 +1,39 @@ +// We explicitly reference the browser version so that we have proper types + +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http/build/src/platform/browser'; +import type { ReadableSpan } from '@opentelemetry/sdk-trace-web'; + +// these save us a dependency +type SendOnErrorCallback = Parameters[2]; +type ExporterConfig = ConstructorParameters[0]; + +export class OTLPTraceExporterBrowserWithXhrRetry extends OTLPTraceExporter { + + private readonly xhrTraceExporter: OTLPTraceExporter; + + constructor(config?: ExporterConfig) { + super(config); + this.xhrTraceExporter = new OTLPTraceExporter({ + ...(config ?? {}), + // passing a truthy value here causes XHR to be used: https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/otlp-exporter-base/src/platform/browser/OTLPExporterBrowserBase.ts#L40 + headers: {}, + }); + } + + send(items: ReadableSpan[], onSuccess: () => void, onError: SendOnErrorCallback): void { + super.send(items, onSuccess, (error) => { + if (error.message.toLocaleLowerCase().includes('beacon')) { + this.xhrTraceExporter.send(items, onSuccess, (xhrError) => { + onError({ + ...error, + message: `${error.message} --- [XHR retry message: ${xhrError.message}; code: ${xhrError.code}].`, + code: error.code, + data: `${error.data} --- [XHR retry data: ${xhrError.data}].`, + }); + }); + } else { + onError(error); + } + }); + } +}