Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable propagators using ENV vars #1929

Merged
merged 6 commits into from
Apr 10, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -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), [])
)
);
}
Expand Down
14 changes: 9 additions & 5 deletions packages/opentelemetry-core/src/utils/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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,
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
28 changes: 27 additions & 1 deletion packages/opentelemetry-node/src/NodeTracerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@
* limitations under the License.
*/

import { diag } from '@opentelemetry/api';
import { diag, 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.
Expand All @@ -34,6 +37,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);
if (config.plugins) {
Expand All @@ -55,4 +74,11 @@ export class NodeTracerProvider extends BasicTracerProvider {

super.register(config);
}

protected _getPropagator(name: string): TextMapPropagator | undefined {
return (
super._getPropagator(name) ||
NodeTracerProvider._registeredPropagators.get(name)?.()
);
}
}
6 changes: 6 additions & 0 deletions packages/opentelemetry-node/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
{
"path": "../opentelemetry-core"
},
{
"path": "../opentelemetry-propagator-b3"
},
{
"path": "../opentelemetry-propagator-jaeger"
},
{
"path": "../opentelemetry-resources"
},
Expand Down
62 changes: 58 additions & 4 deletions packages/opentelemetry-tracing/src/BasicTracerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '.';
Expand All @@ -32,10 +35,25 @@ 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()],
]);

static registerPropagator(name: string, factory: PROPAGATOR_FACTORY): void {
vmarchaud marked this conversation as resolved.
Show resolved Hide resolved
this._registeredPropagators.set(name, factory);
}

private readonly _config: TracerConfig;
private readonly _registeredSpanProcessors: SpanProcessor[] = [];
private readonly _tracers: Map<string, Tracer> = new Map();
Expand Down Expand Up @@ -86,9 +104,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) {
Expand All @@ -103,4 +119,42 @@ 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 {
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,
});
}
}
}
99 changes: 99 additions & 0 deletions packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ import {
setSpan,
setSpanContext,
getSpan,
TextMapPropagator,
TextMapSetter,
Context,
TextMapGetter,
propagation,
diag,
} from '@opentelemetry/api';
import {
AlwaysOnSampler,
AlwaysOffSampler,
TraceState,
} from '@opentelemetry/core';
import { RAW_ENVIRONMENT } from '@opentelemetry/core/src/utils/environment';
import { Resource } from '@opentelemetry/resources';
import * as assert from 'assert';
import * as sinon from 'sinon';
Expand Down Expand Up @@ -115,6 +122,98 @@ describe('BasicTracerProvider', () => {
});
});

describe('.register()', () => {
const envSource = (typeof window !== 'undefined'
? window
: process.env) as RAW_ENVIRONMENT;

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: RAW_ENVIRONMENT['OTEL_PROPAGATORS'];
beforeEach(() => {
setGlobalPropagatorStub = sinon.spy(propagation, 'setGlobalPropagator');
originalPropagators = envSource.OTEL_PROPAGATORS;
});

afterEach(() => {
setGlobalPropagatorStub.restore();
envSource.OTEL_PROPAGATORS = originalPropagators;
});

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 set to a given value if it it provided in an environment variable', () => {
envSource.OTEL_PROPAGATORS = 'dummy';
BasicTracerProvider.registerPropagator(
'dummy',
() => new DummyPropagator()
);

const provider = new BasicTracerProvider();
provider.register();

assert.ok(
setGlobalPropagatorStub.calledOnceWithExactly(
sinon.match.instanceOf(DummyPropagator)
)
);
});

it(
'should be composite if more than 2 propagators provided in an environment variable'
);

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');
Expand Down