Skip to content

Commit 9fc1b10

Browse files
feat: enable propagators using ENV vars (#1929)
* feat: enable propagators using ENV vars * fix: browser tests * fix: remove rebase mistakes * fix: feedback from PR Co-authored-by: Valentin Marchaud <[email protected]>
1 parent 031b0f4 commit 9fc1b10

File tree

8 files changed

+234
-11
lines changed

8 files changed

+234
-11
lines changed

packages/opentelemetry-core/src/context/propagation/composite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class CompositePropagator implements TextMapPropagator {
4141
this._propagators
4242
// older propagators may not have fields function, null check to be sure
4343
.map(p => (typeof p.fields === 'function' ? p.fields() : []))
44-
.reduce((x, y) => x.concat(y))
44+
.reduce((x, y) => x.concat(y), [])
4545
)
4646
);
4747
}

packages/opentelemetry-core/src/utils/environment.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ function isEnvVarANumber(key: unknown): key is keyof ENVIRONMENT_NUMBERS {
4343
);
4444
}
4545

46-
const ENVIRONMENT_LISTS_KEYS = ['OTEL_NO_PATCH_MODULES'] as const;
46+
const ENVIRONMENT_LISTS_KEYS = [
47+
'OTEL_NO_PATCH_MODULES',
48+
'OTEL_PROPAGATORS',
49+
] as const;
4750

4851
type ENVIRONMENT_LISTS = {
4952
[K in typeof ENVIRONMENT_LISTS_KEYS[number]]?: string[];
@@ -83,21 +86,22 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
8386
HOSTNAME: '',
8487
KUBERNETES_SERVICE_HOST: '',
8588
NAMESPACE: '',
89+
OTEL_BSP_EXPORT_TIMEOUT: 30000,
90+
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 512,
91+
OTEL_BSP_MAX_QUEUE_SIZE: 2048,
92+
OTEL_BSP_SCHEDULE_DELAY: 5000,
8693
OTEL_EXPORTER_JAEGER_AGENT_HOST: '',
8794
OTEL_EXPORTER_JAEGER_ENDPOINT: '',
8895
OTEL_EXPORTER_JAEGER_PASSWORD: '',
8996
OTEL_EXPORTER_JAEGER_USER: '',
9097
OTEL_LOG_LEVEL: DiagLogLevel.INFO,
9198
OTEL_NO_PATCH_MODULES: [],
99+
OTEL_PROPAGATORS: ['tracecontext', 'baggage'],
92100
OTEL_RESOURCE_ATTRIBUTES: '',
93101
OTEL_SAMPLING_PROBABILITY: 1,
94102
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 1000,
95103
OTEL_SPAN_EVENT_COUNT_LIMIT: 1000,
96104
OTEL_SPAN_LINK_COUNT_LIMIT: 1000,
97-
OTEL_BSP_EXPORT_TIMEOUT: 30000,
98-
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 512,
99-
OTEL_BSP_MAX_QUEUE_SIZE: 2048,
100-
OTEL_BSP_SCHEDULE_DELAY: 5000,
101105
};
102106

103107
/**

packages/opentelemetry-node/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
"@opentelemetry/context-async-hooks": "^0.18.2",
6767
"@opentelemetry/core": "^0.18.2",
6868
"@opentelemetry/tracing": "^0.18.2",
69+
"@opentelemetry/propagator-b3": "^0.18.0",
70+
"@opentelemetry/propagator-jaeger": "^0.18.0",
6971
"semver": "^7.1.3"
7072
}
7173
}

packages/opentelemetry-node/src/NodeTracerProvider.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
import { TextMapPropagator } from '@opentelemetry/api';
1717
import {
1818
AsyncHooksContextManager,
1919
AsyncLocalStorageContextManager,
2020
} from '@opentelemetry/context-async-hooks';
21+
import { B3Propagator, B3InjectEncoding } from '@opentelemetry/propagator-b3';
2122
import {
2223
BasicTracerProvider,
24+
PROPAGATOR_FACTORY,
2325
SDKRegistrationConfig,
2426
} from '@opentelemetry/tracing';
2527
import * as semver from 'semver';
2628
import { NodeTracerConfig } from './config';
29+
import { JaegerHttpTracePropagator } from '@opentelemetry/propagator-jaeger';
2730

2831
/**
2932
* Register this TracerProvider for use with the OpenTelemetry API.
@@ -33,6 +36,22 @@ import { NodeTracerConfig } from './config';
3336
* @param config Configuration object for SDK registration
3437
*/
3538
export class NodeTracerProvider extends BasicTracerProvider {
39+
protected static readonly _registeredPropagators = new Map<
40+
string,
41+
PROPAGATOR_FACTORY
42+
>([
43+
[
44+
'b3',
45+
() =>
46+
new B3Propagator({ injectEncoding: B3InjectEncoding.SINGLE_HEADER }),
47+
],
48+
[
49+
'b3multi',
50+
() => new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER }),
51+
],
52+
['jaeger', () => new JaegerHttpTracePropagator()],
53+
]);
54+
3655
constructor(config: NodeTracerConfig = {}) {
3756
super(config);
3857
}
@@ -48,4 +67,11 @@ export class NodeTracerProvider extends BasicTracerProvider {
4867

4968
super.register(config);
5069
}
70+
71+
protected _getPropagator(name: string): TextMapPropagator | undefined {
72+
return (
73+
super._getPropagator(name) ||
74+
NodeTracerProvider._registeredPropagators.get(name)?.()
75+
);
76+
}
5177
}

packages/opentelemetry-node/test/NodeTracerProvider.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
setSpan,
2121
setSpanContext,
2222
getSpan,
23+
propagation,
2324
} from '@opentelemetry/api';
2425
import { AlwaysOnSampler, AlwaysOffSampler } from '@opentelemetry/core';
2526
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
@@ -211,4 +212,37 @@ describe('NodeTracerProvider', () => {
211212
return patchedFn();
212213
});
213214
});
215+
216+
describe('.register()', () => {
217+
let originalPropagators: string | number | undefined | string[];
218+
beforeEach(() => {
219+
originalPropagators = process.env.OTEL_PROPAGATORS;
220+
});
221+
222+
afterEach(() => {
223+
// otherwise we may assign 'undefined' (a string)
224+
if (originalPropagators !== undefined) {
225+
(process.env as any).OTEL_PROPAGATORS = originalPropagators;
226+
} else {
227+
delete (process.env as any).OTEL_PROPAGATORS;
228+
}
229+
});
230+
231+
it('should allow propagators as per the specification', () => {
232+
(process.env as any).OTEL_PROPAGATORS = 'b3,b3multi,jaeger';
233+
234+
const provider = new NodeTracerProvider();
235+
provider.register();
236+
237+
assert.deepStrictEqual(propagation.fields(), [
238+
'b3',
239+
'x-b3-traceid',
240+
'x-b3-spanid',
241+
'x-b3-flags',
242+
'x-b3-sampled',
243+
'x-b3-parentspanid',
244+
'uber-trace-id',
245+
]);
246+
});
247+
});
214248
});

packages/opentelemetry-node/tsconfig.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
{
1616
"path": "../opentelemetry-core"
1717
},
18+
{
19+
"path": "../opentelemetry-propagator-b3"
20+
},
21+
{
22+
"path": "../opentelemetry-propagator-jaeger"
23+
},
1824
{
1925
"path": "../opentelemetry-resources"
2026
},

packages/opentelemetry-tracing/src/BasicTracerProvider.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ import {
1919
trace,
2020
context,
2121
propagation,
22+
TextMapPropagator,
23+
diag,
2224
} from '@opentelemetry/api';
2325
import {
2426
CompositePropagator,
25-
HttpBaggage,
2627
HttpTraceContext,
28+
HttpBaggage,
29+
getEnv,
2730
} from '@opentelemetry/core';
2831
import { Resource } from '@opentelemetry/resources';
2932
import { SpanProcessor, Tracer } from '.';
@@ -32,10 +35,21 @@ import { MultiSpanProcessor } from './MultiSpanProcessor';
3235
import { NoopSpanProcessor } from './NoopSpanProcessor';
3336
import { SDKRegistrationConfig, TracerConfig } from './types';
3437
import merge = require('lodash.merge');
38+
39+
export type PROPAGATOR_FACTORY = () => TextMapPropagator;
40+
3541
/**
3642
* This class represents a basic tracer provider which platform libraries can extend
3743
*/
3844
export class BasicTracerProvider implements TracerProvider {
45+
protected static readonly _registeredPropagators = new Map<
46+
string,
47+
PROPAGATOR_FACTORY
48+
>([
49+
['tracecontext', () => new HttpTraceContext()],
50+
['baggage', () => new HttpBaggage()],
51+
]);
52+
3953
private readonly _config: TracerConfig;
4054
private readonly _registeredSpanProcessors: SpanProcessor[] = [];
4155
private readonly _tracers: Map<string, Tracer> = new Map();
@@ -86,9 +100,7 @@ export class BasicTracerProvider implements TracerProvider {
86100
register(config: SDKRegistrationConfig = {}) {
87101
trace.setGlobalTracerProvider(this);
88102
if (config.propagator === undefined) {
89-
config.propagator = new CompositePropagator({
90-
propagators: [new HttpBaggage(), new HttpTraceContext()],
91-
});
103+
config.propagator = this._buildPropagatorFromEnv();
92104
}
93105

94106
if (config.contextManager) {
@@ -103,4 +115,43 @@ export class BasicTracerProvider implements TracerProvider {
103115
shutdown() {
104116
return this.activeSpanProcessor.shutdown();
105117
}
118+
119+
protected _getPropagator(name: string): TextMapPropagator | undefined {
120+
return BasicTracerProvider._registeredPropagators.get(name)?.();
121+
}
122+
123+
protected _buildPropagatorFromEnv(): TextMapPropagator | undefined {
124+
// per spec, propagators from env must be deduplicated
125+
const uniquePropagatorNames = [...new Set(getEnv().OTEL_PROPAGATORS)];
126+
127+
const propagators = uniquePropagatorNames.map(name => {
128+
const propagator = this._getPropagator(name);
129+
if (!propagator) {
130+
diag.warn(
131+
`Propagator "${name}" requested through environment variable is unavailable.`
132+
);
133+
}
134+
135+
return propagator;
136+
});
137+
const validPropagators = propagators.reduce<TextMapPropagator[]>(
138+
(list, item) => {
139+
if (item) {
140+
list.push(item);
141+
}
142+
return list;
143+
},
144+
[]
145+
);
146+
147+
if (validPropagators.length === 0) {
148+
return;
149+
} else if (uniquePropagatorNames.length === 1) {
150+
return validPropagators[0];
151+
} else {
152+
return new CompositePropagator({
153+
propagators: validPropagators,
154+
});
155+
}
156+
}
106157
}

packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ import {
2222
setSpan,
2323
setSpanContext,
2424
getSpan,
25+
TextMapPropagator,
26+
TextMapSetter,
27+
Context,
28+
TextMapGetter,
29+
propagation,
30+
diag,
2531
} from '@opentelemetry/api';
32+
import { CompositePropagator } from '@opentelemetry/core';
2633
import {
2734
AlwaysOnSampler,
2835
AlwaysOffSampler,
@@ -115,6 +122,99 @@ describe('BasicTracerProvider', () => {
115122
});
116123
});
117124

125+
describe('.register()', () => {
126+
const envSource = (typeof window !== 'undefined'
127+
? window
128+
: process.env) as any;
129+
130+
describe('propagator', () => {
131+
class DummyPropagator implements TextMapPropagator {
132+
inject(
133+
context: Context,
134+
carrier: any,
135+
setter: TextMapSetter<any>
136+
): void {
137+
throw new Error('Method not implemented.');
138+
}
139+
extract(
140+
context: Context,
141+
carrier: any,
142+
getter: TextMapGetter<any>
143+
): Context {
144+
throw new Error('Method not implemented.');
145+
}
146+
fields(): string[] {
147+
throw new Error('Method not implemented.');
148+
}
149+
}
150+
151+
let setGlobalPropagatorStub: sinon.SinonSpy<
152+
[TextMapPropagator],
153+
TextMapPropagator
154+
>;
155+
let originalPropagators: string | number | undefined | string[];
156+
beforeEach(() => {
157+
setGlobalPropagatorStub = sinon.spy(propagation, 'setGlobalPropagator');
158+
originalPropagators = envSource.OTEL_PROPAGATORS;
159+
});
160+
161+
afterEach(() => {
162+
setGlobalPropagatorStub.restore();
163+
164+
// otherwise we may assign 'undefined' (a string)
165+
if (originalPropagators !== undefined) {
166+
envSource.OTEL_PROPAGATORS = originalPropagators;
167+
} else {
168+
delete envSource.OTEL_PROPAGATORS;
169+
}
170+
});
171+
172+
it('should be set to a given value if it it provided', () => {
173+
const provider = new BasicTracerProvider();
174+
provider.register({
175+
propagator: new DummyPropagator(),
176+
});
177+
assert.ok(
178+
setGlobalPropagatorStub.calledOnceWithExactly(
179+
sinon.match.instanceOf(DummyPropagator)
180+
)
181+
);
182+
});
183+
184+
it('should be composite if 2 or more propagators provided in an environment variable', () => {
185+
const provider = new BasicTracerProvider();
186+
provider.register();
187+
188+
assert.ok(
189+
setGlobalPropagatorStub.calledOnceWithExactly(
190+
sinon.match.instanceOf(CompositePropagator)
191+
)
192+
);
193+
assert.deepStrictEqual(setGlobalPropagatorStub.args[0][0].fields(), [
194+
'traceparent',
195+
'tracestate',
196+
'baggage',
197+
]);
198+
});
199+
200+
it('warns if there is no propagator registered with a given name', () => {
201+
const warnStub = sinon.spy(diag, 'warn');
202+
203+
envSource.OTEL_PROPAGATORS = 'missing-propagator';
204+
const provider = new BasicTracerProvider({});
205+
provider.register();
206+
207+
assert.ok(
208+
warnStub.calledOnceWithExactly(
209+
'Propagator "missing-propagator" requested through environment variable is unavailable.'
210+
)
211+
);
212+
213+
warnStub.restore();
214+
});
215+
});
216+
});
217+
118218
describe('.startSpan()', () => {
119219
it('should start a span with name only', () => {
120220
const tracer = new BasicTracerProvider().getTracer('default');

0 commit comments

Comments
 (0)