diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 7cbd5bd50a9..1d41e62f082 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -7,6 +7,21 @@ All notable changes to experimental packages in this project will be documented ### :boom: Breaking Change +* feat(otlp-exporter-base)!: collapse base classes into one [#5031](https://github.com/open-telemetry/opentelemetry-js/pull/5031) @pichlermarc + * `OTLPExporterNodeBase` has been removed in favor of a platform-agnostic implementation (`OTLPExporterBase`) + * `OTLPExporterBrowserBase` has been removed in favor of a platform-agnostic implementation (`OTLPExporterBase`) + * `ExportServiceError` was intended for internal use and has been dropped from exports + * `validateAndNormalizeHeaders` was intended for internal use and has been dropped from exports + * `OTLPExporterBase` all properties are now private, the constructor now takes an `IOTLPExportDelegate`, the type parameter for config type has been dropped. + * This type is scheduled for removal in a future version of this package, please treat all exporters as `SpanExporter`, `PushMetricExporter` or `LogRecordExporter`, based on their respective type. +* feat(otlp-grpc-exporter-base)!: collapse base classes into one [#5031](https://github.com/open-telemetry/opentelemetry-js/pull/5031) @pichlermarc + * `OTLPGRPCExporterNodeBase` has been removed in favor of a platform-agnostic implementation (`OTLPExporterBase` from `@opentelemetry/otlp-exporter-base`) +* feat(otlp-exporter-base): internally accept a http header provider function only [#5179](https://github.com/open-telemetry/opentelemetry-js/pull/5179) @pichlermarc +* feat(otlp-transformer)!: accept `ResourceMetrics` in serializers instead of `ResourceMetrics[]` + * (user-facing): `ProtobufMetricsSerializer` now only accepts `ResourceMetrics` instead of `ResourceMetrics[]` to align with `PushMetricExporter` requirements + * (user-facing): `JsonMetricsSerializer` now only accepts `ResourceMetrics` instead of `ResourceMetrics[]` to align with `PushMetricExporter` requirements + + ### :rocket: (Enhancement) ### :bug: (Bug Fix) @@ -53,18 +68,6 @@ All notable changes to experimental packages in this project will be documented * GetFunction * Func * Err -* feat(otlp-exporter-base)!: collapse base classes into one [#5031](https://github.com/open-telemetry/opentelemetry-js/pull/5031) - * `OTLPExporterNodeBase` has been removed in favor of a platform-agnostic implementation (`OTLPExporterBase`) - * `OTLPExporterBrowserBase` has been removed in favor of a platform-agnostic implementation (`OTLPExporterBase`) - * `ExportServiceError` was intended for internal use and has been dropped from exports - * `validateAndNormalizeHeaders` was intended for internal use and has been dropped from exports - * `OTLPExporterBase` all properties are now private, the constructor now takes an `IOTLPExportDelegate`, the type parameter for config type has been dropped. - * This type is scheduled for removal in a future version of this package, please treat all exporters as `SpanExporter`, `PushMetricExporter` or `LogRecordExporter`, based on their respective type. -* feat(otlp-grpc-exporter-base)!: collapse base classes into one [#5031](https://github.com/open-telemetry/opentelemetry-js/pull/5031) - * `OTLPGRPCExporterNodeBase` has been removed in favor of a platform-agnostic implementation (`OTLPExporterBase` from `@opentelemetry/otlp-exporter-base`) -* feat(otlp-transformer)!: accept `ResourceMetrics` in serializers instead of `ResourceMetrics[]` - * (user-facing): `ProtobufMetricsSerializer` now only accepts `ResourceMetrics` instead of `ResourceMetrics[]` to align with `PushMetricExporter` requirements - * (user-facing): `JsonMetricsSerializer` now only accepts `ResourceMetrics` instead of `ResourceMetrics[]` to align with `PushMetricExporter` requirements ### :rocket: (Enhancement) 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 955945e6f10..3f72eda1605 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,6 +19,7 @@ import { OtlpHttpConfiguration, } from './otlp-http-configuration'; import { OTLPExporterNodeConfigBase } from './legacy-node-configuration'; +import { wrapStaticHeadersInFunction } from './shared-configuration'; /** * @deprecated this will be removed in 2.0 @@ -36,7 +37,7 @@ export function convertLegacyBrowserHttpOptions( { url: config.url, timeoutMillis: config.timeoutMillis, - headers: config.headers, + headers: wrapStaticHeadersInFunction(config.headers), concurrencyLimit: config.concurrencyLimit, }, {}, // no fallback for browser case 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 6380a7d3f9e..d2657f5a12d 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 @@ -23,6 +23,7 @@ import { getHttpConfigurationFromEnvironment } from './otlp-http-env-configurati import type * as http from 'http'; import type * as https from 'https'; import { diag } from '@opentelemetry/api'; +import { wrapStaticHeadersInFunction } from './shared-configuration'; function convertLegacyAgentOptions( config: OTLPExporterNodeConfigBase @@ -67,7 +68,7 @@ export function convertLegacyHttpOptions( return mergeOtlpHttpConfigurationWithDefaults( { url: config.url, - headers: config.headers, + headers: wrapStaticHeadersInFunction(config.headers), concurrencyLimit: config.concurrencyLimit, timeoutMillis: config.timeoutMillis, compression: config.compression, 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 0b78a7777bc..7a1853cca9b 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 @@ -27,32 +27,34 @@ import type * as https from 'https'; export interface OtlpHttpConfiguration extends OtlpSharedConfiguration { url: string; - headers: Record; + headers: () => Record; agentOptions: http.AgentOptions | https.AgentOptions; } function mergeHeaders( - userProvidedHeaders: Record | undefined | null, - fallbackHeaders: Record | undefined | null, - defaultHeaders: Record -): Record { + userProvidedHeaders: (() => Record) | undefined | null, + fallbackHeaders: (() => Record) | undefined | null, + defaultHeaders: () => Record +): () => Record { const requiredHeaders = { - ...defaultHeaders, + ...defaultHeaders(), }; const headers = {}; - // add fallback ones first - if (fallbackHeaders != null) { - Object.assign(headers, fallbackHeaders); - } + return () => { + // add fallback ones first + if (fallbackHeaders != null) { + Object.assign(headers, fallbackHeaders()); + } - // override with user-provided ones - if (userProvidedHeaders != null) { - Object.assign(headers, userProvidedHeaders); - } + // override with user-provided ones + if (userProvidedHeaders != null) { + Object.assign(headers, userProvidedHeaders()); + } - // override required ones. - return Object.assign(headers, requiredHeaders); + // override required ones. + return Object.assign(headers, requiredHeaders); + }; } function validateUserProvidedUrl(url: string | undefined): string | undefined { @@ -107,7 +109,7 @@ export function getHttpConfigurationDefaults( ): OtlpHttpConfiguration { return { ...getSharedConfigurationDefaults(), - headers: requiredHeaders, + headers: () => requiredHeaders, url: 'http://localhost:4318/' + signalResourcePath, agentOptions: { keepAlive: true }, }; diff --git a/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts index af15de4c85a..ba05242c9c1 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts @@ -17,8 +17,11 @@ import { baggageUtils } from '@opentelemetry/core'; import { diag } from '@opentelemetry/api'; import { getSharedConfigurationFromEnvironment } from './shared-env-configuration'; import { OtlpHttpConfiguration } from './otlp-http-configuration'; +import { wrapStaticHeadersInFunction } from './shared-configuration'; -function getHeadersFromEnv(signalIdentifier: string) { +function getStaticHeadersFromEnv( + signalIdentifier: string +): Record | undefined { const signalSpecificRawHeaders = process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_HEADERS`]?.trim(); const nonSignalSpecificRawHeaders = @@ -126,6 +129,8 @@ export function getHttpConfigurationFromEnvironment( url: getSpecificUrlFromEnv(signalIdentifier) ?? getNonSpecificUrlFromEnv(signalResourcePath), - headers: getHeadersFromEnv(signalIdentifier), + headers: wrapStaticHeadersInFunction( + getStaticHeadersFromEnv(signalIdentifier) + ), }; } 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 e69fe35beeb..d9d589e3699 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts @@ -41,6 +41,16 @@ export function validateTimeoutMillis(timeoutMillis: number) { ); } +export function wrapStaticHeadersInFunction( + headers: Record | undefined +): (() => Record) | undefined { + if (headers == null) { + return undefined; + } + + return () => headers; +} + /** * @param userProvidedConfiguration Configuration options provided by the user in code. * @param fallbackConfiguration Fallback to use when the {@link userProvidedConfiguration} does not specify an option. 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 dc830d3dec9..402348e338a 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 @@ -44,7 +44,7 @@ export function createOtlpSendBeaconExportDelegate( createRetryingTransport({ transport: createSendBeaconTransport({ url: options.url, - blobType: options.headers['Content-Type'], + blobType: options.headers()['Content-Type'], }), }) ); 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 07ee5b9dafb..593d378e1d1 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 @@ -28,7 +28,7 @@ export type sendWithHttp = ( export interface HttpRequestParameters { url: string; - headers: Record; + headers: () => Record; compression: 'gzip' | 'none'; agentOptions: http.AgentOptions | https.AgentOptions; } 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 6f9247b43ec..99c55b0872e 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 @@ -49,7 +49,7 @@ export function sendWithHttp( path: parsedUrl.pathname, method: 'POST', headers: { - ...params.headers, + ...params.headers(), }, agent: agent, }; 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 36d497549ae..e2802aa521b 100644 --- a/experimental/packages/otlp-exporter-base/src/transport/xhr-transport.ts +++ b/experimental/packages/otlp-exporter-base/src/transport/xhr-transport.ts @@ -24,7 +24,7 @@ import { export interface XhrRequestParameters { url: string; - headers: Record; + headers: () => Record; } class XhrTransport implements IExporterTransport { @@ -35,7 +35,8 @@ class XhrTransport implements IExporterTransport { const xhr = new XMLHttpRequest(); xhr.timeout = timeoutMillis; xhr.open('POST', this._parameters.url); - Object.entries(this._parameters.headers).forEach(([k, v]) => { + const headers = this._parameters.headers(); + Object.entries(headers).forEach(([k, v]) => { xhr.setRequestHeader(k, v); }); @@ -80,9 +81,7 @@ class XhrTransport implements IExporterTransport { }); }; - xhr.send( - new Blob([data], { type: this._parameters.headers['Content-Type'] }) - ); + xhr.send(new Blob([data], { type: headers['Content-Type'] })); }); } diff --git a/experimental/packages/otlp-exporter-base/src/util.ts b/experimental/packages/otlp-exporter-base/src/util.ts index 515b913402d..96ee08366c2 100644 --- a/experimental/packages/otlp-exporter-base/src/util.ts +++ b/experimental/packages/otlp-exporter-base/src/util.ts @@ -21,17 +21,19 @@ import { diag } from '@opentelemetry/api'; * @param partialHeaders */ export function validateAndNormalizeHeaders( - partialHeaders: Partial> = {} -): 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; + 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; + }; } 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 af14aa41f2d..7fd82fc0544 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,11 +26,11 @@ import { ensureHeadersContain } from '../testHelper'; const testTransportParameters = { url: 'http://example.test', - headers: { + headers: () => ({ foo: 'foo-value', bar: 'bar-value', 'Content-Type': 'application/json', - }, + }), }; const requestTimeout = 1000; 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 a5197deee58..d44e885b48e 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,7 +27,7 @@ describe('mergeOtlpHttpConfigurationWithDefaults', function () { timeoutMillis: 1, compression: 'none', concurrencyLimit: 2, - headers: { 'User-Agent': 'default-user-agent' }, + headers: () => ({ 'User-Agent': 'default-user-agent' }), agentOptions: { keepAlive: true }, }; @@ -35,14 +35,14 @@ describe('mergeOtlpHttpConfigurationWithDefaults', function () { it('merges headers instead of overriding', function () { const config = mergeOtlpHttpConfigurationWithDefaults( { - headers: { foo: 'user' }, + headers: () => ({ foo: 'user' }), }, { - headers: { foo: 'fallback', bar: 'fallback' }, + headers: () => ({ foo: 'fallback', bar: 'fallback' }), }, testDefaults ); - assert.deepStrictEqual(config.headers, { + assert.deepStrictEqual(config.headers(), { 'User-Agent': 'default-user-agent', foo: 'user', bar: 'fallback', @@ -52,14 +52,14 @@ describe('mergeOtlpHttpConfigurationWithDefaults', function () { it('does not override default (required) header', function () { const config = mergeOtlpHttpConfigurationWithDefaults( { - headers: { 'User-Agent': 'custom' }, + headers: () => ({ 'User-Agent': 'custom' }), }, { - headers: { 'User-Agent': 'custom-fallback' }, + headers: () => ({ 'User-Agent': 'custom-fallback' }), }, testDefaults ); - assert.deepStrictEqual(config.headers, { + assert.deepStrictEqual(config.headers(), { 'User-Agent': 'default-user-agent', }); }); @@ -69,12 +69,16 @@ describe('mergeOtlpHttpConfigurationWithDefaults', function () { { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore simulating plain JavaScript usage, ignoring types - headers: { foo: 'foo-user-provided', bar: undefined, baz: null }, + headers: () => ({ + foo: 'foo-user-provided', + bar: undefined, + baz: null, + }), }, {}, testDefaults ); - assert.deepStrictEqual(config.headers, { + assert.deepStrictEqual(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 3bd00a2f226..d9f6c11ce38 100644 --- a/experimental/packages/otlp-exporter-base/test/common/util.test.ts +++ b/experimental/packages/otlp-exporter-base/test/common/util.test.ts @@ -31,7 +31,9 @@ describe('parseHeaders', function () { foo2: 'bar', foo3: 1, }; - const result = validateAndNormalizeHeaders(headers); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore simulating plain JS usage + const result = validateAndNormalizeHeaders(() => headers)(); assert.deepStrictEqual(result, { foo2: 'bar', foo3: '1', @@ -44,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/otlp-http-env-configuration.test.ts b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-env-configuration.test.ts index 5b4bc4dc473..13913b1f1c4 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 @@ -49,7 +49,7 @@ describe('getHttpConfigurationFromEnvironment', function () { 'METRICS', 'v1/metrics' ); - assert.deepEqual(config.headers, { + assert.deepEqual(config.headers?.(), { key1: 'metrics', key2: 'value2', }); @@ -62,7 +62,7 @@ describe('getHttpConfigurationFromEnvironment', function () { 'METRICS', 'v1/metrics' ); - assert.deepEqual(config.headers, { + assert.deepEqual(config.headers?.(), { key1: 'value1', key2: 'value2', }); @@ -76,7 +76,7 @@ describe('getHttpConfigurationFromEnvironment', function () { 'METRICS', 'v1/metrics' ); - assert.deepEqual(config.headers, { + assert.deepEqual(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 6d9c935025d..d4c9230ae1b 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 @@ -54,7 +54,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -80,7 +80,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -104,7 +104,7 @@ describe('HttpExporterTransport', function () { // act const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -129,7 +129,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -158,7 +158,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -191,7 +191,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -212,7 +212,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ // use wrong port url: 'http://example.test', - headers: {}, + headers: () => ({}), compression: 'none', agentOptions: {}, }); @@ -262,7 +262,7 @@ describe('HttpExporterTransport', function () { // act const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: { foo: 'foo-value', bar: 'bar-value' }, + headers: () => ({ foo: 'foo-value', bar: 'bar-value' }), compression: 'none', agentOptions: {}, }); @@ -311,7 +311,7 @@ describe('HttpExporterTransport', function () { const transport = createHttpExporterTransport({ url: 'http://localhost:8080', - headers: { foo: 'foo-value', bar: 'bar-value' }, + headers: () => ({ foo: 'foo-value', bar: 'bar-value' }), compression: 'gzip', agentOptions: {}, }); 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 5ee0f414dbf..e5cafc4df9d 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 () { agentOptions: {}, compression: 'none', concurrencyLimit: 30, - headers: {}, + headers: () => ({}), timeoutMillis: 1000, }, serializer