Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<string, string>>` in addition to the existing `Record<string, string>` 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
import { OTLPExporterNodeConfigBase } from './legacy-node-configuration';
import { diag } from '@opentelemetry/api';
import { wrapStaticHeadersInFunction } from './shared-configuration';
import {
getNodeHttpConfigurationDefaults,
HttpAgentFactory,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>;
/**
* 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 <caption> Using headers options directly: </caption>
* headers: {
* Authorization: "Api-Token my-secret-token",
* }
*
* @example <caption> Using a custom factory function </caption>
* 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<string, string> | HeadersFactory;
url?: string;
concurrencyLimit?: number;
/** Maximum time the OTLP exporter will wait for each batch export.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,35 @@ import {
} from './shared-configuration';
import { validateAndNormalizeHeaders } from '../util';

export type HeadersFactory = () => Promise<Record<string, string>>;

export interface OtlpHttpConfiguration extends OtlpSharedConfiguration {
url: string;
headers: () => Record<string, string>;
headers: HeadersFactory;
}

function mergeHeaders(
userProvidedHeaders: (() => Record<string, string>) | undefined | null,
fallbackHeaders: (() => Record<string, string>) | undefined | null,
defaultHeaders: () => Record<string, string>
): () => Record<string, string> {
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.
Expand Down Expand Up @@ -84,7 +89,7 @@ export function mergeOtlpHttpConfigurationWithDefaults(
defaultConfiguration
),
headers: mergeHeaders(
validateAndNormalizeHeaders(userProvidedConfiguration.headers),
userProvidedConfiguration.headers,
fallbackConfiguration.headers,
defaultConfiguration.headers
),
Expand All @@ -101,7 +106,7 @@ export function getHttpConfigurationDefaults(
): OtlpHttpConfiguration {
return {
...getSharedConfigurationDefaults(),
headers: () => requiredHeaders,
headers: async () => requiredHeaders,
url: 'http://localhost:4318/' + signalResourcePath,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import { HeadersFactory } from './otlp-http-configuration';

/**
* Configuration shared across all OTLP exporters
*
Expand All @@ -39,12 +41,12 @@ export function validateTimeoutMillis(timeoutMillis: number) {

export function wrapStaticHeadersInFunction(
headers: Record<string, string> | undefined
): (() => Record<string, string>) | undefined {
): HeadersFactory | undefined {
if (headers == null) {
return undefined;
}

return () => headers;
return async () => headers;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function createOtlpSendBeaconExportDelegate<Internal, Response>(
createRetryingTransport({
transport: createSendBeaconTransport({
url: options.url,
blobType: options.headers()['Content-Type'],
headers: options.headers,
}),
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>;
headers: HeadersFactory;
}

class FetchTransport implements IExporterTransport {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ class HttpExporterTransport implements IExporterTransport {

async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
const { agent, request } = await this._loadUtils();
const headers = await this._parameters.headers();

return new Promise<ExportResponse>(resolve => {
sendWithHttp(
request,
this._parameters,
this._parameters.url,
headers,
this._parameters.compression,
this._parameters.userAgent,
agent,
data,
result => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
* limitations under the License.
*/

import { HeadersFactory } from '../configuration/otlp-http-configuration';

export interface HttpRequestParameters {
url: string;
headers: () => Record<string, string>;
headers: HeadersFactory;
compression: 'gzip' | 'none';
userAgent?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<string, string>,
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;
}
Expand Down Expand Up @@ -107,7 +108,7 @@ export function sendWithHttp(
});
});

compressAndSend(req, params.compression, data, (error: Error) => {
compressAndSend(req, compression, data, (error: Error) => {
onDone({
status: 'failure',
error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExportResponse> {
async send(data: Uint8Array): Promise<ExportResponse> {
const blobType = (await this._params.headers())['Content-Type'];
return new Promise<ExportResponse>(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,26 @@ 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
* @see {@link createFetchTransport}
*/
export interface XhrRequestParameters {
url: string;
headers: () => Record<string, string>;
headers: HeadersFactory;
}

class XhrTransport implements IExporterTransport {
constructor(private _parameters: XhrRequestParameters) {}

send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
return new Promise<ExportResponse>(resolve => {
async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
const headers = await this._parameters.headers();
const response = await new Promise<ExportResponse>(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);
});
Expand Down Expand Up @@ -89,6 +90,8 @@ class XhrTransport implements IExporterTransport {

xhr.send(data);
});

return response;
}

shutdown() {
Expand Down
Loading