Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* refactor(configuration): throw warning and not error for invalid files [#6124](https://github.com/open-telemetry/opentelemetry-js/pull/6124) @maryliag
* refactor(configuration): dont have a default value for node resource detectors [#6131](https://github.com/open-telemetry/opentelemetry-js/pull/6131) @maryliag
* feat(configuration): doesnt set meter,tracer,logger provider by default [#6130](https://github.com/open-telemetry/opentelemetry-js/pull/6130) @maryliag
* feat(opentelemetry-sdk-node): set instrumentation and propagators for experimental start [#6148](https://github.com/open-telemetry/opentelemetry-js/pull/6148) @maryliag

### :bug: Bug Fixes

Expand Down
19 changes: 17 additions & 2 deletions experimental/packages/opentelemetry-sdk-node/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ import {
createConfigFactory,
} from '@opentelemetry/configuration';
import { diag, DiagConsoleLogger } from '@opentelemetry/api';
import { setupDefaultContextManager } from './utils';
import {
getPropagatorFromConfigFactory,
setupDefaultContextManager,
setupPropagator,
} from './utils';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import type { SDKOptions } from './types';

/**
* @experimental Function to start the OpenTelemetry Node SDK
* @param sdkOptions
*/
export function startNodeSDK(): {
export function startNodeSDK(sdkOptions: SDKOptions): {
shutdown: () => Promise<void>;
} {
const configFactory: ConfigFactory = createConfigFactory();
Expand All @@ -38,7 +44,16 @@ export function startNodeSDK(): {
diag.setLogger(new DiagConsoleLogger(), { logLevel: config.log_level });
}

registerInstrumentations({
instrumentations: sdkOptions?.instrumentations?.flat() ?? [],
});
setupDefaultContextManager();
setupPropagator(
sdkOptions?.textMapPropagator === null
? null // null means don't set.
: (sdkOptions?.textMapPropagator ??
getPropagatorFromConfigFactory(config))
);

const shutdownFn = async () => {
const promises: Promise<unknown>[] = [];
Expand Down
5 changes: 5 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ export interface NodeSDKConfiguration {
spanLimits: SpanLimits;
idGenerator: IdGenerator;
}

export interface SDKOptions {
Comment thread
maryliag marked this conversation as resolved.
Outdated
instrumentations?: (Instrumentation | Instrumentation[])[];
textMapPropagator?: TextMapPropagator | null;
}
80 changes: 80 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';
import { JaegerPropagator } from '@opentelemetry/propagator-jaeger';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { ConfigurationModel } from '@opentelemetry/configuration';

const RESOURCE_DETECTOR_ENVIRONMENT = 'env';
const RESOURCE_DETECTOR_HOST = 'host';
Expand Down Expand Up @@ -230,6 +231,74 @@ export function getPropagatorFromEnv(): TextMapPropagator | null | undefined {
}
}

/**
* Get a propagator as defined by configuration model from configuration factory
*/
export function getPropagatorFromConfigFactory(
config: ConfigurationModel
Comment thread
maryliag marked this conversation as resolved.
Outdated
): TextMapPropagator | null | undefined {
const propagatorsValue = getKeyListFromObjectArray(
config.propagator?.composite
);
if (propagatorsValue == null) {
// return undefined to fall back to default
return undefined;
}

if (propagatorsValue.includes('none')) {
return null;
}

// Implementation note: this only contains specification required propagators that are actually hosted in this repo.
// Any other propagators (like aws, aws-lambda, should go into `@opentelemetry/auto-configuration-propagators` instead).
const propagatorsFactory = new Map<string, () => TextMapPropagator>([
['tracecontext', () => new W3CTraceContextPropagator()],
['baggage', () => new W3CBaggagePropagator()],
['b3', () => new B3Propagator()],
[
'b3multi',
() => new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER }),
],
['jaeger', () => new JaegerPropagator()],
]);

// Values MUST be deduplicated in order to register a Propagator only once.
const uniquePropagatorNames = Array.from(new Set(propagatorsValue));

const propagators = uniquePropagatorNames.map(name => {
const propagator = propagatorsFactory.get(name)?.();
if (!propagator) {
diag.warn(
`Propagator "${name}" requested through configuration is unavailable.`
);
return undefined;
}

return propagator;
});

const validPropagators = propagators.reduce<TextMapPropagator[]>(
(list, item) => {
if (item) {
list.push(item);
}
return list;
},
[]
);

if (validPropagators.length === 0) {
// null to signal that the default should **not** be used in its place.
return null;
} else if (uniquePropagatorNames.length === 1) {
return validPropagators[0];
} else {
return new CompositePropagator({
propagators: validPropagators,
});
}
}

export function setupContextManager(
contextManager: ContextManager | null | undefined
) {
Expand Down Expand Up @@ -279,3 +348,14 @@ export function setupPropagator(

propagation.setGlobalPropagator(propagator);
}

export function getKeyListFromObjectArray(
obj: object[] | undefined
): string[] | undefined {
if (!obj || obj.length === 0) {
return undefined;
}
return obj
.map(item => Object.keys(item))
.reduce((prev, curr) => prev.concat(curr), []);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('startNodeSDK', function () {
it('should return NOOP_SDK when disabled is true', () => {
const info = Sinon.spy(diag, 'info');
process.env.OTEL_SDK_DISABLED = 'true';
const sdk = startNodeSDK();
const sdk = startNodeSDK({});

Sinon.assert.calledWith(info, 'OpenTelemetry SDK is disabled');

Expand All @@ -46,7 +46,7 @@ describe('startNodeSDK', function () {
it('should return NOOP_SDK when disabled is true', () => {
process.env.OTEL_EXPERIMENTAL_CONFIG_FILE =
'test/fixtures/kitchen-sink.yaml';
const sdk = startNodeSDK();
const sdk = startNodeSDK({});

assertDefaultContextManagerRegistered();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
* limitations under the License.
*/

import { getPropagatorFromEnv } from '../src/utils';
import {
getPropagatorFromEnv,
getKeyListFromObjectArray,
getPropagatorFromConfigFactory,
} from '../src/utils';
import * as assert from 'assert';
import * as sinon from 'sinon';
import { diag } from '@opentelemetry/api';
import { ConfigurationModel } from '@opentelemetry/configuration';

describe('getPropagatorFromEnv', function () {
afterEach(() => {
Expand Down Expand Up @@ -101,3 +106,95 @@ describe('getPropagatorFromEnv', function () {
assert.deepStrictEqual(getPropagatorFromEnv(), null);
});
});

describe('getPropagatorFromConfigFactory', function () {
afterEach(() => {
sinon.restore();
});

it('when not defined', function () {
const propagator = getPropagatorFromConfigFactory({});
assert.deepStrictEqual(propagator, undefined);
});

it('should return the selected propagator when one is in the list', () => {
const config: ConfigurationModel = {
propagator: { composite: [{ tracecontext: undefined }] },
};
assert.deepStrictEqual(getPropagatorFromConfigFactory(config)?.fields(), [
'traceparent',
'tracestate',
]);
});

it('should return the selected propagators when multiple are in the list', () => {
const config: ConfigurationModel = {
propagator: {
composite: [
{ tracecontext: undefined },
{ baggage: undefined },
{ b3: undefined },
{ b3multi: undefined },
{ jaeger: undefined },
],
},
};
assert.deepStrictEqual(getPropagatorFromConfigFactory(config)?.fields(), [
'traceparent',
'tracestate',
'baggage',
'b3',
'x-b3-traceid',
'x-b3-spanid',
'x-b3-flags',
'x-b3-sampled',
'x-b3-parentspanid',
'uber-trace-id',
]);
});

it('should return null and warn if propagators are unknown', () => {
const warnStub = sinon.stub(diag, 'warn');
const config: ConfigurationModel = {
propagator: {
composite: [
{ my: undefined },
{ unknown: undefined },
{ propagators: undefined },
],
},
};
assert.deepStrictEqual(getPropagatorFromConfigFactory(config), null);
sinon.assert.calledWithExactly(
warnStub,
'Propagator "my" requested through configuration is unavailable.'
);
sinon.assert.calledWithExactly(
warnStub,
'Propagator "unknown" requested through configuration is unavailable.'
);
sinon.assert.calledWithExactly(
warnStub,
'Propagator "propagators" requested through configuration is unavailable.'
);
sinon.assert.calledThrice(warnStub);
});

it('should return null if only "none" is selected', () => {
const config: ConfigurationModel = {
propagator: {
composite: [{ none: undefined }],
},
};

assert.deepStrictEqual(getPropagatorFromConfigFactory(config), null);
});
});

describe('getStringKeyListFromObjectArray', function () {
it('correct list', () => {
const input = [{ a: 1, b: 2 }, { c: 3, d: 4 }, { e: 5 }];
const result = getKeyListFromObjectArray(input);
assert.deepStrictEqual(result, ['a', 'b', 'c', 'd', 'e']);
});
});