Skip to content

Commit 64df121

Browse files
committed
feat(sdk-trace-base)!: drop ability to instantiate propagators beyond defaults
1 parent c00f36e commit 64df121

File tree

11 files changed

+207
-334
lines changed

11 files changed

+207
-334
lines changed

experimental/packages/opentelemetry-sdk-node/src/sdk.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
getResourceDetectorsFromEnv,
7575
getSpanProcessorsFromEnv,
7676
filterBlanksAndNulls,
77+
getPropgagatorFromEnv,
7778
} from './utils';
7879

7980
/** This class represents everything needed to register a fully configured OpenTelemetry Node.js SDK */
@@ -376,7 +377,9 @@ export class NodeSDK {
376377
this._tracerProviderConfig?.contextManager ??
377378
// _tracerProviderConfig may be undefined if trace-specific settings are not provided - fall back to raw config
378379
this._configuration?.contextManager,
379-
propagator: this._tracerProviderConfig?.textMapPropagator,
380+
propagator:
381+
this._tracerProviderConfig?.textMapPropagator ??
382+
getPropgagatorFromEnv(),
380383
});
381384
}
382385

experimental/packages/opentelemetry-sdk-node/src/utils.ts

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { diag } from '@opentelemetry/api';
18-
import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core';
17+
import { diag, TextMapPropagator } from '@opentelemetry/api';
18+
import {
19+
CompositePropagator,
20+
getEnv,
21+
getEnvWithoutDefaults,
22+
W3CTraceContextPropagator,
23+
} from '@opentelemetry/core';
1924
import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
2025
import { OTLPTraceExporter as OTLPHttpTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
2126
import { OTLPTraceExporter as OTLPGrpcTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
@@ -35,6 +40,8 @@ import {
3540
SpanExporter,
3641
SpanProcessor,
3742
} from '@opentelemetry/sdk-trace-base';
43+
import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';
44+
import { JaegerPropagator } from '@opentelemetry/propagator-jaeger';
3845

3946
const RESOURCE_DETECTOR_ENVIRONMENT = 'env';
4047
const RESOURCE_DETECTOR_HOST = 'host';
@@ -180,3 +187,66 @@ export function getSpanProcessorsFromEnv(): SpanProcessor[] {
180187

181188
return processors;
182189
}
190+
191+
/**
192+
* Get a propagator as defined by environment variables
193+
*/
194+
export function getPropgagatorFromEnv(): TextMapPropagator | null | undefined {
195+
// Empty and undefined MUST be treated equal.
196+
if (
197+
process.env.OTEL_PROPAGATORS === undefined ||
198+
process.env.OTEL_PROPAGATORS?.trim() === ''
199+
) {
200+
// return undefined to fall back to default
201+
return undefined;
202+
}
203+
204+
// Implementation note: this only contains specification required propagators that are actually hosted in this repo.
205+
// Any other propagators (like aws, aws-lambda, should go into `@opentelemetry/auto-configuration-propagators` instead).
206+
const propagatorsFactory = new Map<string, () => TextMapPropagator>([
207+
['tracecontext', () => new W3CTraceContextPropagator()],
208+
['baggage', () => new W3CTraceContextPropagator()],
209+
['b3', () => new B3Propagator()],
210+
[
211+
'b3multi',
212+
() => new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER }),
213+
],
214+
['jaeger', () => new JaegerPropagator()],
215+
]);
216+
217+
// Values MUST be deduplicated in order to register a Propagator only once.
218+
const uniquePropagatorNames = Array.from(new Set(getEnv().OTEL_PROPAGATORS));
219+
220+
const propagators = uniquePropagatorNames.map(name => {
221+
const propagator = propagatorsFactory.get(name)?.();
222+
if (!propagator) {
223+
diag.warn(
224+
`Propagator "${name}" requested through environment variable is unavailable.`
225+
);
226+
return undefined;
227+
}
228+
229+
return propagator;
230+
});
231+
232+
const validPropagators = propagators.reduce<TextMapPropagator[]>(
233+
(list, item) => {
234+
if (item) {
235+
list.push(item);
236+
}
237+
return list;
238+
},
239+
[]
240+
);
241+
242+
if (validPropagators.length === 0) {
243+
// null to signal that the default should **not** be used in its place.
244+
return null;
245+
} else if (uniquePropagatorNames.length === 1) {
246+
return validPropagators[0];
247+
} else {
248+
return new CompositePropagator({
249+
propagators: validPropagators,
250+
});
251+
}
252+
}

experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,20 @@ describe('Node SDK', () => {
414414
assert.equal(actualContextManager, expectedContextManager);
415415
await sdk.shutdown();
416416
});
417+
418+
it('should register a propagators as defined in OTEL_PROPAGATORS if trace SDK is configured', async () => {
419+
process.env.OTEL_PROPAGATORS = 'b3';
420+
const sdk = new NodeSDK({
421+
traceExporter: new ConsoleSpanExporter(),
422+
autoDetectResources: false,
423+
});
424+
425+
sdk.start();
426+
427+
assert.deepStrictEqual(propagation.fields(), ['b3']);
428+
429+
await sdk.shutdown();
430+
});
417431
});
418432

419433
async function waitForNumberOfMetrics(
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { getPropgagatorFromEnv } from '../src/utils';
18+
import * as assert from 'assert';
19+
import * as sinon from 'sinon';
20+
import { diag } from '@opentelemetry/api';
21+
22+
describe('getPropagatorFromEnv', function () {
23+
afterEach(() => {
24+
delete process.env.OTEL_PROPAGATORS;
25+
sinon.restore();
26+
});
27+
28+
describe('should default to undefined', function () {
29+
it('when not defined', function () {
30+
delete process.env.OTEL_PROPAGATORS;
31+
32+
const propagator = getPropgagatorFromEnv();
33+
34+
assert.deepStrictEqual(propagator, undefined);
35+
});
36+
37+
it('on empty string', function () {
38+
(process.env as any).OTEL_PROPAGATORS = '';
39+
40+
const propagator = getPropgagatorFromEnv();
41+
42+
assert.deepStrictEqual(propagator, undefined);
43+
});
44+
45+
it('on space-only string', function () {
46+
(process.env as any).OTEL_PROPAGATORS = ' ';
47+
48+
const propagator = getPropgagatorFromEnv();
49+
50+
assert.deepStrictEqual(propagator, undefined);
51+
});
52+
});
53+
54+
it('should return the selected propagator when one is in the list', () => {
55+
process.env.OTEL_PROPAGATORS = 'tracecontext';
56+
assert.deepStrictEqual(getPropgagatorFromEnv()?.fields(), [
57+
'traceparent',
58+
'tracestate',
59+
]);
60+
});
61+
62+
it('should return the selected propagators when multiple are in the list', () => {
63+
process.env.OTEL_PROPAGATORS = 'b3,jaeger';
64+
assert.deepStrictEqual(getPropgagatorFromEnv()?.fields(), [
65+
'b3',
66+
'uber-trace-id',
67+
]);
68+
});
69+
70+
it('should return the selected propagators when multiple are in the list', () => {
71+
process.env.OTEL_PROPAGATORS = 'b3,jaeger';
72+
assert.deepStrictEqual(getPropgagatorFromEnv()?.fields(), [
73+
'b3',
74+
'uber-trace-id',
75+
]);
76+
});
77+
78+
it('should return null and warn if propagators are unknown', () => {
79+
const warnStub = sinon.stub(diag, 'warn');
80+
81+
process.env.OTEL_PROPAGATORS = 'my, unknown, propagators';
82+
assert.deepStrictEqual(getPropgagatorFromEnv(), null);
83+
sinon.assert.calledWithExactly(
84+
warnStub,
85+
'Propagator "my" requested through environment variable is unavailable.'
86+
);
87+
sinon.assert.calledWithExactly(
88+
warnStub,
89+
'Propagator "unknown" requested through environment variable is unavailable.'
90+
);
91+
sinon.assert.calledWithExactly(
92+
warnStub,
93+
'Propagator "propagators" requested through environment variable is unavailable.'
94+
);
95+
sinon.assert.calledThrice(warnStub);
96+
});
97+
98+
it('should return null if only "none" is selected', () => {
99+
process.env.OTEL_PROPAGATORS = 'none';
100+
101+
assert.deepStrictEqual(getPropgagatorFromEnv(), null);
102+
});
103+
});

package-lock.json

Lines changed: 0 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts

Lines changed: 12 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import {
1818
context,
19-
diag,
2019
propagation,
2120
TextMapPropagator,
2221
trace,
@@ -26,7 +25,6 @@ import {
2625
CompositePropagator,
2726
W3CBaggagePropagator,
2827
W3CTraceContextPropagator,
29-
getEnv,
3028
merge,
3129
} from '@opentelemetry/core';
3230
import { IResource, Resource } from '@opentelemetry/resources';
@@ -48,18 +46,14 @@ export enum ForceFlushState {
4846
'unresolved',
4947
}
5048

49+
function getDefaultPropagators(): TextMapPropagator[] {
50+
return [new W3CTraceContextPropagator(), new W3CBaggagePropagator()];
51+
}
52+
5153
/**
5254
* This class represents a basic tracer provider which platform libraries can extend
5355
*/
5456
export class BasicTracerProvider implements TracerProvider {
55-
protected static readonly _registeredPropagators = new Map<
56-
string,
57-
PROPAGATOR_FACTORY
58-
>([
59-
['tracecontext', () => new W3CTraceContextPropagator()],
60-
['baggage', () => new W3CBaggagePropagator()],
61-
]);
62-
6357
private readonly _config: TracerConfig;
6458
private readonly _tracers: Map<string, Tracer> = new Map();
6559
private readonly _resource: IResource;
@@ -117,16 +111,19 @@ export class BasicTracerProvider implements TracerProvider {
117111
*/
118112
register(config: SDKRegistrationConfig = {}): void {
119113
trace.setGlobalTracerProvider(this);
120-
if (config.propagator === undefined) {
121-
config.propagator = this._buildPropagatorFromEnv();
122-
}
123114

124115
if (config.contextManager) {
125116
context.setGlobalContextManager(config.contextManager);
126117
}
127118

128-
if (config.propagator) {
129-
propagation.setGlobalPropagator(config.propagator);
119+
// undefined means "unset", null means don't register propagator
120+
if (config.propagator !== null) {
121+
propagation.setGlobalPropagator(
122+
config.propagator ??
123+
new CompositePropagator({
124+
propagators: getDefaultPropagators(),
125+
})
126+
);
130127
}
131128
}
132129

@@ -182,54 +179,4 @@ export class BasicTracerProvider implements TracerProvider {
182179
shutdown(): Promise<void> {
183180
return this._activeSpanProcessor.shutdown();
184181
}
185-
186-
/**
187-
* TS cannot yet infer the type of this.constructor:
188-
* https://github.com/Microsoft/TypeScript/issues/3841#issuecomment-337560146
189-
* There is no need to override either of the getters in your child class.
190-
* The type of the registered component maps should be the same across all
191-
* classes in the inheritance tree.
192-
*/
193-
protected _getPropagator(name: string): TextMapPropagator | undefined {
194-
return (
195-
this.constructor as typeof BasicTracerProvider
196-
)._registeredPropagators.get(name)?.();
197-
}
198-
199-
protected _buildPropagatorFromEnv(): TextMapPropagator | undefined {
200-
// per spec, propagators from env must be deduplicated
201-
const uniquePropagatorNames = Array.from(
202-
new Set(getEnv().OTEL_PROPAGATORS)
203-
);
204-
205-
const propagators = uniquePropagatorNames.map(name => {
206-
const propagator = this._getPropagator(name);
207-
if (!propagator) {
208-
diag.warn(
209-
`Propagator "${name}" requested through environment variable is unavailable.`
210-
);
211-
}
212-
213-
return propagator;
214-
});
215-
const validPropagators = propagators.reduce<TextMapPropagator[]>(
216-
(list, item) => {
217-
if (item) {
218-
list.push(item);
219-
}
220-
return list;
221-
},
222-
[]
223-
);
224-
225-
if (validPropagators.length === 0) {
226-
return;
227-
} else if (uniquePropagatorNames.length === 1) {
228-
return validPropagators[0];
229-
} else {
230-
return new CompositePropagator({
231-
propagators: validPropagators,
232-
});
233-
}
234-
}
235182
}

0 commit comments

Comments
 (0)