diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 10cf6682920..4f44802ef4b 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -8,13 +8,19 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 ### :boom: Breaking Changes +* feat(otlp-exporter-base)!: allow passing an async function to headers option [#5994](https://github.com/open-telemetry/opentelemetry-js/pull/5994/files) @pichlermarc + * In addition to static headers, OTLP exporters now allow passing an async function that returns headers which will be called before each export. See TSDoc for `headers` in `OTLPExporterConfigBase` for details. + * Breaking changes: + * (user-facing): `headers` option in all OTLP exporters now accepts a function that returns a `Promise>` in addition to the existing `Record` type. + * (user-facing): `headers` in `HttpNodeRequestParameters`, `FetchTransportParameters`, and `XhrRequestParameters` now only accept async functions. + * (user-facing): `headers` in `OtlpHttpConfiguration` now only accepts async functions. * feat(sdk-node)!: drop lazy-loading of jaeger exporter [#5989](https://github.com/open-telemetry/opentelemetry-js/pull/5989) * (user-facing): setting `OTEL_TRACE_EXPORTER=jaeger` not instantiate a Jaeger exporter anymore, please use `OTEL_TRACE_EXPORTER=otlp` instead. * Jaeger now has [native API support for OTLP](https://www.jaegertracing.io/docs/1.73/architecture/apis/#opentelemetry-protocol-stable) and [Jaeger's Thrift API endpoints have been deprecated](https://www.jaegertracing.io/docs/1.73/architecture/apis/#thrift-over-http-stable) ### :rocket: Features -* feat(sdk-node): always set up propagtion and context manager [#5930](https://github.com/open-telemetry/opentelemetry-js/pull/5930) +* feat(sdk-node): always set up propagtion and context manager [#5930](https://github.com/open-telemetry/opentelemetry-js/pull/5930) @pichlermarc * using `(new NodeSDK).start()` will now automatically set up a context management and propagation, even if no Trace SDK is initialized. * feat(otlp-exporter-base, otlp-grpc-exporter-base): add an option to let an SDK distribution prepend their own user-agent string in HTTP & GRPC exporters [#5928](https://github.com/open-telemetry/opentelemetry-js/pull/5928) @david-luna diff --git a/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-browser-http-options.ts b/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-browser-http-options.ts index 3f72eda1605..d1ceda77bf6 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-browser-http-options.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-browser-http-options.ts @@ -19,7 +19,7 @@ import { OtlpHttpConfiguration, } from './otlp-http-configuration'; import { OTLPExporterNodeConfigBase } from './legacy-node-configuration'; -import { wrapStaticHeadersInFunction } from './shared-configuration'; +import { convertLegacyHeaders } from './convert-legacy-http-options'; /** * @deprecated this will be removed in 2.0 @@ -37,7 +37,7 @@ export function convertLegacyBrowserHttpOptions( { url: config.url, timeoutMillis: config.timeoutMillis, - headers: wrapStaticHeadersInFunction(config.headers), + headers: convertLegacyHeaders(config), concurrencyLimit: config.concurrencyLimit, }, {}, // no fallback for browser case diff --git a/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-http-options.ts b/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-http-options.ts new file mode 100644 index 00000000000..f32aee3bb65 --- /dev/null +++ b/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-http-options.ts @@ -0,0 +1,28 @@ +/* + * 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 { OTLPExporterConfigBase } from './legacy-base-configuration'; +import { wrapStaticHeadersInFunction } from './shared-configuration'; +import { HeadersFactory } from './otlp-http-configuration'; + +export function convertLegacyHeaders( + config: OTLPExporterConfigBase +): HeadersFactory | undefined { + if (typeof config.headers === 'function') { + return config.headers; + } + return wrapStaticHeadersInFunction(config.headers); +} diff --git a/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-node-http-options.ts b/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-node-http-options.ts index 1c703720e34..e73f364f709 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-node-http-options.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/convert-legacy-node-http-options.ts @@ -15,7 +15,6 @@ */ import { OTLPExporterNodeConfigBase } from './legacy-node-configuration'; import { diag } from '@opentelemetry/api'; -import { wrapStaticHeadersInFunction } from './shared-configuration'; import { getNodeHttpConfigurationDefaults, HttpAgentFactory, @@ -24,6 +23,7 @@ import { } from './otlp-node-http-configuration'; import { httpAgentFactoryFromOptions } from '../index-node-http'; import { getNodeHttpConfigurationFromEnvironment } from './otlp-node-http-env-configuration'; +import { convertLegacyHeaders } from './convert-legacy-http-options'; function convertLegacyAgentOptions( config: OTLPExporterNodeConfigBase @@ -65,7 +65,7 @@ export function convertLegacyHttpOptions( return mergeOtlpNodeHttpConfigurationWithDefaults( { url: config.url, - headers: wrapStaticHeadersInFunction(config.headers), + headers: convertLegacyHeaders(config), concurrencyLimit: config.concurrencyLimit, timeoutMillis: config.timeoutMillis, compression: config.compression, diff --git a/experimental/packages/otlp-exporter-base/src/configuration/legacy-base-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/legacy-base-configuration.ts index 8a36b267ddf..722b757665e 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/legacy-base-configuration.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/legacy-base-configuration.ts @@ -13,8 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { HeadersFactory } from './otlp-http-configuration'; + export interface OTLPExporterConfigBase { - headers?: Record; + /** + * Custom headers that will be attached to the HTTP request that's sent to the endpoint. + * + * @remarks + * Prefer using a plain object over a factory function wherever possible. + * If using a factory function (`HttpAgentFactory`), **do not import `http` or `https` at the top of the file** + * Instead, use dynamic `import()` or `require()` to load the module. This ensures that the `http` or `https` + * module is not loaded before `@opentelemetry/instrumentation-http` can instrument it. + * + * Functions passed to the exporter MUST NOT throw errors. + * + * @example Using headers options directly: + * headers: { + * Authorization: "Api-Token my-secret-token", + * } + * + * @example Using a custom factory function + * headers: async () => { + * // ... do whatever you need to obtain the headers, ensuring you `await import('your-library')` to avoid breaking instrumentations ... + * return { + * Authorization: `Bearer ${token}`, + * }; + * }; + */ + headers?: Record | HeadersFactory; url?: string; concurrencyLimit?: number; /** Maximum time the OTLP exporter will wait for each batch export. diff --git a/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts index 499e33eb50f..5d62f204485 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts @@ -21,30 +21,35 @@ import { } from './shared-configuration'; import { validateAndNormalizeHeaders } from '../util'; +export type HeadersFactory = () => Promise>; + export interface OtlpHttpConfiguration extends OtlpSharedConfiguration { url: string; - headers: () => Record; + headers: HeadersFactory; } function mergeHeaders( - userProvidedHeaders: (() => Record) | undefined | null, - fallbackHeaders: (() => Record) | undefined | null, - defaultHeaders: () => Record -): () => Record { - const requiredHeaders = { - ...defaultHeaders(), - }; - const headers = {}; + userProvidedHeaders: HeadersFactory | undefined | null, + fallbackHeaders: HeadersFactory | undefined | null, + defaultHeaders: HeadersFactory +): HeadersFactory { + return async () => { + const requiredHeaders = { + ...(await defaultHeaders()), + }; + const headers = {}; - return () => { // add fallback ones first if (fallbackHeaders != null) { - Object.assign(headers, fallbackHeaders()); + Object.assign(headers, await fallbackHeaders()); } // override with user-provided ones if (userProvidedHeaders != null) { - Object.assign(headers, userProvidedHeaders()); + Object.assign( + headers, + validateAndNormalizeHeaders(await userProvidedHeaders()) + ); } // override required ones. @@ -84,7 +89,7 @@ export function mergeOtlpHttpConfigurationWithDefaults( defaultConfiguration ), headers: mergeHeaders( - validateAndNormalizeHeaders(userProvidedConfiguration.headers), + userProvidedConfiguration.headers, fallbackConfiguration.headers, defaultConfiguration.headers ), @@ -101,7 +106,7 @@ export function getHttpConfigurationDefaults( ): OtlpHttpConfiguration { return { ...getSharedConfigurationDefaults(), - headers: () => requiredHeaders, + headers: async () => requiredHeaders, url: 'http://localhost:4318/' + signalResourcePath, }; } diff --git a/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts index c771cc07678..450b793026a 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { HeadersFactory } from './otlp-http-configuration'; + /** * Configuration shared across all OTLP exporters * @@ -39,12 +41,12 @@ export function validateTimeoutMillis(timeoutMillis: number) { export function wrapStaticHeadersInFunction( headers: Record | undefined -): (() => Record) | undefined { +): HeadersFactory | undefined { if (headers == null) { return undefined; } - return () => headers; + return async () => headers; } /** diff --git a/experimental/packages/otlp-exporter-base/src/otlp-browser-http-export-delegate.ts b/experimental/packages/otlp-exporter-base/src/otlp-browser-http-export-delegate.ts index e98f6d3dbbb..57d1d87b169 100644 --- a/experimental/packages/otlp-exporter-base/src/otlp-browser-http-export-delegate.ts +++ b/experimental/packages/otlp-exporter-base/src/otlp-browser-http-export-delegate.ts @@ -61,7 +61,7 @@ export function createOtlpSendBeaconExportDelegate( createRetryingTransport({ transport: createSendBeaconTransport({ url: options.url, - blobType: options.headers()['Content-Type'], + headers: options.headers, }), }) ); diff --git a/experimental/packages/otlp-exporter-base/src/transport/fetch-transport.ts b/experimental/packages/otlp-exporter-base/src/transport/fetch-transport.ts index b3d63930c0a..03cf7425ec2 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/fetch-transport.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/fetch-transport.ts @@ -21,10 +21,11 @@ import { isExportRetryable, parseRetryAfterToMills, } from '../is-export-retryable'; +import { HeadersFactory } from '../configuration/otlp-http-configuration'; export interface FetchTransportParameters { url: string; - headers: () => Record; + headers: HeadersFactory; } class FetchTransport implements IExporterTransport { @@ -38,7 +39,7 @@ class FetchTransport implements IExporterTransport { const url = new URL(this._parameters.url); const response = await fetch(url.href, { method: 'POST', - headers: this._parameters.headers(), + headers: await this._parameters.headers(), body: data, signal: abortController.signal, keepalive: isBrowserEnvironment, diff --git a/experimental/packages/otlp-exporter-base/src/transport/http-exporter-transport.ts b/experimental/packages/otlp-exporter-base/src/transport/http-exporter-transport.ts index 532df8f07e1..9bef885476b 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/http-exporter-transport.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/http-exporter-transport.ts @@ -35,11 +35,15 @@ class HttpExporterTransport implements IExporterTransport { async send(data: Uint8Array, timeoutMillis: number): Promise { const { agent, request } = await this._loadUtils(); + const headers = await this._parameters.headers(); return new Promise(resolve => { sendWithHttp( request, - this._parameters, + this._parameters.url, + headers, + this._parameters.compression, + this._parameters.userAgent, agent, data, result => { diff --git a/experimental/packages/otlp-exporter-base/src/transport/http-transport-types.ts b/experimental/packages/otlp-exporter-base/src/transport/http-transport-types.ts index 8a1bb5c5ede..8dd86711a4c 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/http-transport-types.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/http-transport-types.ts @@ -14,9 +14,11 @@ * limitations under the License. */ +import { HeadersFactory } from '../configuration/otlp-http-configuration'; + export interface HttpRequestParameters { url: string; - headers: () => Record; + headers: HeadersFactory; compression: 'gzip' | 'none'; userAgent?: string; } diff --git a/experimental/packages/otlp-exporter-base/src/transport/http-transport-utils.ts b/experimental/packages/otlp-exporter-base/src/transport/http-transport-utils.ts index ed2c86b6b7d..f0675ffdd6e 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/http-transport-utils.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/http-transport-utils.ts @@ -17,7 +17,6 @@ import type * as http from 'http'; import type * as https from 'https'; import * as zlib from 'zlib'; import { Readable } from 'stream'; -import { HttpRequestParameters } from './http-transport-types'; import { ExportResponse } from '../export-response'; import { isExportRetryable, @@ -39,17 +38,19 @@ const DEFAULT_USER_AGENT = `OTel-OTLP-Exporter-JavaScript/${VERSION}`; */ export function sendWithHttp( request: typeof https.request | typeof http.request, - params: HttpRequestParameters, + url: string, + headers: Record, + compression: 'gzip' | 'none', + userAgent: string | undefined, agent: http.Agent | https.Agent, data: Uint8Array, onDone: (response: ExportResponse) => void, timeoutMillis: number ): void { - const parsedUrl = new URL(params.url); + const parsedUrl = new URL(url); - const headers = { ...params.headers() }; - if (params.userAgent) { - headers['User-Agent'] = `${params.userAgent} ${DEFAULT_USER_AGENT}`; + if (userAgent) { + headers['User-Agent'] = `${userAgent} ${DEFAULT_USER_AGENT}`; } else { headers['User-Agent'] = DEFAULT_USER_AGENT; } @@ -107,7 +108,7 @@ export function sendWithHttp( }); }); - compressAndSend(req, params.compression, data, (error: Error) => { + compressAndSend(req, compression, data, (error: Error) => { onDone({ status: 'failure', error, diff --git a/experimental/packages/otlp-exporter-base/src/transport/send-beacon-transport.ts b/experimental/packages/otlp-exporter-base/src/transport/send-beacon-transport.ts index 448265c27b0..36a50a1f1a6 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/send-beacon-transport.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/send-beacon-transport.ts @@ -17,23 +17,26 @@ import { IExporterTransport } from '../exporter-transport'; import { ExportResponse } from '../export-response'; import { diag } from '@opentelemetry/api'; +import { HeadersFactory } from '../configuration/otlp-http-configuration'; export interface SendBeaconParameters { url: string; /** - * for instance 'application/x-protobuf' + * Only `Content-Type` will be used, sendBeacon does not support custom headers + * https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon */ - blobType: string; + headers: HeadersFactory; } class SendBeaconTransport implements IExporterTransport { constructor(private _params: SendBeaconParameters) {} - send(data: Uint8Array): Promise { + async send(data: Uint8Array): Promise { + const blobType = (await this._params.headers())['Content-Type']; return new Promise(resolve => { if ( navigator.sendBeacon( this._params.url, - new Blob([data], { type: this._params.blobType }) + new Blob([data], { type: blobType }) ) ) { // no way to signal retry, treat everything as success diff --git a/experimental/packages/otlp-exporter-base/src/transport/xhr-transport.ts b/experimental/packages/otlp-exporter-base/src/transport/xhr-transport.ts index 31ff3bb9b0f..cf16c6bff64 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/xhr-transport.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/xhr-transport.ts @@ -23,6 +23,7 @@ import { } from '../is-export-retryable'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import type { createFetchTransport } from './fetch-transport'; +import { HeadersFactory } from '../configuration/otlp-http-configuration'; /** * @deprecated favor the fetch transport @@ -30,18 +31,18 @@ import type { createFetchTransport } from './fetch-transport'; */ export interface XhrRequestParameters { url: string; - headers: () => Record; + headers: HeadersFactory; } class XhrTransport implements IExporterTransport { constructor(private _parameters: XhrRequestParameters) {} - send(data: Uint8Array, timeoutMillis: number): Promise { - return new Promise(resolve => { + async send(data: Uint8Array, timeoutMillis: number): Promise { + const headers = await this._parameters.headers(); + const response = await new Promise(resolve => { const xhr = new XMLHttpRequest(); xhr.timeout = timeoutMillis; xhr.open('POST', this._parameters.url); - const headers = this._parameters.headers(); Object.entries(headers).forEach(([k, v]) => { xhr.setRequestHeader(k, v); }); @@ -89,6 +90,8 @@ class XhrTransport implements IExporterTransport { xhr.send(data); }); + + return response; } shutdown() { diff --git a/experimental/packages/otlp-exporter-base/src/util.ts b/experimental/packages/otlp-exporter-base/src/util.ts index 96ee08366c2..a8eddb255f4 100644 --- a/experimental/packages/otlp-exporter-base/src/util.ts +++ b/experimental/packages/otlp-exporter-base/src/util.ts @@ -21,19 +21,17 @@ import { diag } from '@opentelemetry/api'; * @param partialHeaders */ export function validateAndNormalizeHeaders( - partialHeaders: (() => Record) | undefined -): () => Record { - return () => { - const headers: Record = {}; - Object.entries(partialHeaders?.() ?? {}).forEach(([key, value]) => { - if (typeof value !== 'undefined') { - headers[key] = String(value); - } else { - diag.warn( - `Header "${key}" has invalid value (${value}) and will be ignored` - ); - } - }); - return headers; - }; + partialHeaders: Record | undefined +): Record { + const headers: Record = {}; + Object.entries(partialHeaders ?? {}).forEach(([key, value]) => { + if (typeof value !== 'undefined') { + headers[key] = String(value); + } else { + diag.warn( + `Header "${key}" has invalid value (${value}) and will be ignored` + ); + } + }); + return headers; } diff --git a/experimental/packages/otlp-exporter-base/test/browser/configuration/otlp-http-configuration.test.ts b/experimental/packages/otlp-exporter-base/test/browser/configuration/otlp-http-configuration.test.ts index ef41dec66d8..23fe5799cd4 100644 --- a/experimental/packages/otlp-exporter-base/test/browser/configuration/otlp-http-configuration.test.ts +++ b/experimental/packages/otlp-exporter-base/test/browser/configuration/otlp-http-configuration.test.ts @@ -25,7 +25,7 @@ describe('mergeOtlpHttpConfigurationWithDefaults (browser)', function () { timeoutMillis: 1, compression: 'none', concurrencyLimit: 2, - headers: () => ({ 'User-Agent': 'default-user-agent' }), + headers: async () => ({ 'User-Agent': 'default-user-agent' }), }; it('resolves user-provided relative url to document', function () { diff --git a/experimental/packages/otlp-exporter-base/test/browser/fetch-transport.test.ts b/experimental/packages/otlp-exporter-base/test/browser/fetch-transport.test.ts index 82efa2411b1..2e7316c6727 100644 --- a/experimental/packages/otlp-exporter-base/test/browser/fetch-transport.test.ts +++ b/experimental/packages/otlp-exporter-base/test/browser/fetch-transport.test.ts @@ -25,7 +25,7 @@ import { const testTransportParameters = { url: 'http://example.test', - headers: () => ({ + headers: async () => ({ foo: 'foo-value', bar: 'bar-value', 'Content-Type': 'application/json', diff --git a/experimental/packages/otlp-exporter-base/test/browser/send-beacon-transport.test.ts b/experimental/packages/otlp-exporter-base/test/browser/send-beacon-transport.test.ts index 86a44d04352..d9847860012 100644 --- a/experimental/packages/otlp-exporter-base/test/browser/send-beacon-transport.test.ts +++ b/experimental/packages/otlp-exporter-base/test/browser/send-beacon-transport.test.ts @@ -28,7 +28,7 @@ describe('SendBeaconTransport', function () { const sendStub = sinon.stub(navigator, 'sendBeacon').returns(false); const transport = createSendBeaconTransport({ url: 'http://example.test', - blobType: 'application/json', + headers: async () => ({ 'Content-Type': 'application/json' }), }); // act @@ -55,7 +55,7 @@ describe('SendBeaconTransport', function () { const sendStub = sinon.stub(navigator, 'sendBeacon').returns(true); const transport = createSendBeaconTransport({ url: 'http://example.test', - blobType: 'application/json', + headers: async () => ({ 'Content-Type': 'application/json' }), }); // act diff --git a/experimental/packages/otlp-exporter-base/test/browser/xhr-transport.test.ts b/experimental/packages/otlp-exporter-base/test/browser/xhr-transport.test.ts index 5928206469a..1fed66d30d7 100644 --- a/experimental/packages/otlp-exporter-base/test/browser/xhr-transport.test.ts +++ b/experimental/packages/otlp-exporter-base/test/browser/xhr-transport.test.ts @@ -26,7 +26,7 @@ import { ensureHeadersContain } from '../testHelper'; const testTransportParameters = { url: 'http://example.test', - headers: () => ({ + headers: async () => ({ foo: 'foo-value', bar: 'bar-value', 'Content-Type': 'application/json', @@ -36,8 +36,30 @@ const testTransportParameters = { const requestTimeout = 1000; const testPayload = Uint8Array.from([1, 2, 3]); +function respondWhenRequestExists( + server: sinon.SinonFakeServer, + responder: (request: sinon.SinonFakeXMLHttpRequest) => void +) { + function tryRespond() { + if (server.requests.length > 0) { + responder(server.requests[0]); + } else { + setTimeout(tryRespond, 0); + } + } + setTimeout(tryRespond, 0); +} + +function hasOnTimeout(request: unknown): request is { ontimeout: () => void } { + if (request == null || typeof request != 'object') { + return false; + } + + return 'ontimeout' in request && typeof request['ontimeout'] === 'function'; +} + describe('XhrTransport', function () { - afterEach(() => { + afterEach(function () { sinon.restore(); }); @@ -48,7 +70,7 @@ describe('XhrTransport', function () { const transport = createXhrTransport(testTransportParameters); let request: sinon.SinonFakeXMLHttpRequest; - queueMicrotask(() => { + respondWhenRequestExists(server, () => { // this executes after the act block request = server.requests[0]; request.respond(200, {}, 'test response'); @@ -84,7 +106,7 @@ describe('XhrTransport', function () { const server = sinon.fakeServer.create(); const transport = createXhrTransport(testTransportParameters); - queueMicrotask(() => { + respondWhenRequestExists(server, () => { // this executes after the act block const request = server.requests[0]; request.respond(404, {}, ''); @@ -107,7 +129,7 @@ describe('XhrTransport', function () { const server = sinon.fakeServer.create(); const transport = createXhrTransport(testTransportParameters); - queueMicrotask(() => { + respondWhenRequestExists(server, () => { // this executes after the act block const request = server.requests[0]; request.respond(503, { 'Retry-After': 5 }, ''); @@ -132,10 +154,18 @@ describe('XhrTransport', function () { it('returns failure when request times out', function (done) { // arrange // A fake server needed, otherwise the message will not be a timeout but a failure to connect. - sinon.useFakeServer(); - const clock = sinon.useFakeTimers(); + const server = sinon.useFakeServer(); const transport = createXhrTransport(testTransportParameters); + respondWhenRequestExists(server, () => { + // this executes after the act block + const request = server.requests[0]; + // Manually trigger the ontimeout event, but don't respond. + if (hasOnTimeout(request)) { + request.ontimeout(); + } + }); + //act transport.send(testPayload, requestTimeout).then(response => { // assert @@ -150,7 +180,6 @@ describe('XhrTransport', function () { } done(); }, done /* catch any rejections */); - clock.tick(requestTimeout + 100); }); it('returns failure when no server exists', function (done) { diff --git a/experimental/packages/otlp-exporter-base/test/common/configuration/otlp-http-configuration.test.ts b/experimental/packages/otlp-exporter-base/test/common/configuration/otlp-http-configuration.test.ts index e9e12f64c05..19a4ceff5a8 100644 --- a/experimental/packages/otlp-exporter-base/test/common/configuration/otlp-http-configuration.test.ts +++ b/experimental/packages/otlp-exporter-base/test/common/configuration/otlp-http-configuration.test.ts @@ -27,43 +27,43 @@ describe('mergeOtlpHttpConfigurationWithDefaults (common)', function () { timeoutMillis: 1, compression: 'none', concurrencyLimit: 2, - headers: () => ({ 'User-Agent': 'default-user-agent' }), + headers: async () => ({ 'User-Agent': 'default-user-agent' }), }; describe('headers', function () { - it('merges headers instead of overriding', function () { + it('merges headers instead of overriding', async function () { const config = mergeOtlpHttpConfigurationWithDefaults( { - headers: () => ({ foo: 'user' }), + headers: async () => ({ foo: 'user' }), }, { - headers: () => ({ foo: 'fallback', bar: 'fallback' }), + headers: async () => ({ foo: 'fallback', bar: 'fallback' }), }, testDefaults ); - assert.deepStrictEqual(config.headers(), { + assert.deepStrictEqual(await config.headers(), { 'User-Agent': 'default-user-agent', foo: 'user', bar: 'fallback', }); }); - it('does not override default (required) header', function () { + it('does not override default (required) header', async function () { const config = mergeOtlpHttpConfigurationWithDefaults( { - headers: () => ({ 'User-Agent': 'custom' }), + headers: async () => ({ 'User-Agent': 'custom' }), }, { - headers: () => ({ 'User-Agent': 'custom-fallback' }), + headers: async () => ({ 'User-Agent': 'custom-fallback' }), }, testDefaults ); - assert.deepStrictEqual(config.headers(), { + assert.deepStrictEqual(await config.headers(), { 'User-Agent': 'default-user-agent', }); }); - it('drops user-provided headers with undefined values', function () { + it('drops user-provided headers with undefined values', async function () { const config = mergeOtlpHttpConfigurationWithDefaults( { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -77,7 +77,7 @@ describe('mergeOtlpHttpConfigurationWithDefaults (common)', function () { {}, testDefaults ); - assert.deepStrictEqual(config.headers(), { + assert.deepStrictEqual(await config.headers(), { foo: 'foo-user-provided', baz: 'null', 'User-Agent': 'default-user-agent', diff --git a/experimental/packages/otlp-exporter-base/test/common/util.test.ts b/experimental/packages/otlp-exporter-base/test/common/util.test.ts index d9f6c11ce38..f7c148a5d6d 100644 --- a/experimental/packages/otlp-exporter-base/test/common/util.test.ts +++ b/experimental/packages/otlp-exporter-base/test/common/util.test.ts @@ -33,7 +33,7 @@ describe('parseHeaders', function () { }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore simulating plain JS usage - const result = validateAndNormalizeHeaders(() => headers)(); + const result = validateAndNormalizeHeaders(headers); assert.deepStrictEqual(result, { foo2: 'bar', foo3: '1', @@ -46,7 +46,7 @@ describe('parseHeaders', function () { }); it('should parse undefined', function () { - const result = validateAndNormalizeHeaders(undefined)(); + const result = validateAndNormalizeHeaders(undefined); assert.deepStrictEqual(result, {}); }); }); diff --git a/experimental/packages/otlp-exporter-base/test/node/configuration/convert-legacy-node-otlp-http-options.test.ts b/experimental/packages/otlp-exporter-base/test/node/configuration/convert-legacy-node-otlp-http-options.test.ts index 0c44e60406d..9a1e43b0b1a 100644 --- a/experimental/packages/otlp-exporter-base/test/node/configuration/convert-legacy-node-otlp-http-options.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/configuration/convert-legacy-node-otlp-http-options.test.ts @@ -91,4 +91,25 @@ describe('convertLegacyHttpOptions', function () { assert.ok(agent.options.keepAlive); assert.strictEqual(agent.options.port, 1234); }); + + it('should pass along header factory as-is', async function () { + const headers = { foo: 'bar' }; + const options = convertLegacyHttpOptions( + { + headers: async () => headers, + }, + 'SIGNAL', + 'v1/signal', + {} + ); + + // act + const initialHeaders = await options.headers(); + headers.foo = 'baz'; + const laterHeaders = await options.headers(); + + // assert + assert.deepStrictEqual(initialHeaders, { foo: 'bar' }); + assert.deepStrictEqual(laterHeaders, { foo: 'baz' }); + }); }); diff --git a/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts index 7d6a3f5dabb..02195587ebb 100644 --- a/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts @@ -24,7 +24,7 @@ describe('mergeOtlpNodeHttpConfigurationWithDefaults', function () { timeoutMillis: 1, compression: 'none', concurrencyLimit: 2, - headers: () => ({}), + headers: async () => ({}), agentFactory: () => null!, userAgent: `OTel-OTLP-Exporter-JavaScript/${VERSION}`, }; diff --git a/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-env-configuration.test.ts b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-env-configuration.test.ts index 66d843a65ba..1c5fa225b70 100644 --- a/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-env-configuration.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-env-configuration.test.ts @@ -41,7 +41,7 @@ describe('getHttpConfigurationFromEnvironment', function () { assert.strictEqual(config.headers, undefined); }); - it('merges headers instead of overriding', function () { + it('merges headers instead of overriding', async function () { process.env.OTEL_EXPORTER_OTLP_HEADERS = 'key1=value1,key2=value2'; process.env.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'key1=metrics'; @@ -49,26 +49,26 @@ describe('getHttpConfigurationFromEnvironment', function () { 'METRICS', 'v1/metrics' ); - assert.deepEqual(config.headers?.(), { + assert.deepEqual(await config.headers?.(), { key1: 'metrics', key2: 'value2', }); }); - it('allows non-specific only headers', function () { + it('allows non-specific only headers', async function () { process.env.OTEL_EXPORTER_OTLP_HEADERS = 'key1=value1,key2=value2'; const config = getNodeHttpConfigurationFromEnvironment( 'METRICS', 'v1/metrics' ); - assert.deepEqual(config.headers?.(), { + assert.deepEqual(await config.headers?.(), { key1: 'value1', key2: 'value2', }); }); - it('allows specific only headers', function () { + it('allows specific only headers', async function () { process.env.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'key1=value1,key2=value2'; @@ -76,7 +76,7 @@ describe('getHttpConfigurationFromEnvironment', function () { 'METRICS', 'v1/metrics' ); - assert.deepEqual(config.headers?.(), { + assert.deepEqual(await config.headers?.(), { key1: 'value1', key2: 'value2', }); diff --git a/experimental/packages/otlp-exporter-base/test/node/http-exporter-transport.test.ts b/experimental/packages/otlp-exporter-base/test/node/http-exporter-transport.test.ts index aad98576840..30d464bd185 100644 --- a/experimental/packages/otlp-exporter-base/test/node/http-exporter-transport.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/http-exporter-transport.test.ts @@ -56,7 +56,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -93,7 +93,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://jocajhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: protocol => { assert.strictEqual(protocol, 'http:'); @@ -122,7 +122,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -146,7 +146,7 @@ describe('HttpExporterTransport', function () { // act const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -171,7 +171,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -208,7 +208,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -241,7 +241,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -262,7 +262,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ // use wrong port url: 'http://example.test', - headers: () => ({}), + headers: async () => ({}), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -312,7 +312,7 @@ describe('HttpExporterTransport', function () { // act const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({ foo: 'foo-value', bar: 'bar-value' }), + headers: async () => ({ foo: 'foo-value', bar: 'bar-value' }), compression: 'none', agentFactory: () => new http.Agent(), }); @@ -361,7 +361,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: () => ({ foo: 'foo-value', bar: 'bar-value' }), + headers: async () => ({ foo: 'foo-value', bar: 'bar-value' }), compression: 'gzip', agentFactory: () => new http.Agent(), }); diff --git a/experimental/packages/otlp-exporter-base/test/node/http-transport-utils.test.ts b/experimental/packages/otlp-exporter-base/test/node/http-transport-utils.test.ts index deac0b6d92e..7b3906f004d 100644 --- a/experimental/packages/otlp-exporter-base/test/node/http-transport-utils.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/http-transport-utils.test.ts @@ -54,11 +54,10 @@ describe('sendWithHttp', function () { let firstCallback = true; sendWithHttp( requestFn, - { - url: 'http://localhost:8080', - compression: 'gzip', - headers: () => ({}), - }, + 'http://localhost:8080', + {}, + 'gzip', + undefined, new http.Agent(), Buffer.from([1, 2, 3]), // TODO: the `onDone` callback is called twice because there are two error handlers @@ -82,12 +81,10 @@ describe('sendWithHttp', function () { let firstCallback = true; sendWithHttp( requestFn, - { - url: 'http://localhost:8080', - compression: 'gzip', - headers: () => ({}), - userAgent: 'Transport-User-Agent/1.2.3', - }, + 'http://localhost:8080', + {}, + 'gzip', + 'Transport-User-Agent/1.2.3', new http.Agent(), Buffer.from([1, 2, 3]), // TODO: the `onDone` callback is called twice because there are two error handlers diff --git a/experimental/packages/otlp-exporter-base/test/node/otlp-http-export-delegate.test.ts b/experimental/packages/otlp-exporter-base/test/node/otlp-http-export-delegate.test.ts index 6a890d3b643..a1c7bd57a0b 100644 --- a/experimental/packages/otlp-exporter-base/test/node/otlp-http-export-delegate.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/otlp-http-export-delegate.test.ts @@ -58,7 +58,7 @@ describe('createOtlpHttpExportDelegate', function () { agentFactory: () => new http.Agent(), compression: 'none', concurrencyLimit: 30, - headers: () => ({}), + headers: async () => ({}), timeoutMillis: 1000, }, serializer