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 <contact@vmarchaud.fr>
This commit is contained in:
Jakub Malinowski 2021-04-10 20:52:20 +02:00 committed by GitHub
parent 031b0f4286
commit 9fc1b109e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 234 additions and 11 deletions

View File

@ -41,7 +41,7 @@ export class CompositePropagator implements TextMapPropagator {
this._propagators
// older propagators may not have fields function, null check to be sure
.map(p => (typeof p.fields === 'function' ? p.fields() : []))
.reduce((x, y) => x.concat(y))
.reduce((x, y) => x.concat(y), [])
)
);
}

View File

@ -43,7 +43,10 @@ function isEnvVarANumber(key: unknown): key is keyof ENVIRONMENT_NUMBERS {
);
}
const ENVIRONMENT_LISTS_KEYS = ['OTEL_NO_PATCH_MODULES'] as const;
const ENVIRONMENT_LISTS_KEYS = [
'OTEL_NO_PATCH_MODULES',
'OTEL_PROPAGATORS',
] as const;
type ENVIRONMENT_LISTS = {
[K in typeof ENVIRONMENT_LISTS_KEYS[number]]?: string[];
@ -83,21 +86,22 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
HOSTNAME: '',
KUBERNETES_SERVICE_HOST: '',
NAMESPACE: '',
OTEL_BSP_EXPORT_TIMEOUT: 30000,
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 512,
OTEL_BSP_MAX_QUEUE_SIZE: 2048,
OTEL_BSP_SCHEDULE_DELAY: 5000,
OTEL_EXPORTER_JAEGER_AGENT_HOST: '',
OTEL_EXPORTER_JAEGER_ENDPOINT: '',
OTEL_EXPORTER_JAEGER_PASSWORD: '',
OTEL_EXPORTER_JAEGER_USER: '',
OTEL_LOG_LEVEL: DiagLogLevel.INFO,
OTEL_NO_PATCH_MODULES: [],
OTEL_PROPAGATORS: ['tracecontext', 'baggage'],
OTEL_RESOURCE_ATTRIBUTES: '',
OTEL_SAMPLING_PROBABILITY: 1,
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 1000,
OTEL_SPAN_EVENT_COUNT_LIMIT: 1000,
OTEL_SPAN_LINK_COUNT_LIMIT: 1000,
OTEL_BSP_EXPORT_TIMEOUT: 30000,
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 512,
OTEL_BSP_MAX_QUEUE_SIZE: 2048,
OTEL_BSP_SCHEDULE_DELAY: 5000,
};
/**

View File

@ -66,6 +66,8 @@
"@opentelemetry/context-async-hooks": "^0.18.2",
"@opentelemetry/core": "^0.18.2",
"@opentelemetry/tracing": "^0.18.2",
"@opentelemetry/propagator-b3": "^0.18.0",
"@opentelemetry/propagator-jaeger": "^0.18.0",
"semver": "^7.1.3"
}
}

View File

@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TextMapPropagator } from '@opentelemetry/api';
import {
AsyncHooksContextManager,
AsyncLocalStorageContextManager,
} from '@opentelemetry/context-async-hooks';
import { B3Propagator, B3InjectEncoding } from '@opentelemetry/propagator-b3';
import {
BasicTracerProvider,
PROPAGATOR_FACTORY,
SDKRegistrationConfig,
} from '@opentelemetry/tracing';
import * as semver from 'semver';
import { NodeTracerConfig } from './config';
import { JaegerHttpTracePropagator } from '@opentelemetry/propagator-jaeger';
/**
* Register this TracerProvider for use with the OpenTelemetry API.
@ -33,6 +36,22 @@ import { NodeTracerConfig } from './config';
* @param config Configuration object for SDK registration
*/
export class NodeTracerProvider extends BasicTracerProvider {
protected static readonly _registeredPropagators = new Map<
string,
PROPAGATOR_FACTORY
>([
[
'b3',
() =>
new B3Propagator({ injectEncoding: B3InjectEncoding.SINGLE_HEADER }),
],
[
'b3multi',
() => new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER }),
],
['jaeger', () => new JaegerHttpTracePropagator()],
]);
constructor(config: NodeTracerConfig = {}) {
super(config);
}
@ -48,4 +67,11 @@ export class NodeTracerProvider extends BasicTracerProvider {
super.register(config);
}
protected _getPropagator(name: string): TextMapPropagator | undefined {
return (
super._getPropagator(name) ||
NodeTracerProvider._registeredPropagators.get(name)?.()
);
}
}

View File

@ -20,6 +20,7 @@ import {
setSpan,
setSpanContext,
getSpan,
propagation,
} from '@opentelemetry/api';
import { AlwaysOnSampler, AlwaysOffSampler } from '@opentelemetry/core';
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
@ -211,4 +212,37 @@ describe('NodeTracerProvider', () => {
return patchedFn();
});
});
describe('.register()', () => {
let originalPropagators: string | number | undefined | string[];
beforeEach(() => {
originalPropagators = process.env.OTEL_PROPAGATORS;
});
afterEach(() => {
// otherwise we may assign 'undefined' (a string)
if (originalPropagators !== undefined) {
(process.env as any).OTEL_PROPAGATORS = originalPropagators;
} else {
delete (process.env as any).OTEL_PROPAGATORS;
}
});
it('should allow propagators as per the specification', () => {
(process.env as any).OTEL_PROPAGATORS = 'b3,b3multi,jaeger';
const provider = new NodeTracerProvider();
provider.register();
assert.deepStrictEqual(propagation.fields(), [
'b3',
'x-b3-traceid',
'x-b3-spanid',
'x-b3-flags',
'x-b3-sampled',
'x-b3-parentspanid',
'uber-trace-id',
]);
});
});
});

View File

@ -15,6 +15,12 @@
{
"path": "../opentelemetry-core"
},
{
"path": "../opentelemetry-propagator-b3"
},
{
"path": "../opentelemetry-propagator-jaeger"
},
{
"path": "../opentelemetry-resources"
},

View File

@ -19,11 +19,14 @@ import {
trace,
context,
propagation,
TextMapPropagator,
diag,
} from '@opentelemetry/api';
import {
CompositePropagator,
HttpBaggage,
HttpTraceContext,
HttpBaggage,
getEnv,
} from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';
import { SpanProcessor, Tracer } from '.';
@ -32,10 +35,21 @@ import { MultiSpanProcessor } from './MultiSpanProcessor';
import { NoopSpanProcessor } from './NoopSpanProcessor';
import { SDKRegistrationConfig, TracerConfig } from './types';
import merge = require('lodash.merge');
export type PROPAGATOR_FACTORY = () => TextMapPropagator;
/**
* This class represents a basic tracer provider which platform libraries can extend
*/
export class BasicTracerProvider implements TracerProvider {
protected static readonly _registeredPropagators = new Map<
string,
PROPAGATOR_FACTORY
>([
['tracecontext', () => new HttpTraceContext()],
['baggage', () => new HttpBaggage()],
]);
private readonly _config: TracerConfig;
private readonly _registeredSpanProcessors: SpanProcessor[] = [];
private readonly _tracers: Map<string, Tracer> = new Map();
@ -86,9 +100,7 @@ export class BasicTracerProvider implements TracerProvider {
register(config: SDKRegistrationConfig = {}) {
trace.setGlobalTracerProvider(this);
if (config.propagator === undefined) {
config.propagator = new CompositePropagator({
propagators: [new HttpBaggage(), new HttpTraceContext()],
});
config.propagator = this._buildPropagatorFromEnv();
}
if (config.contextManager) {
@ -103,4 +115,43 @@ export class BasicTracerProvider implements TracerProvider {
shutdown() {
return this.activeSpanProcessor.shutdown();
}
protected _getPropagator(name: string): TextMapPropagator | undefined {
return BasicTracerProvider._registeredPropagators.get(name)?.();
}
protected _buildPropagatorFromEnv(): TextMapPropagator | undefined {
// per spec, propagators from env must be deduplicated
const uniquePropagatorNames = [...new Set(getEnv().OTEL_PROPAGATORS)];
const propagators = uniquePropagatorNames.map(name => {
const propagator = this._getPropagator(name);
if (!propagator) {
diag.warn(
`Propagator "${name}" requested through environment variable is unavailable.`
);
}
return propagator;
});
const validPropagators = propagators.reduce<TextMapPropagator[]>(
(list, item) => {
if (item) {
list.push(item);
}
return list;
},
[]
);
if (validPropagators.length === 0) {
return;
} else if (uniquePropagatorNames.length === 1) {
return validPropagators[0];
} else {
return new CompositePropagator({
propagators: validPropagators,
});
}
}
}

View File

@ -22,7 +22,14 @@ import {
setSpan,
setSpanContext,
getSpan,
TextMapPropagator,
TextMapSetter,
Context,
TextMapGetter,
propagation,
diag,
} from '@opentelemetry/api';
import { CompositePropagator } from '@opentelemetry/core';
import {
AlwaysOnSampler,
AlwaysOffSampler,
@ -115,6 +122,99 @@ describe('BasicTracerProvider', () => {
});
});
describe('.register()', () => {
const envSource = (typeof window !== 'undefined'
? window
: process.env) as any;
describe('propagator', () => {
class DummyPropagator implements TextMapPropagator {
inject(
context: Context,
carrier: any,
setter: TextMapSetter<any>
): void {
throw new Error('Method not implemented.');
}
extract(
context: Context,
carrier: any,
getter: TextMapGetter<any>
): Context {
throw new Error('Method not implemented.');
}
fields(): string[] {
throw new Error('Method not implemented.');
}
}
let setGlobalPropagatorStub: sinon.SinonSpy<
[TextMapPropagator],
TextMapPropagator
>;
let originalPropagators: string | number | undefined | string[];
beforeEach(() => {
setGlobalPropagatorStub = sinon.spy(propagation, 'setGlobalPropagator');
originalPropagators = envSource.OTEL_PROPAGATORS;
});
afterEach(() => {
setGlobalPropagatorStub.restore();
// otherwise we may assign 'undefined' (a string)
if (originalPropagators !== undefined) {
envSource.OTEL_PROPAGATORS = originalPropagators;
} else {
delete envSource.OTEL_PROPAGATORS;
}
});
it('should be set to a given value if it it provided', () => {
const provider = new BasicTracerProvider();
provider.register({
propagator: new DummyPropagator(),
});
assert.ok(
setGlobalPropagatorStub.calledOnceWithExactly(
sinon.match.instanceOf(DummyPropagator)
)
);
});
it('should be composite if 2 or more propagators provided in an environment variable', () => {
const provider = new BasicTracerProvider();
provider.register();
assert.ok(
setGlobalPropagatorStub.calledOnceWithExactly(
sinon.match.instanceOf(CompositePropagator)
)
);
assert.deepStrictEqual(setGlobalPropagatorStub.args[0][0].fields(), [
'traceparent',
'tracestate',
'baggage',
]);
});
it('warns if there is no propagator registered with a given name', () => {
const warnStub = sinon.spy(diag, 'warn');
envSource.OTEL_PROPAGATORS = 'missing-propagator';
const provider = new BasicTracerProvider({});
provider.register();
assert.ok(
warnStub.calledOnceWithExactly(
'Propagator "missing-propagator" requested through environment variable is unavailable.'
)
);
warnStub.restore();
});
});
});
describe('.startSpan()', () => {
it('should start a span with name only', () => {
const tracer = new BasicTracerProvider().getTracer('default');