diff --git a/packages/opentelemetry-sdk-node/README.md b/packages/opentelemetry-sdk-node/README.md index 156352b11b..81a3bacc4a 100644 --- a/packages/opentelemetry-sdk-node/README.md +++ b/packages/opentelemetry-sdk-node/README.md @@ -2,7 +2,7 @@ [![NPM Published Version][npm-img]][npm-url] [![dependencies][dependencies-image]][dependencies-url] -[![devDependencies][devDependencies-image]][devDependencies-url] +[![devDependencies][devdependencies-image]][devdependencies-url] [![Apache License][license-image]][license-image] This package provides the full OpenTelemetry SDK for Node.js including tracing and metrics. @@ -34,13 +34,16 @@ $ npm install @opentelemetry/auto-instrumentations-node Before any other module in your application is loaded, you must initialize the SDK. If you fail to initialize the SDK or initialize it too late, no-op implementations will be provided to any library which acquires a tracer or meter from the API. + This example uses Jaeger and Prometheus, but exporters exist for [other tracing backends][other-tracing-backends]. ```javascript const opentelemetry = require("@opentelemetry/sdk-node"); const { JaegerExporter } = require("@opentelemetry/exporter-jaeger"); const { PrometheusExporter } = require("@opentelemetry/exporter-prometheus"); -const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node"); +const { + getNodeAutoInstrumentations, +} = require("@opentelemetry/auto-instrumentations-node"); const jaegerExporter = new JaegerExporter({ serviceName: 'my-service', @@ -59,22 +62,21 @@ const sdk = new opentelemetry.NodeSDK({ // You can optionally detect resources asynchronously from the environment. // Detected resources are merged with the resources provided in the SDK configuration. -sdk - .start() - .then(() => { - // Resources have been detected and SDK is started - }) +sdk.start().then(() => { + // Resources have been detected and SDK is started +}); // You can also use the shutdown method to gracefully shut down the SDK before process shutdown // or on some operating system signal. const process = require("process"); process.on("SIGTERM", () => { - sdk.shutdown() + sdk + .shutdown() .then( () => console.log("SDK shut down successfully"), - (err) => console.log("Error shutting down SDK", err), + (err) => console.log("Error shutting down SDK", err) ) - .finally(() => process.exit(0)) + .finally(() => process.exit(0)); }); ``` @@ -126,7 +128,7 @@ Configure a custom sampler. By default all traces will be sampled. Configure a trace exporter. If an exporter OR span processor is not configured, the tracing SDK will not be initialized and registered. If an exporter is configured, it will be used with a [BatchSpanProcessor](../opentelemetry-tracing/src/export/BatchSpanProcessor.ts). -### traceParams +### spanLimits Configure tracing parameters. These are the same trace parameters used to [configure a tracer](../opentelemetry-tracing/src/types.ts#L71). @@ -145,9 +147,8 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [dependencies-image]: https://status.david-dm.org/gh/open-telemetry/opentelemetry-js.svg?path=packages%2Fopentelemetry-sdk-node [dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-sdk-node -[devDependencies-image]: https://status.david-dm.org/gh/open-telemetry/opentelemetry-js.svg?path=packages%2Fopentelemetry-sdk-node&type=dev -[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-sdk-node&type=dev +[devdependencies-image]: https://status.david-dm.org/gh/open-telemetry/opentelemetry-js.svg?path=packages%2Fopentelemetry-sdk-node&type=dev +[devdependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-sdk-node&type=dev [npm-url]: https://www.npmjs.com/package/@opentelemetry/sdk-node [npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fsdk-node.svg - [other-tracing-backends]: https://github.com/open-telemetry/opentelemetry-js#trace-exporters diff --git a/packages/opentelemetry-sdk-node/src/sdk.ts b/packages/opentelemetry-sdk-node/src/sdk.ts index 7791461153..4f8741d672 100644 --- a/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/packages/opentelemetry-sdk-node/src/sdk.ts @@ -67,8 +67,8 @@ export class NodeSDK { if (configuration.sampler) { tracerProviderConfig.sampler = configuration.sampler; } - if (configuration.traceParams) { - tracerProviderConfig.traceParams = configuration.traceParams; + if (configuration.spanLimits) { + tracerProviderConfig.spanLimits = configuration.spanLimits; } const spanProcessor = diff --git a/packages/opentelemetry-sdk-node/src/types.ts b/packages/opentelemetry-sdk-node/src/types.ts index a34c1b5140..27d12b8825 100644 --- a/packages/opentelemetry-sdk-node/src/types.ts +++ b/packages/opentelemetry-sdk-node/src/types.ts @@ -22,7 +22,7 @@ import { Resource } from '@opentelemetry/resources'; import { SpanExporter, SpanProcessor, - TraceParams, + SpanLimits, } from '@opentelemetry/tracing'; export interface NodeSDKConfiguration { @@ -38,5 +38,5 @@ export interface NodeSDKConfiguration { sampler: Sampler; spanProcessor: SpanProcessor; traceExporter: SpanExporter; - traceParams: TraceParams; + spanLimits: SpanLimits; } diff --git a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts index 01d6cb8b21..436bf313a7 100644 --- a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts +++ b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts @@ -41,6 +41,13 @@ import { BatchSpanProcessor } from './export/BatchSpanProcessor'; export type PROPAGATOR_FACTORY = () => TextMapPropagator; export type EXPORTER_FACTORY = () => SpanExporter; +export enum ForceFlushState { + 'resolved', + 'timeout', + 'error', + 'unresolved', +} + /** * This class represents a basic tracer provider which platform libraries can extend */ @@ -140,6 +147,55 @@ export class BasicTracerProvider implements TracerProvider { } } + forceFlush(): Promise { + const timeout = this._config.forceFlushTimeoutMillis; + const promises = this._registeredSpanProcessors.map( + (spanProcessor: SpanProcessor) => { + return new Promise(resolve => { + let state: ForceFlushState; + const timeoutInterval = setTimeout(() => { + resolve( + new Error( + `Span processor did not completed within timeout period of ${timeout} ms` + ) + ); + state = ForceFlushState.timeout; + }, timeout); + + spanProcessor + .forceFlush() + .then(() => { + clearTimeout(timeoutInterval); + if (state !== ForceFlushState.timeout) { + state = ForceFlushState.resolved; + resolve(state); + } + }) + .catch(error => { + clearTimeout(timeoutInterval); + state = ForceFlushState.error; + resolve(error); + }); + }); + } + ); + + return new Promise((resolve, reject) => { + Promise.all(promises) + .then(results => { + const errors = results.filter( + result => result !== ForceFlushState.resolved + ); + if (errors.length > 0) { + reject(errors); + } else { + resolve(); + } + }) + .catch(error => reject([error])); + }); + } + shutdown() { return this.activeSpanProcessor.shutdown(); } diff --git a/packages/opentelemetry-tracing/src/Span.ts b/packages/opentelemetry-tracing/src/Span.ts index 79ab92544a..d7a18fbe2e 100644 --- a/packages/opentelemetry-tracing/src/Span.ts +++ b/packages/opentelemetry-tracing/src/Span.ts @@ -29,7 +29,7 @@ import { ReadableSpan } from './export/ReadableSpan'; import { TimedEvent } from './TimedEvent'; import { Tracer } from './Tracer'; import { SpanProcessor } from './SpanProcessor'; -import { TraceParams } from './types'; +import { SpanLimits } from './types'; import { SpanAttributeValue, Context } from '@opentelemetry/api'; import { ExceptionEventName } from './enums'; @@ -56,7 +56,7 @@ export class Span implements api.Span, ReadableSpan { private _ended = false; private _duration: api.HrTime = [-1, -1]; private readonly _spanProcessor: SpanProcessor; - private readonly _traceParams: TraceParams; + private readonly _spanLimits: SpanLimits; /** Constructs a new Span instance. */ constructor( @@ -77,7 +77,7 @@ export class Span implements api.Span, ReadableSpan { this.startTime = timeInputToHrTime(startTime); this.resource = parentTracer.resource; this.instrumentationLibrary = parentTracer.instrumentationLibrary; - this._traceParams = parentTracer.getActiveTraceParams(); + this._spanLimits = parentTracer.getSpanLimits(); this._spanProcessor = parentTracer.getActiveSpanProcessor(); this._spanProcessor.onStart(this, context); } @@ -100,7 +100,7 @@ export class Span implements api.Span, ReadableSpan { if ( Object.keys(this.attributes).length >= - this._traceParams.numberOfAttributesPerSpan! && + this._spanLimits.attributeCountLimit! && !Object.prototype.hasOwnProperty.call(this.attributes, key) ) { return this; @@ -129,7 +129,7 @@ export class Span implements api.Span, ReadableSpan { startTime?: api.TimeInput ): this { if (this._isSpanEnded()) return this; - if (this.events.length >= this._traceParams.numberOfEventsPerSpan!) { + if (this.events.length >= this._spanLimits.eventCountLimit!) { api.diag.warn('Dropping extra events.'); this.events.shift(); } diff --git a/packages/opentelemetry-tracing/src/Tracer.ts b/packages/opentelemetry-tracing/src/Tracer.ts index 314ccbba72..e84ebf77b4 100644 --- a/packages/opentelemetry-tracing/src/Tracer.ts +++ b/packages/opentelemetry-tracing/src/Tracer.ts @@ -24,7 +24,7 @@ import { import { Resource } from '@opentelemetry/resources'; import { BasicTracerProvider } from './BasicTracerProvider'; import { Span } from './Span'; -import { TraceParams, TracerConfig } from './types'; +import { SpanLimits, TracerConfig } from './types'; import { mergeConfig } from './utility'; /** @@ -32,7 +32,7 @@ import { mergeConfig } from './utility'; */ export class Tracer implements api.Tracer { private readonly _sampler: api.Sampler; - private readonly _traceParams: TraceParams; + private readonly _spanLimits: SpanLimits; private readonly _idGenerator: IdGenerator; readonly resource: Resource; readonly instrumentationLibrary: InstrumentationLibrary; @@ -47,7 +47,7 @@ export class Tracer implements api.Tracer { ) { const localConfig = mergeConfig(config); this._sampler = localConfig.sampler; - this._traceParams = localConfig.traceParams; + this._spanLimits = localConfig.spanLimits; this._idGenerator = config.idGenerator || new RandomIdGenerator(); this.resource = _tracerProvider.resource; this.instrumentationLibrary = instrumentationLibrary; @@ -126,9 +126,9 @@ export class Tracer implements api.Tracer { return span; } - /** Returns the active {@link TraceParams}. */ - getActiveTraceParams(): TraceParams { - return this._traceParams; + /** Returns the active {@link SpanLimits}. */ + getSpanLimits(): SpanLimits { + return this._spanLimits; } getActiveSpanProcessor() { diff --git a/packages/opentelemetry-tracing/src/config.ts b/packages/opentelemetry-tracing/src/config.ts index 2db3db8adc..433a0161da 100644 --- a/packages/opentelemetry-tracing/src/config.ts +++ b/packages/opentelemetry-tracing/src/config.ts @@ -31,15 +31,16 @@ const FALLBACK_OTEL_TRACES_SAMPLER = TracesSamplerValues.AlwaysOn; /** * Default configuration. For fields with primitive values, any user-provided * value will override the corresponding default value. For fields with - * non-primitive values (like `traceParams`), the user-provided value will be + * non-primitive values (like `spanLimits`), the user-provided value will be * used to extend the default value. */ export const DEFAULT_CONFIG = { sampler: buildSamplerFromEnv(env), - traceParams: { - numberOfAttributesPerSpan: getEnv().OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, - numberOfLinksPerSpan: getEnv().OTEL_SPAN_LINK_COUNT_LIMIT, - numberOfEventsPerSpan: getEnv().OTEL_SPAN_EVENT_COUNT_LIMIT, + forceFlushTimeoutMillis: 30000, + spanLimits: { + attributeCountLimit: getEnv().OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, + linkCountLimit: getEnv().OTEL_SPAN_LINK_COUNT_LIMIT, + eventCountLimit: getEnv().OTEL_SPAN_EVENT_COUNT_LIMIT, }, }; diff --git a/packages/opentelemetry-tracing/src/types.ts b/packages/opentelemetry-tracing/src/types.ts index 31561c1915..be3a595490 100644 --- a/packages/opentelemetry-tracing/src/types.ts +++ b/packages/opentelemetry-tracing/src/types.ts @@ -29,8 +29,8 @@ export interface TracerConfig { */ sampler?: Sampler; - /** Trace Parameters */ - traceParams?: TraceParams; + /** Span Limits */ + spanLimits?: SpanLimits; /** Resource associated with trace telemetry */ resource?: Resource; @@ -40,6 +40,12 @@ export interface TracerConfig { * The default idGenerator generates random ids */ idGenerator?: IdGenerator; + + /** + * How long the forceFlush can run before it is cancelled. + * The default value is 30000ms + */ + forceFlushTimeoutMillis?: number; } /** @@ -56,13 +62,13 @@ export interface SDKRegistrationConfig { } /** Global configuration of trace service */ -export interface TraceParams { - /** numberOfAttributesPerSpan is number of attributes per span */ - numberOfAttributesPerSpan?: number; - /** numberOfLinksPerSpan is number of links per span */ - numberOfLinksPerSpan?: number; - /** numberOfEventsPerSpan is number of message events per span */ - numberOfEventsPerSpan?: number; +export interface SpanLimits { + /** attributeCountLimit is number of attributes per span */ + attributeCountLimit?: number; + /** linkCountLimit is number of links per span */ + linkCountLimit?: number; + /** eventCountLimit is number of message events per span */ + eventCountLimit?: number; } /** Interface configuration for a buffer. */ diff --git a/packages/opentelemetry-tracing/src/utility.ts b/packages/opentelemetry-tracing/src/utility.ts index 615d4d57f3..88ac69701c 100644 --- a/packages/opentelemetry-tracing/src/utility.ts +++ b/packages/opentelemetry-tracing/src/utility.ts @@ -33,10 +33,10 @@ export function mergeConfig(userConfig: TracerConfig) { userConfig ); - target.traceParams = Object.assign( + target.spanLimits = Object.assign( {}, - DEFAULT_CONFIG.traceParams, - userConfig.traceParams || {} + DEFAULT_CONFIG.spanLimits, + userConfig.spanLimits || {} ); return target; diff --git a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts index 8f2f20269b..06bdb2d6e0 100644 --- a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts @@ -78,51 +78,51 @@ describe('BasicTracerProvider', () => { assert.ok(provider instanceof BasicTracerProvider); }); - it('should construct an instance with default trace params', () => { + it('should construct an instance with default span limits', () => { const tracer = new BasicTracerProvider({}).getTracer('default'); - assert.deepStrictEqual(tracer.getActiveTraceParams(), { - numberOfAttributesPerSpan: 128, - numberOfEventsPerSpan: 128, - numberOfLinksPerSpan: 128, + assert.deepStrictEqual(tracer.getSpanLimits(), { + attributeCountLimit: 128, + eventCountLimit: 128, + linkCountLimit: 128, }); }); - it('should construct an instance with customized numberOfAttributesPerSpan trace params', () => { + it('should construct an instance with customized attributeCountLimit span limits', () => { const tracer = new BasicTracerProvider({ - traceParams: { - numberOfAttributesPerSpan: 100, + spanLimits: { + attributeCountLimit: 100, }, }).getTracer('default'); - assert.deepStrictEqual(tracer.getActiveTraceParams(), { - numberOfAttributesPerSpan: 100, - numberOfEventsPerSpan: 128, - numberOfLinksPerSpan: 128, + assert.deepStrictEqual(tracer.getSpanLimits(), { + attributeCountLimit: 100, + eventCountLimit: 128, + linkCountLimit: 128, }); }); - it('should construct an instance with customized numberOfEventsPerSpan trace params', () => { + it('should construct an instance with customized eventCountLimit span limits', () => { const tracer = new BasicTracerProvider({ - traceParams: { - numberOfEventsPerSpan: 300, + spanLimits: { + eventCountLimit: 300, }, }).getTracer('default'); - assert.deepStrictEqual(tracer.getActiveTraceParams(), { - numberOfAttributesPerSpan: 128, - numberOfEventsPerSpan: 300, - numberOfLinksPerSpan: 128, + assert.deepStrictEqual(tracer.getSpanLimits(), { + attributeCountLimit: 128, + eventCountLimit: 300, + linkCountLimit: 128, }); }); - it('should construct an instance with customized numberOfLinksPerSpan trace params', () => { + it('should construct an instance with customized linkCountLimit span limits', () => { const tracer = new BasicTracerProvider({ - traceParams: { - numberOfLinksPerSpan: 10, + spanLimits: { + linkCountLimit: 10, }, }).getTracer('default'); - assert.deepStrictEqual(tracer.getActiveTraceParams(), { - numberOfAttributesPerSpan: 128, - numberOfEventsPerSpan: 128, - numberOfLinksPerSpan: 10, + assert.deepStrictEqual(tracer.getSpanLimits(), { + attributeCountLimit: 128, + eventCountLimit: 128, + linkCountLimit: 10, }); }); @@ -441,6 +441,64 @@ describe('BasicTracerProvider', () => { }); }); + describe('.forceFlush()', () => { + it('should call forceFlush on all registered span processors', done => { + sinon.restore(); + const forceFlushStub = sinon.stub( + NoopSpanProcessor.prototype, + 'forceFlush' + ); + forceFlushStub.resolves(); + + const tracerProvider = new BasicTracerProvider(); + const spanProcessorOne = new NoopSpanProcessor(); + const spanProcessorTwo = new NoopSpanProcessor(); + + tracerProvider.addSpanProcessor(spanProcessorOne); + tracerProvider.addSpanProcessor(spanProcessorTwo); + + tracerProvider + .forceFlush() + .then(() => { + sinon.restore(); + assert(forceFlushStub.calledTwice); + done(); + }) + .catch(error => { + sinon.restore(); + done(error); + }); + }); + + it('should throw error when calling forceFlush on all registered span processors fails', done => { + sinon.restore(); + + const forceFlushStub = sinon.stub( + NoopSpanProcessor.prototype, + 'forceFlush' + ); + forceFlushStub.returns(Promise.reject('Error')); + + const tracerProvider = new BasicTracerProvider(); + const spanProcessorOne = new NoopSpanProcessor(); + const spanProcessorTwo = new NoopSpanProcessor(); + tracerProvider.addSpanProcessor(spanProcessorOne); + tracerProvider.addSpanProcessor(spanProcessorTwo); + + tracerProvider + .forceFlush() + .then(() => { + sinon.restore(); + done(new Error('Successful forceFlush not expected')); + }) + .catch(_error => { + sinon.restore(); + sinon.assert.calledTwice(forceFlushStub); + done(); + }); + }); + }); + describe('.bind()', () => { it('should bind context with NoopContextManager context manager', done => { const tracer = new BasicTracerProvider().getTracer('default'); diff --git a/packages/opentelemetry-tracing/test/Span.test.ts b/packages/opentelemetry-tracing/test/Span.test.ts index 23dcacd639..75631fa5c3 100644 --- a/packages/opentelemetry-tracing/test/Span.test.ts +++ b/packages/opentelemetry-tracing/test/Span.test.ts @@ -37,9 +37,9 @@ const performanceTimeOrigin = hrTime(); describe('Span', () => { const tracer = new BasicTracerProvider({ - traceParams: { - numberOfAttributesPerSpan: 100, - numberOfEventsPerSpan: 100, + spanLimits: { + attributeCountLimit: 100, + eventCountLimit: 100, }, }).getTracer('default'); const name = 'span1';