diff --git a/packages/opentelemetry-core/src/platform/browser/ShutdownNotifier.ts b/packages/opentelemetry-core/src/platform/browser/ShutdownNotifier.ts index 05ccc38e011..616692fd9d2 100644 --- a/packages/opentelemetry-core/src/platform/browser/ShutdownNotifier.ts +++ b/packages/opentelemetry-core/src/platform/browser/ShutdownNotifier.ts @@ -25,8 +25,8 @@ export function notifyOnGlobalShutdown(cb: () => void): () => void { } /** - * Warning: meant for internal use only! Closes the current window, triggering the unload event + * Warning: meant for internal use only! */ export function _invokeGlobalShutdown() { - window.close(); -} + window.dispatchEvent(new Event("unload")); +} \ No newline at end of file diff --git a/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts index ad4472066fa..66646afc65b 100644 --- a/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts @@ -31,13 +31,13 @@ import { statusDescriptionTagName, } from '../../src/transform'; import * as zipkinTypes from '../../src/types'; -import { Resource, TELEMETRY_SDK_RESOURCE } from '@opentelemetry/resources'; +import { Resource } from '@opentelemetry/resources'; const logger = new NoopLogger(); const tracer = new BasicTracerProvider({ logger, }).getTracer('default'); -const language = tracer.resource.attributes[TELEMETRY_SDK_RESOURCE.LANGUAGE]; +const language = 'nodejs'; const parentId = '5c1c63257de34c67'; const spanContext: api.SpanContext = { @@ -54,7 +54,7 @@ const DUMMY_RESOURCE = new Resource({ describe('transform', () => { describe('toZipkinSpan', () => { - it('should convert an OpenTelemetry span to a Zipkin span', () => { + it('should convert an OpenTelemetry span to a Zipkin span', async () => { const span = new Span( tracer, 'my-span', @@ -70,7 +70,7 @@ describe('transform', () => { span.end(); const zipkinSpan = toZipkinSpan( - span, + await span.toReadableSpan(), 'my-service', statusCodeTagName, statusDescriptionTagName @@ -104,7 +104,7 @@ describe('transform', () => { traceId: span.spanContext.traceId, }); }); - it("should skip parentSpanId if doesn't exist", () => { + it("should skip parentSpanId if doesn't exist", async () => { const span = new Span( tracer, 'my-span', @@ -114,7 +114,7 @@ describe('transform', () => { span.end(); const zipkinSpan = toZipkinSpan( - span, + await span.toReadableSpan(), 'my-service', statusCodeTagName, statusDescriptionTagName @@ -151,12 +151,12 @@ describe('transform', () => { ].forEach(item => it(`should map OpenTelemetry SpanKind ${ api.SpanKind[item.ot] - } to Zipkin ${item.zipkin}`, () => { + } to Zipkin ${item.zipkin}`, async () => { const span = new Span(tracer, 'my-span', spanContext, item.ot); span.end(); const zipkinSpan = toZipkinSpan( - span, + await span.toReadableSpan(), 'my-service', statusCodeTagName, statusDescriptionTagName diff --git a/packages/opentelemetry-grpc-utils/test/grpcUtils.test.ts b/packages/opentelemetry-grpc-utils/test/grpcUtils.test.ts index 15f3284961b..b72e127f854 100644 --- a/packages/opentelemetry-grpc-utils/test/grpcUtils.test.ts +++ b/packages/opentelemetry-grpc-utils/test/grpcUtils.test.ts @@ -482,11 +482,12 @@ export const runTests = ( const args = [client, method.request, method.metadata]; await (method.method as any) .apply({}, args) - .then((result: TestRequestResponse | TestRequestResponse[]) => { + .then(async (result: TestRequestResponse | TestRequestResponse[]) => { assert.ok( checkEqual(result)(method.result), 'gRPC call returns correct values' ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); if (checkSpans) { const incomingSpan = spans[0]; @@ -516,7 +517,7 @@ export const runTests = ( }); }); - it(`should raise an error for client childSpan/server rootSpan - ${method.description} - status = OK`, () => { + it(`should raise an error for client childSpan/server rootSpan - ${method.description} - status = OK`, async () => { const expectEmpty = memoryExporter.getFinishedSpans(); assert.strictEqual(expectEmpty.length, 0); @@ -533,9 +534,10 @@ export const runTests = ( const args = [client, method.request, method.metadata]; await (method.method as any) .apply({}, args) - .then(() => { + .then(async () => { // Assert if (checkSpans) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 2); const serverSpan = spans[0]; diff --git a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts index fcb3ec52cb5..93b2286e639 100644 --- a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts @@ -37,7 +37,7 @@ export abstract class BaseObserverMetric name: string, options: api.MetricOptions, private readonly _batcher: Batcher, - resource: Resource, + resource: Promise, metricKind: MetricKind, instrumentationLibrary: InstrumentationLibrary, callback?: (observerResult: api.ObserverResult) => unknown diff --git a/packages/opentelemetry-metrics/src/BatchObserverMetric.ts b/packages/opentelemetry-metrics/src/BatchObserverMetric.ts index 241ff0813a0..6bdc13d9dfb 100644 --- a/packages/opentelemetry-metrics/src/BatchObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/BatchObserverMetric.ts @@ -37,7 +37,7 @@ export class BatchObserverMetric name: string, options: api.BatchMetricOptions, private readonly _batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary, callback?: (observerResult: api.BatchObserverResult) => void ) { diff --git a/packages/opentelemetry-metrics/src/CounterMetric.ts b/packages/opentelemetry-metrics/src/CounterMetric.ts index c3f35fb8bb4..ff857fdaa87 100644 --- a/packages/opentelemetry-metrics/src/CounterMetric.ts +++ b/packages/opentelemetry-metrics/src/CounterMetric.ts @@ -28,7 +28,7 @@ export class CounterMetric extends Metric implements api.Counter { name: string, options: api.MetricOptions, private readonly _batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary ) { super(name, options, MetricKind.COUNTER, resource, instrumentationLibrary); diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts index d0a54049b3e..362f4448a22 100644 --- a/packages/opentelemetry-metrics/src/Meter.ts +++ b/packages/opentelemetry-metrics/src/Meter.ts @@ -39,7 +39,7 @@ export class Meter implements api.Meter { private readonly _logger: api.Logger; private readonly _metrics = new Map>(); private readonly _batcher: Batcher; - private readonly _resource: Resource; + private readonly _resource: Promise; private readonly _instrumentationLibrary: InstrumentationLibrary; private readonly _controller: PushController; private _isShutdown = false; @@ -54,7 +54,9 @@ export class Meter implements api.Meter { ) { this._logger = config.logger || new ConsoleLogger(config.logLevel); this._batcher = config.batcher ?? new UngroupedBatcher(); - this._resource = config.resource || Resource.createTelemetrySDKResource(); + this._resource = Promise.resolve( + config.resource || Resource.createTelemetrySDKResource() + ); this._instrumentationLibrary = instrumentationLibrary; // start the push controller const exporter = config.exporter || new NoopExporter(); diff --git a/packages/opentelemetry-metrics/src/MeterProvider.ts b/packages/opentelemetry-metrics/src/MeterProvider.ts index 5fb6b87e4a1..45500257716 100644 --- a/packages/opentelemetry-metrics/src/MeterProvider.ts +++ b/packages/opentelemetry-metrics/src/MeterProvider.ts @@ -29,12 +29,14 @@ export class MeterProvider implements api.MeterProvider { private _cleanNotifyOnGlobalShutdown: Function | undefined; private _shuttingDownPromise: Promise = Promise.resolve(); private _isShutdown = false; - readonly resource: Resource; + readonly resource: Promise; readonly logger: api.Logger; constructor(config: MeterConfig = DEFAULT_CONFIG) { this.logger = config.logger ?? new ConsoleLogger(config.logLevel); - this.resource = config.resource ?? Resource.createTelemetrySDKResource(); + this.resource = Promise.resolve( + config.resource ?? Resource.createTelemetrySDKResource() + ); this._config = Object.assign({}, config, { logger: this.logger, resource: this.resource, diff --git a/packages/opentelemetry-metrics/src/Metric.ts b/packages/opentelemetry-metrics/src/Metric.ts index 8516ac70958..a2183c3c2cc 100644 --- a/packages/opentelemetry-metrics/src/Metric.ts +++ b/packages/opentelemetry-metrics/src/Metric.ts @@ -34,7 +34,7 @@ export abstract class Metric private readonly _name: string, private readonly _options: api.MetricOptions, private readonly _kind: MetricKind, - readonly resource: Resource, + readonly resource: Promise, readonly instrumentationLibrary: InstrumentationLibrary ) { this._disabled = !!_options.disabled; @@ -85,17 +85,15 @@ export abstract class Metric } getMetricRecord(): Promise { - return new Promise(resolve => { - resolve( - Array.from(this._instruments.values()).map(instrument => ({ - descriptor: this._descriptor, - labels: instrument.getLabels(), - aggregator: instrument.getAggregator(), - resource: this.resource, - instrumentationLibrary: this.instrumentationLibrary, - })) - ); - }); + return this.resource.then(resource => + Array.from(this._instruments.values()).map(instrument => ({ + descriptor: this._descriptor, + labels: instrument.getLabels(), + aggregator: instrument.getAggregator(), + resource, + instrumentationLibrary: this.instrumentationLibrary, + })) + ); } private _getMetricDescriptor(): MetricDescriptor { diff --git a/packages/opentelemetry-metrics/src/SumObserverMetric.ts b/packages/opentelemetry-metrics/src/SumObserverMetric.ts index 99f9b0d3c6a..a1476cf4dce 100644 --- a/packages/opentelemetry-metrics/src/SumObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/SumObserverMetric.ts @@ -31,7 +31,7 @@ export class SumObserverMetric name: string, options: api.MetricOptions, batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary, callback?: (observerResult: api.ObserverResult) => unknown ) { diff --git a/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts b/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts index 19a8d8a0dd3..f7d7266488f 100644 --- a/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts +++ b/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts @@ -30,7 +30,7 @@ export class UpDownCounterMetric name: string, options: api.MetricOptions, private readonly _batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary ) { super( diff --git a/packages/opentelemetry-metrics/src/UpDownSumObserverMetric.ts b/packages/opentelemetry-metrics/src/UpDownSumObserverMetric.ts index 8bb0440487f..d1f5d78d261 100644 --- a/packages/opentelemetry-metrics/src/UpDownSumObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/UpDownSumObserverMetric.ts @@ -29,7 +29,7 @@ export class UpDownSumObserverMetric name: string, options: api.MetricOptions, batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary, callback?: (observerResult: api.ObserverResult) => unknown ) { diff --git a/packages/opentelemetry-metrics/src/ValueObserverMetric.ts b/packages/opentelemetry-metrics/src/ValueObserverMetric.ts index 8c2547600d9..e7b62bf0fd2 100644 --- a/packages/opentelemetry-metrics/src/ValueObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/ValueObserverMetric.ts @@ -28,7 +28,7 @@ export class ValueObserverMetric name: string, options: api.MetricOptions, batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary, callback?: (observerResult: api.ObserverResult) => unknown ) { diff --git a/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts b/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts index 82757641056..f9238c5e9dc 100644 --- a/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts +++ b/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts @@ -30,7 +30,7 @@ export class ValueRecorderMetric name: string, options: api.MetricOptions, private readonly _batcher: Batcher, - resource: Resource, + resource: Promise, instrumentationLibrary: InstrumentationLibrary ) { super( diff --git a/packages/opentelemetry-metrics/src/types.ts b/packages/opentelemetry-metrics/src/types.ts index d8e5768a482..3860ef5fbfe 100644 --- a/packages/opentelemetry-metrics/src/types.ts +++ b/packages/opentelemetry-metrics/src/types.ts @@ -35,7 +35,7 @@ export interface MeterConfig { interval?: number; /** Resource associated with metric telemetry */ - resource?: Resource; + resource?: Resource | Promise; /** Metric batcher. */ batcher?: Batcher; diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts index 81ce0a31aad..8e993f931d4 100644 --- a/packages/opentelemetry-metrics/test/Meter.test.ts +++ b/packages/opentelemetry-metrics/test/Meter.test.ts @@ -135,7 +135,7 @@ describe('Meter', () => { it('should pipe through resource', async () => { const counter = meter.createCounter('name') as CounterMetric; - assert.ok(counter.resource instanceof Resource); + assert.ok((await counter.resource) instanceof Resource); counter.add(1, { foo: 'bar' }); @@ -349,7 +349,7 @@ describe('Meter', () => { const upDownCounter = meter.createUpDownCounter( 'name' ) as UpDownCounterMetric; - assert.ok(upDownCounter.resource instanceof Resource); + assert.ok((await upDownCounter.resource) instanceof Resource); upDownCounter.add(1, { foo: 'bar' }); @@ -559,7 +559,7 @@ describe('Meter', () => { const valueRecorder = meter.createValueRecorder( 'name' ) as ValueRecorderMetric; - assert.ok(valueRecorder.resource instanceof Resource); + assert.ok((await valueRecorder.resource) instanceof Resource); valueRecorder.record(1, { foo: 'bar' }); @@ -869,7 +869,7 @@ describe('Meter', () => { result.observe(42, { foo: 'bar' }); return Promise.resolve(); }) as SumObserverMetric; - assert.ok(sumObserver.resource instanceof Resource); + assert.ok((await sumObserver.resource) instanceof Resource); const [record] = await sumObserver.getMetricRecord(); assert.ok(record.resource instanceof Resource); @@ -950,7 +950,7 @@ describe('Meter', () => { const valueObserver = meter.createValueObserver('name', {}, result => { result.observe(42, { foo: 'bar' }); }) as ValueObserverMetric; - assert.ok(valueObserver.resource instanceof Resource); + assert.ok((await valueObserver.resource) instanceof Resource); const [record] = await valueObserver.getMetricRecord(); assert.ok(record.resource instanceof Resource); @@ -1093,7 +1093,7 @@ describe('Meter', () => { return Promise.resolve(); } ) as UpDownSumObserverMetric; - assert.ok(upDownSumObserver.resource instanceof Resource); + assert.ok((await upDownSumObserver.resource) instanceof Resource); const [record] = await upDownSumObserver.getMetricRecord(); assert.ok(record.resource instanceof Resource); diff --git a/packages/opentelemetry-node/test/NodeTracerProvider.test.ts b/packages/opentelemetry-node/test/NodeTracerProvider.test.ts index d1b00006b13..cef95de3d0e 100644 --- a/packages/opentelemetry-node/test/NodeTracerProvider.test.ts +++ b/packages/opentelemetry-node/test/NodeTracerProvider.test.ts @@ -185,15 +185,16 @@ describe('NodeTracerProvider', () => { assert.strictEqual(span.isRecording(), true); }); - it('should assign resource to span', () => { + it('should assign resource to span', async () => { provider = new NodeTracerProvider({ logger: new NoopLogger(), }); const span = provider.getTracer('default').startSpan('my-span') as Span; assert.ok(span); - assert.ok(span.resource instanceof Resource); + const resource = await span.resource; + assert.ok(resource instanceof Resource); assert.equal( - span.resource.attributes[TELEMETRY_SDK_RESOURCE.LANGUAGE], + resource.attributes[TELEMETRY_SDK_RESOURCE.LANGUAGE], 'nodejs' ); }); diff --git a/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts b/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts index 60d34fa45ce..56b46244737 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts @@ -161,6 +161,7 @@ describe('HttpPlugin', () => { const result = await httpRequest.get( `${protocol}://${hostname}:${serverPort}${pathname}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [incomingSpan, outgoingSpan] = spans; const validations = { @@ -201,6 +202,7 @@ describe('HttpPlugin', () => { result.reqHeaders[OT_REQUEST_HEADER] === undefined, 'custom header should be stripped' ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(result.data, 'Ok'); assert.strictEqual(spans.length, 0); @@ -264,6 +266,7 @@ describe('HttpPlugin', () => { }, } ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [incomingSpan, outgoingSpan] = spans; const validations = { @@ -318,6 +321,7 @@ describe('HttpPlugin', () => { result.reqHeaders[OT_REQUEST_HEADER] === undefined, 'custom header should be stripped' ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(result.data, 'Ok'); assert.strictEqual(spans.length, 0); @@ -348,12 +352,14 @@ describe('HttpPlugin', () => { httpErrorCodes[i].toString() ); + await new Promise(resolve => setTimeout(resolve)); const isReset = memoryExporter.getFinishedSpans().length === 0; assert.ok(isReset); const result = await httpRequest.get( `${protocol}://${hostname}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const reqSpan = spans[0]; @@ -384,6 +390,7 @@ describe('HttpPlugin', () => { `${protocol}://${hostname}${testPath}` ); span.end(); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [reqSpan, localSpan] = spans; const validations = { @@ -427,6 +434,7 @@ describe('HttpPlugin', () => { `${protocol}://${hostname}${testPath}` ); span.end(); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [reqSpan, localSpan] = spans; const validations = { @@ -464,6 +472,7 @@ describe('HttpPlugin', () => { await provider.getTracer('default').withSpan(span, async () => { for (let i = 0; i < num; i++) { await httpRequest.get(`${protocol}://${hostname}${testPath}`); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans[i].name, 'HTTP GET'); assert.strictEqual( @@ -472,6 +481,7 @@ describe('HttpPlugin', () => { ); } span.end(); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); // 5 child spans ended + 1 span (root) assert.strictEqual(spans.length, 6); @@ -485,6 +495,7 @@ describe('HttpPlugin', () => { await httpRequest.get( `${protocol}://${hostname}:${serverPort}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); }); @@ -501,6 +512,7 @@ describe('HttpPlugin', () => { // nock throw assert.ok(error.message.startsWith('Nock: No match for request')); } + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); }); @@ -521,16 +533,18 @@ describe('HttpPlugin', () => { ) > 0 ); } + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); // for this arg with don't provide trace. We pass arg to original method (http.get) assert.strictEqual(spans.length, 0); }); } - it('should have 1 ended span when request throw on bad "options" object', () => { + it('should have 1 ended span when request throw on bad "options" object', async () => { try { http.request({ protocol: 'telnet' }); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } @@ -560,18 +574,20 @@ describe('HttpPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } }); - it('should have 1 ended span when request throw on bad "options" object', () => { + it('should have 1 ended span when request throw on bad "options" object', async () => { nock.cleanAll(); nock.enableNetConnect(); try { http.request({ protocol: 'telnet' }); assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } @@ -601,6 +617,7 @@ describe('HttpPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } @@ -636,6 +653,7 @@ describe('HttpPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); @@ -674,6 +692,7 @@ describe('HttpPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); @@ -686,7 +705,8 @@ describe('HttpPlugin', () => { nock.cleanAll(); nock.enableNetConnect(); const req = http.request(`${protocol}://${hostname}/`); - req.on('close', () => { + req.on('close', async () => { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); @@ -702,7 +722,8 @@ describe('HttpPlugin', () => { const req = http.request(`${host}/`); req.on('response', response => { response.on('data', () => {}); - response.on('end', () => { + response.on('end', async () => { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); @@ -722,6 +743,7 @@ describe('HttpPlugin', () => { await httpRequest.get( `${protocol}://${hostname}:${serverPort}${pathname}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [incomingSpan, outgoingSpan] = spans; @@ -779,6 +801,7 @@ describe('HttpPlugin', () => { await httpRequest.get( `${protocol}://${hostname}:${serverPort}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); }); @@ -793,6 +816,7 @@ describe('HttpPlugin', () => { const result = await httpRequest.get( `${protocol}://${hostname}:${serverPort}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); assert( result.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] !== undefined ); @@ -817,6 +841,7 @@ describe('HttpPlugin', () => { const result = await httpRequest.get( `${protocol}://${hostname}:${serverPort}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); assert( result.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] !== undefined ); @@ -846,8 +871,9 @@ describe('HttpPlugin', () => { tracer.withSpan(span, () => { httpRequest .get(`${protocol}://${hostname}:${serverPort}${testPath}`) - .then(result => { + .then(async result => { span.end(); + await new Promise(resolve => setTimeout(resolve)); assert( result.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] !== undefined diff --git a/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts b/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts index 8ea38fea17b..e3e5179bf45 100644 --- a/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts +++ b/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts @@ -107,6 +107,7 @@ describe('HttpPlugin Integration tests', () => { const result = await httpRequest.get( `${protocol}://google.fr/?query=test` ); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; @@ -133,6 +134,7 @@ describe('HttpPlugin Integration tests', () => { const result = await httpRequest.get( new url.URL(`${protocol}://google.fr/?query=test`) ); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; @@ -162,6 +164,7 @@ describe('HttpPlugin Integration tests', () => { headers: { 'x-foo': 'foo' }, } ); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; @@ -189,6 +192,7 @@ describe('HttpPlugin Integration tests', () => { it('custom attributes should show up on client spans', async () => { const result = await httpRequest.get(`${protocol}://google.fr/`); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { @@ -216,6 +220,7 @@ describe('HttpPlugin Integration tests', () => { ); const result = await httpRequest.get(options); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { diff --git a/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts b/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts index 22c459d6b12..cdd19d02f60 100644 --- a/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts +++ b/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts @@ -162,6 +162,7 @@ describe('HttpsPlugin', () => { const result = await httpsRequest.get( `${protocol}://${hostname}:${serverPort}${pathname}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [incomingSpan, outgoingSpan] = spans; const validations = { @@ -198,6 +199,7 @@ describe('HttpsPlugin', () => { }; const result = await httpsRequest.get(options); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(result.data, 'Ok'); assert.strictEqual(spans.length, 0); @@ -271,6 +273,7 @@ describe('HttpsPlugin', () => { }, } ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [incomingSpan, outgoingSpan] = spans; const validations = { @@ -322,6 +325,7 @@ describe('HttpsPlugin', () => { }; const result = await httpsRequest.get(options); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(result.data, 'Ok'); assert.strictEqual(spans.length, 0); @@ -340,12 +344,14 @@ describe('HttpsPlugin', () => { httpErrorCodes[i].toString() ); + await new Promise(resolve => setTimeout(resolve)); const isReset = memoryExporter.getFinishedSpans().length === 0; assert.ok(isReset); const result = await httpsRequest.get( `${protocol}://${hostname}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const reqSpan = spans[0]; @@ -376,6 +382,7 @@ describe('HttpsPlugin', () => { `${protocol}://${hostname}${testPath}` ); span.end(); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [reqSpan, localSpan] = spans; const validations = { @@ -419,6 +426,7 @@ describe('HttpsPlugin', () => { `${protocol}://${hostname}${testPath}` ); span.end(); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [reqSpan, localSpan] = spans; const validations = { @@ -456,6 +464,7 @@ describe('HttpsPlugin', () => { await tracer.withSpan(span, async () => { for (let i = 0; i < num; i++) { await httpsRequest.get(`${protocol}://${hostname}${testPath}`); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans[i].name, 'HTTP GET'); assert.strictEqual( @@ -464,6 +473,7 @@ describe('HttpsPlugin', () => { ); } span.end(); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); // 5 child spans ended + 1 span (root) assert.strictEqual(spans.length, 6); @@ -477,6 +487,7 @@ describe('HttpsPlugin', () => { await httpsRequest.get( `${protocol}://${hostname}:${serverPort}${testPath}` ); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); }); @@ -493,6 +504,7 @@ describe('HttpsPlugin', () => { // nock throw assert.ok(error.message.startsWith('Nock: No match for request')); } + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); }); @@ -513,16 +525,18 @@ describe('HttpsPlugin', () => { ) > 0 ); } + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); // for this arg with don't provide trace. We pass arg to original method (https.get) assert.strictEqual(spans.length, 0); }); } - it('should have 1 ended span when request throw on bad "options" object', () => { + it('should have 1 ended span when request throw on bad "options" object', async () => { try { https.request({ protocol: 'telnet' }); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } @@ -552,18 +566,20 @@ describe('HttpsPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } }); - it('should have 1 ended span when request throw on bad "options" object', () => { + it('should have 1 ended span when request throw on bad "options" object', async () => { nock.cleanAll(); nock.enableNetConnect(); try { https.request({ protocol: 'telnet' }); assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } @@ -593,6 +609,7 @@ describe('HttpsPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); } @@ -628,6 +645,7 @@ describe('HttpsPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); @@ -666,6 +684,7 @@ describe('HttpsPlugin', () => { await promiseRequest; assert.fail(); } catch (error) { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); @@ -680,7 +699,8 @@ describe('HttpsPlugin', () => { const req = https.request(`${host}/`); req.on('response', response => { response.on('data', () => {}); - response.on('end', () => { + response.on('end', async () => { + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const [span] = spans; assert.strictEqual(spans.length, 1); diff --git a/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts b/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts index ef291281f5e..bde691026f7 100644 --- a/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts +++ b/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts @@ -108,6 +108,7 @@ describe('HttpsPlugin Integration tests', () => { }); it('should create a rootSpan for GET requests and add propagation headers', async () => { + await new Promise(resolve => setTimeout(resolve)); let spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); @@ -115,6 +116,7 @@ describe('HttpsPlugin Integration tests', () => { `${protocol}://google.fr/?query=test` ); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { @@ -134,6 +136,7 @@ describe('HttpsPlugin Integration tests', () => { }); it('should create a rootSpan for GET requests and add propagation headers if URL is used', async () => { + await new Promise(resolve => setTimeout(resolve)); let spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); @@ -141,6 +144,7 @@ describe('HttpsPlugin Integration tests', () => { new url.URL(`${protocol}://google.fr/?query=test`) ); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { @@ -160,6 +164,7 @@ describe('HttpsPlugin Integration tests', () => { }); it('should create a valid rootSpan with propagation headers for GET requests if URL and options are used', async () => { + await new Promise(resolve => setTimeout(resolve)); let spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); @@ -168,6 +173,7 @@ describe('HttpsPlugin Integration tests', () => { { headers: { 'x-foo': 'foo' } } ); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { @@ -194,6 +200,7 @@ describe('HttpsPlugin Integration tests', () => { it('custom attributes should show up on client spans', async () => { const result = await httpsRequest.get(`${protocol}://google.fr/`); + await new Promise(resolve => setTimeout(resolve)); const spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { @@ -213,6 +220,7 @@ describe('HttpsPlugin Integration tests', () => { }); it('should create a span for GET requests and add propagation headers with Expect headers', async () => { + await new Promise(resolve => setTimeout(resolve)); let spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); const options = Object.assign( @@ -221,6 +229,7 @@ describe('HttpsPlugin Integration tests', () => { ); const result = await httpsRequest.get(options); + await new Promise(resolve => setTimeout(resolve)); spans = memoryExporter.getFinishedSpans(); const span = spans[0]; const validations = { @@ -260,45 +269,52 @@ describe('HttpsPlugin Integration tests', () => { resHeaders: http.IncomingHttpHeaders; }; let data = ''; - const spans = memoryExporter.getFinishedSpans(); - assert.strictEqual(spans.length, 0); - const options = { headers }; - const req = https.get( - `${protocol}://google.fr/`, - options, - (resp: http.IncomingMessage) => { - const res = (resp as unknown) as http.IncomingMessage & { - req: http.IncomingMessage; - }; - - resp.on('data', chunk => { - data += chunk; - }); - resp.on('end', () => { - validations = { - hostname: 'google.fr', - httpStatusCode: 301, - httpMethod: 'GET', - pathname: '/', - resHeaders: resp.headers, - /* tslint:disable:no-any */ - reqHeaders: (res.req as any).getHeaders - ? (res.req as any).getHeaders() - : (res.req as any)._headers, - /* tslint:enable:no-any */ + setTimeout(() => { + const spans = memoryExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 0); + const options = { headers }; + const req = https.get( + `${protocol}://google.fr/`, + options, + (resp: http.IncomingMessage) => { + const res = (resp as unknown) as http.IncomingMessage & { + req: http.IncomingMessage; }; - }); - } - ); - req.on('close', () => { - const spans = memoryExporter.getFinishedSpans(); - assert.strictEqual(spans.length, 1); - assert.strictEqual(spans[0].name, 'HTTP GET'); - assert.ok(data); - assert.ok(validations.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY]); - assert.ok(validations.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY]); - done(); + resp.on('data', chunk => { + data += chunk; + }); + resp.on('end', () => { + validations = { + hostname: 'google.fr', + httpStatusCode: 301, + httpMethod: 'GET', + pathname: '/', + resHeaders: resp.headers, + /* tslint:disable:no-any */ + reqHeaders: (res.req as any).getHeaders + ? (res.req as any).getHeaders() + : (res.req as any)._headers, + /* tslint:enable:no-any */ + }; + }); + } + ); + + req.on('close', async () => { + await new Promise(resolve => setTimeout(resolve)); + const spans = memoryExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 1); + assert.strictEqual(spans[0].name, 'HTTP GET'); + assert.ok(data); + assert.ok( + validations.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] + ); + assert.ok( + validations.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY] + ); + done(); + }); }); }); } diff --git a/packages/opentelemetry-sdk-node/src/sdk.ts b/packages/opentelemetry-sdk-node/src/sdk.ts index a411ab85dfe..28108987df3 100644 --- a/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/packages/opentelemetry-sdk-node/src/sdk.ts @@ -126,13 +126,15 @@ export class NodeSDK { } /** Detect resource attributes */ - public async detectResources(config?: ResourceDetectionConfig) { + private _detectResources( + config?: ResourceDetectionConfig + ): Promise { const internalConfig: ResourceDetectionConfig = { detectors: [awsEc2Detector, gcpDetector, envDetector], ...config, }; - this.addResource(await detectResources(internalConfig)); + return detectResources(internalConfig); } /** Manually add a resource */ @@ -143,15 +145,16 @@ export class NodeSDK { /** * Once the SDK has been configured, call this method to construct SDK components and register them with the OpenTelemetry API. */ - public async start() { - if (this._autoDetectResources) { - await this.detectResources(); - } + public start() { + const resource = (this._autoDetectResources + ? this._detectResources() + : Promise.resolve(new Resource({})) + ).then(resource => this._resource.merge(resource)); if (this._tracerProviderConfig) { const tracerProvider = new NodeTracerProvider({ ...this._tracerProviderConfig.tracerConfig, - resource: this._resource, + resource, }); tracerProvider.addSpanProcessor(this._tracerProviderConfig.spanProcessor); @@ -164,7 +167,7 @@ export class NodeSDK { if (this._meterProviderConfig) { const meterProvider = new MeterProvider({ ...this._meterProviderConfig, - resource: this._resource, + resource, }); metrics.setGlobalMeterProvider(meterProvider); diff --git a/packages/opentelemetry-sdk-node/test/sdk.test.ts b/packages/opentelemetry-sdk-node/test/sdk.test.ts index 34598a2cbaa..90a4f92b0b6 100644 --- a/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -14,72 +14,30 @@ * limitations under the License. */ -import * as nock from 'nock'; -import * as semver from 'semver'; import { context, metrics, - NoopTextMapPropagator, NoopMeterProvider, + NoopTextMapPropagator, NoopTracerProvider, propagation, - trace, ProxyTracerProvider, + trace, } from '@opentelemetry/api'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; import { NoopContextManager } from '@opentelemetry/context-base'; import { CompositePropagator } from '@opentelemetry/core'; import { ConsoleMetricExporter, MeterProvider } from '@opentelemetry/metrics'; import { NodeTracerProvider } from '@opentelemetry/node'; +import * as NodeConfig from '@opentelemetry/node/build/src/config'; import { ConsoleSpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; import * as assert from 'assert'; -import { NodeSDK } from '../src'; -import * as NodeConfig from '@opentelemetry/node/build/src/config'; +import * as nock from 'nock'; import * as Sinon from 'sinon'; -import { awsEc2Detector } from '@opentelemetry/resource-detector-aws'; -import { resetIsAvailableCache } from '@opentelemetry/resource-detector-gcp'; -import { - assertServiceResource, - assertCloudResource, - assertHostResource, -} from '@opentelemetry/resources/test/util/resource-assertions'; -import { - BASE_PATH, - HEADER_NAME, - HEADER_VALUE, - HOST_ADDRESS, - SECONDARY_HOST_ADDRESS, -} from 'gcp-metadata'; -import { Resource } from '@opentelemetry/resources'; - -const HEADERS = { - [HEADER_NAME.toLowerCase()]: HEADER_VALUE, -}; -const INSTANCE_PATH = BASE_PATH + '/instance'; -const INSTANCE_ID_PATH = BASE_PATH + '/instance/id'; -const PROJECT_ID_PATH = BASE_PATH + '/project/project-id'; -const ZONE_PATH = BASE_PATH + '/instance/zone'; -const CLUSTER_NAME_PATH = BASE_PATH + '/instance/attributes/cluster-name'; - -const AWS_HOST = 'http://' + awsEc2Detector.AWS_IDMS_ENDPOINT; -const AWS_TOKEN_PATH = awsEc2Detector.AWS_INSTANCE_TOKEN_DOCUMENT_PATH; -const AWS_IDENTITY_PATH = awsEc2Detector.AWS_INSTANCE_IDENTITY_DOCUMENT_PATH; -const AWS_HOST_PATH = awsEc2Detector.AWS_INSTANCE_HOST_DOCUMENT_PATH; -const AWS_METADATA_TTL_HEADER = awsEc2Detector.AWS_METADATA_TTL_HEADER; -const AWS_METADATA_TOKEN_HEADER = awsEc2Detector.AWS_METADATA_TOKEN_HEADER; - -const mockedTokenResponse = 'my-token'; -const mockedIdentityResponse = { - instanceId: 'my-instance-id', - instanceType: 'my-instance-type', - accountId: 'my-account-id', - region: 'my-region', - availabilityZone: 'my-zone', -}; -const mockedHostResponse = 'my-hostname'; +import { NodeSDK } from '../src'; describe('Node SDK', () => { before(() => { @@ -183,270 +141,4 @@ describe('Node SDK', () => { assert.ok(metrics.getMeterProvider() instanceof MeterProvider); }); }); - - describe('detectResources', async () => { - beforeEach(() => { - nock.disableNetConnect(); - process.env.OTEL_RESOURCE_ATTRIBUTES = - 'service.instance.id=627cc493,service.name=my-service,service.namespace=default,service.version=0.0.1'; - }); - - afterEach(() => { - nock.cleanAll(); - nock.enableNetConnect(); - delete process.env.OTEL_RESOURCE_ATTRIBUTES; - }); - - // GCP detector only works in 10+ - (semver.satisfies(process.version, '>=10') ? describe : describe.skip)( - 'in GCP environment', - () => { - after(() => { - resetIsAvailableCache(); - }); - - it('returns a merged resource', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - const gcpScope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .reply(200, () => 452003179927758, HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(200, () => 'project/zone/my-zone', HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(404); - const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const awsScope = nock(AWS_HOST) - .persist() - .put(AWS_TOKEN_PATH) - .matchHeader(AWS_METADATA_TTL_HEADER, '60') - .replyWithError({ code: 'ENOTFOUND' }); - await sdk.detectResources(); - const resource = sdk['_resource']; - - awsScope.done(); - gcpSecondaryScope.done(); - gcpScope.done(); - - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', - }); - assertHostResource(resource, { id: '452003179927758' }); - assertServiceResource(resource, { - instanceId: '627cc493', - name: 'my-service', - namespace: 'default', - version: '0.0.1', - }); - }); - } - ); - - describe('in AWS environment', () => { - it('returns a merged resource', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - const awsScope = nock(AWS_HOST) - .persist() - .put(AWS_TOKEN_PATH) - .matchHeader(AWS_METADATA_TTL_HEADER, '60') - .reply(200, () => mockedTokenResponse) - .get(AWS_IDENTITY_PATH) - .matchHeader(AWS_METADATA_TOKEN_HEADER, mockedTokenResponse) - .reply(200, () => mockedIdentityResponse) - .get(AWS_HOST_PATH) - .matchHeader(AWS_METADATA_TOKEN_HEADER, mockedTokenResponse) - .reply(200, () => mockedHostResponse); - await sdk.detectResources(); - const resource: Resource = sdk['_resource']; - awsScope.done(); - - assertCloudResource(resource, { - provider: 'aws', - accountId: 'my-account-id', - region: 'my-region', - zone: 'my-zone', - }); - assertHostResource(resource, { - id: 'my-instance-id', - hostType: 'my-instance-type', - name: 'my-hostname', - hostName: 'my-hostname', - }); - assertServiceResource(resource, { - instanceId: '627cc493', - name: 'my-service', - namespace: 'default', - version: '0.0.1', - }); - }); - }); - - describe('in no environment', () => { - it('should return empty resource', async () => { - const scope = nock(AWS_HOST).put(AWS_TOKEN_PATH).replyWithError({ - code: 'ENOTFOUND', - }); - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - await sdk.detectResources({ - detectors: [awsEc2Detector], - }); - const resource: Resource = sdk['_resource']; - assert.ok(resource); - assert.deepStrictEqual(resource, Resource.createTelemetrySDKResource()); - - scope.done(); - }); - }); - - describe('with a buggy detector', () => { - it('returns a merged resource', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - const stub = Sinon.stub(awsEc2Detector, 'detect').throws(); - await sdk.detectResources(); - const resource = sdk['_resource']; - - assertServiceResource(resource, { - instanceId: '627cc493', - name: 'my-service', - namespace: 'default', - version: '0.0.1', - }); - - stub.restore(); - }); - }); - - describe('with a debug logger', () => { - // Local functions to test if a mocked method is ever called with a specific argument or regex matching for an argument. - // Needed because of race condition with parallel detectors. - const callArgsContains = ( - mockedFunction: sinon.SinonSpy, - arg: any - ): boolean => { - return mockedFunction.getCalls().some(call => { - return call.args.some(callarg => arg === callarg); - }); - }; - const callArgsMatches = ( - mockedFunction: sinon.SinonSpy, - regex: RegExp - ): boolean => { - return mockedFunction.getCalls().some(call => { - return regex.test(call.args.toString()); - }); - }; - - it('prints detected resources and debug messages to the logger', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - // This test depends on the env detector to be functioning as intended - const mockedLoggerMethod = Sinon.fake(); - await sdk.detectResources({ - logger: { - debug: mockedLoggerMethod, - info: Sinon.fake(), - warn: Sinon.fake(), - error: Sinon.fake(), - }, - }); - - // Test for AWS and GCP Detector failure - assert.ok( - callArgsContains( - mockedLoggerMethod, - 'GcpDetector failed: GCP Metadata unavailable.' - ) - ); - assert.ok( - callArgsContains( - mockedLoggerMethod, - 'AwsEc2Detector failed: Nock: Disallowed net connect for "169.254.169.254:80/latest/api/token"' - ) - ); - // Test that the Env Detector successfully found its resource and populated it with the right values. - assert.ok( - callArgsContains(mockedLoggerMethod, 'EnvDetector found resource.') - ); - // Regex formatting accounts for whitespace variations in util.inspect output over different node versions - assert.ok( - callArgsMatches( - mockedLoggerMethod, - /{\s+'service\.instance\.id':\s+'627cc493',\s+'service\.name':\s+'my-service',\s+'service\.namespace':\s+'default',\s+'service\.version':\s+'0\.0\.1'\s+}\s*/ - ) - ); - }); - - describe('with missing environment variable', () => { - beforeEach(() => { - delete process.env.OTEL_RESOURCE_ATTRIBUTES; - }); - - it('prints correct error messages when EnvDetector has no env variable', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - const mockedLoggerMethod = Sinon.fake(); - await sdk.detectResources({ - logger: { - debug: mockedLoggerMethod, - info: Sinon.fake(), - warn: Sinon.fake(), - error: Sinon.fake(), - }, - }); - - assert.ok( - callArgsContains( - mockedLoggerMethod, - 'EnvDetector failed: Environment variable "OTEL_RESOURCE_ATTRIBUTES" is missing.' - ) - ); - }); - }); - - describe('with a faulty environment variable', () => { - beforeEach(() => { - process.env.OTEL_RESOURCE_ATTRIBUTES = 'bad=~attribute'; - }); - - it('prints correct error messages when EnvDetector has an invalid variable', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, - }); - const mockedLoggerMethod = Sinon.fake(); - await sdk.detectResources({ - logger: { - debug: mockedLoggerMethod, - info: Sinon.fake(), - warn: Sinon.fake(), - error: Sinon.fake(), - }, - }); - - assert.ok( - callArgsContains( - mockedLoggerMethod, - 'EnvDetector failed: Attribute value should be a ASCII string with a length not exceed 255 characters.' - ) - ); - }); - }); - }); - }); }); diff --git a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts index 065e74b2b89..4132ba2b356 100644 --- a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts +++ b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts @@ -40,11 +40,13 @@ export class BasicTracerProvider implements api.TracerProvider { activeSpanProcessor = new NoopSpanProcessor(); readonly logger: api.Logger; - readonly resource: Resource; + readonly resource: Promise; constructor(config: TracerConfig = DEFAULT_CONFIG) { this.logger = config.logger ?? new ConsoleLogger(config.logLevel); - this.resource = config.resource ?? Resource.createTelemetrySDKResource(); + this.resource = Promise.resolve( + config.resource ?? Resource.createTelemetrySDKResource() + ); this._config = Object.assign({}, config, { logger: this.logger, resource: this.resource, diff --git a/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts b/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts index 97341a277a0..0809cb5d103 100644 --- a/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts @@ -50,15 +50,8 @@ export class MultiSpanProcessor implements SpanProcessor { } shutdown(): Promise { - const promises: Promise[] = []; - - for (const spanProcessor of this._spanProcessors) { - promises.push(spanProcessor.shutdown()); - } - return new Promise((resolve, reject) => { - Promise.all(promises).then(() => { - resolve(); - }, reject); - }); + return Promise.all( + this._spanProcessors.map(processor => processor.shutdown()) + ).then(() => {}); } } diff --git a/packages/opentelemetry-tracing/src/Span.ts b/packages/opentelemetry-tracing/src/Span.ts index b8117b0195e..917eebd3bd0 100644 --- a/packages/opentelemetry-tracing/src/Span.ts +++ b/packages/opentelemetry-tracing/src/Span.ts @@ -35,7 +35,7 @@ import { TraceParams } from './types'; /** * This class represents a span. */ -export class Span implements api.Span, ReadableSpan { +export class Span implements api.Span { // Below properties are included to implement ReadableSpan for export // purposes but are not intended to be written-to directly. readonly spanContext: api.SpanContext; @@ -45,7 +45,7 @@ export class Span implements api.Span, ReadableSpan { readonly links: api.Link[] = []; readonly events: api.TimedEvent[] = []; readonly startTime: api.HrTime; - readonly resource: Resource; + readonly resource: Promise; readonly instrumentationLibrary: InstrumentationLibrary; name: string; status: api.Status = { @@ -79,7 +79,9 @@ export class Span implements api.Span, ReadableSpan { this._logger = parentTracer.logger; this._traceParams = parentTracer.getActiveTraceParams(); this._spanProcessor = parentTracer.getActiveSpanProcessor(); - this._spanProcessor.onStart(this); + this.toReadableSpan().then(readableSpan => + this._spanProcessor.onStart(readableSpan) + ); } context(): api.SpanContext { @@ -175,7 +177,9 @@ export class Span implements api.Span, ReadableSpan { ); } - this._spanProcessor.onEnd(this); + this.toReadableSpan().then(readableSpan => + this._spanProcessor.onEnd(readableSpan) + ); } isRecording(): boolean { @@ -219,6 +223,25 @@ export class Span implements api.Span, ReadableSpan { return this._ended; } + public toReadableSpan(): Promise { + return this.resource.then(resource => ({ + attributes: this.attributes, + duration: this.duration, + endTime: this.endTime, + ended: this.ended, + events: this.events, + instrumentationLibrary: this.instrumentationLibrary, + kind: this.kind, + links: this.links, + name: this.name, + resource, + spanContext: this.spanContext, + startTime: this.startTime, + status: this.status, + parentSpanId: this.parentSpanId, + })); + } + private _isSpanEnded(): boolean { if (this._ended) { this._logger.warn( diff --git a/packages/opentelemetry-tracing/src/Tracer.ts b/packages/opentelemetry-tracing/src/Tracer.ts index 4a44d73599c..3b8579f8542 100644 --- a/packages/opentelemetry-tracing/src/Tracer.ts +++ b/packages/opentelemetry-tracing/src/Tracer.ts @@ -39,7 +39,7 @@ export class Tracer implements api.Tracer { private readonly _sampler: api.Sampler; private readonly _traceParams: TraceParams; private readonly _idGenerator: IdGenerator; - readonly resource: Resource; + readonly resource: Promise; readonly instrumentationLibrary: InstrumentationLibrary; readonly logger: api.Logger; diff --git a/packages/opentelemetry-tracing/src/types.ts b/packages/opentelemetry-tracing/src/types.ts index c90502006ed..06f180afa81 100644 --- a/packages/opentelemetry-tracing/src/types.ts +++ b/packages/opentelemetry-tracing/src/types.ts @@ -41,7 +41,7 @@ export interface TracerConfig { traceParams?: TraceParams; /** Resource associated with trace telemetry */ - resource?: Resource; + resource?: Resource | Promise; /** Bool for whether or not graceful shutdown is enabled. If disabled spans will not be exported when SIGTERM is recieved */ gracefulShutdown?: boolean; diff --git a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts index 2e95fce0203..e956953655f 100644 --- a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts @@ -316,11 +316,12 @@ describe('BasicTracerProvider', () => { assert.strictEqual(span.isRecording(), true); }); - it('should assign a resource', () => { + it('should assign a resource', async () => { const tracer = new BasicTracerProvider().getTracer('default'); const span = tracer.startSpan('my-span') as Span; assert.ok(span); - assert.ok(span.resource instanceof Resource); + const resource = await span.toReadableSpan().then(s => s.resource); + assert.ok(resource instanceof Resource); }); }); @@ -362,9 +363,10 @@ describe('BasicTracerProvider', () => { }); describe('.resource', () => { - it('should return a Resource', () => { + it('should return a Resource', async () => { const tracerProvider = new BasicTracerProvider(); - assert.ok(tracerProvider.resource instanceof Resource); + const resource = await tracerProvider.resource; + assert.ok(resource instanceof Resource); }); }); diff --git a/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts index c0e4c58d7da..e702804baeb 100644 --- a/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts @@ -20,7 +20,7 @@ import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor, - Span, + ReadableSpan, SpanProcessor, } from '../src'; import { @@ -30,9 +30,9 @@ import { import { MultiSpanProcessor } from '../src/MultiSpanProcessor'; class TestProcessor implements SpanProcessor { - spans: Span[] = []; - onStart(span: Span): void {} - onEnd(span: Span): void { + spans: ReadableSpan[] = []; + onStart(span: ReadableSpan): void {} + onEnd(span: ReadableSpan): void { this.spans.push(span); } shutdown(): Promise { @@ -53,7 +53,7 @@ describe('MultiSpanProcessor', () => { } }); - it('should handle empty span processor', () => { + it('should handle empty span processor', async () => { const multiSpanProcessor = new MultiSpanProcessor([]); const tracerProvider = new BasicTracerProvider(); @@ -61,10 +61,10 @@ describe('MultiSpanProcessor', () => { const tracer = tracerProvider.getTracer('default'); const span = tracer.startSpan('one'); span.end(); - multiSpanProcessor.shutdown(); + await multiSpanProcessor.shutdown(); }); - it('should handle one span processor', () => { + it('should handle one span processor', async () => { const processor1 = new TestProcessor(); const multiSpanProcessor = new MultiSpanProcessor([processor1]); @@ -74,8 +74,9 @@ describe('MultiSpanProcessor', () => { const span = tracer.startSpan('one'); assert.strictEqual(processor1.spans.length, 0); span.end(); + await new Promise(resolve => setTimeout(resolve)); assert.strictEqual(processor1.spans.length, 1); - multiSpanProcessor.shutdown(); + await multiSpanProcessor.shutdown(); }); it('should handle two span processor', async () => { @@ -91,15 +92,16 @@ describe('MultiSpanProcessor', () => { assert.strictEqual(processor1.spans.length, processor2.spans.length); span.end(); + await new Promise(resolve => setTimeout(resolve)); assert.strictEqual(processor1.spans.length, 1); - assert.strictEqual(processor1.spans.length, processor2.spans.length); + assert.strictEqual(processor2.spans.length, 1); await multiSpanProcessor.shutdown(); assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); + assert.strictEqual(processor2.spans.length, 0); }); - it('should export spans on graceful shutdown from two span processor', () => { + it('should export spans on graceful shutdown from two span processors', done => { const processor1 = new TestProcessor(); const processor2 = new TestProcessor(); const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]); @@ -109,87 +111,40 @@ describe('MultiSpanProcessor', () => { const tracer = tracerProvider.getTracer('default'); const span = tracer.startSpan('one'); assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); + assert.strictEqual(processor2.spans.length, 0); span.end(); - assert.strictEqual(processor1.spans.length, 1); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - - removeEvent = notifyOnGlobalShutdown(() => { - assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); + setTimeout(() => { + assert.strictEqual(processor1.spans.length, 1); + assert.strictEqual(processor2.spans.length, 1); + + removeEvent = notifyOnGlobalShutdown(() => { + assert.strictEqual(processor1.spans.length, 0); + assert.strictEqual(processor2.spans.length, 0); + done(); + }); + _invokeGlobalShutdown(); }); - _invokeGlobalShutdown(); }); - it('should export spans on manual shutdown from two span processor', () => { + it('should export spans on manual shutdown from two span processor', async () => { const processor1 = new TestProcessor(); const processor2 = new TestProcessor(); - const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]); const tracerProvider = new BasicTracerProvider(); - tracerProvider.addSpanProcessor(multiSpanProcessor); - const tracer = tracerProvider.getTracer('default'); - const span = tracer.startSpan('one'); - assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - - span.end(); - assert.strictEqual(processor1.spans.length, 1); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - - tracerProvider.shutdown().then(() => { - assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - }); - }); - it('should export spans on graceful shutdown from two span processor', () => { - const processor1 = new TestProcessor(); - const processor2 = new TestProcessor(); - const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]); + tracerProvider.addSpanProcessor(processor1); + tracerProvider.addSpanProcessor(processor2); - const tracerProvider = new BasicTracerProvider(); - tracerProvider.addSpanProcessor(multiSpanProcessor); const tracer = tracerProvider.getTracer('default'); - const span = tracer.startSpan('one'); - assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); + tracer.startSpan('one'); - span.end(); - assert.strictEqual(processor1.spans.length, 1); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - - removeEvent = notifyOnGlobalShutdown(() => { - assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - }); - _invokeGlobalShutdown(); - }); - - it('should export spans on manual shutdown from two span processor', () => { - const processor1 = new TestProcessor(); - const processor2 = new TestProcessor(); - const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]); - - const tracerProvider = new BasicTracerProvider(); - tracerProvider.addSpanProcessor(multiSpanProcessor); - const tracer = tracerProvider.getTracer('default'); - const span = tracer.startSpan('one'); + await tracerProvider.shutdown(); assert.strictEqual(processor1.spans.length, 0); assert.strictEqual(processor1.spans.length, processor2.spans.length); - - span.end(); - assert.strictEqual(processor1.spans.length, 1); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - - tracerProvider.shutdown().then(() => { - assert.strictEqual(processor1.spans.length, 0); - assert.strictEqual(processor1.spans.length, processor2.spans.length); - }); }); - it('should force span processors to flush', () => { + it('should force span processors to flush', async () => { let flushed = false; const processor: SpanProcessor = { forceFlush: () => { @@ -203,7 +158,7 @@ describe('MultiSpanProcessor', () => { }, }; const multiSpanProcessor = new MultiSpanProcessor([processor]); - multiSpanProcessor.forceFlush(); + await multiSpanProcessor.forceFlush(); assert.ok(flushed); }); diff --git a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts index 4b0f6590931..cc6ccb6c8f8 100644 --- a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts @@ -77,14 +77,15 @@ describe('BatchSpanProcessor', () => { const spy: sinon.SinonSpy = sinon.spy(exporter, 'export') as any; const span = createSampledSpan(`${name}_0`); + const readableSpan = await span.toReadableSpan(); - processor.onEnd(span); + processor.onEnd(readableSpan); assert.strictEqual(processor['_finishedSpans'].length, 1); await processor.forceFlush(); assert.strictEqual(exporter.getFinishedSpans().length, 1); - processor.onEnd(span); + processor.onEnd(readableSpan); assert.strictEqual(processor['_finishedSpans'].length, 1); assert.strictEqual(spy.args.length, 1); @@ -92,7 +93,7 @@ describe('BatchSpanProcessor', () => { assert.strictEqual(spy.args.length, 2); assert.strictEqual(exporter.getFinishedSpans().length, 0); - processor.onEnd(span); + processor.onEnd(readableSpan); assert.strictEqual(spy.args.length, 2); assert.strictEqual(processor['_finishedSpans'].length, 0); assert.strictEqual(exporter.getFinishedSpans().length, 0); @@ -102,52 +103,58 @@ describe('BatchSpanProcessor', () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.bufferSize; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onStart(span); + const readableSpan = await span.toReadableSpan(); + processor.onStart(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 0); - processor.onEnd(span); + processor.onEnd(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 0); } // Now we should start seeing the spans in exporter const span = createSampledSpan(`${name}_6`); - processor.onEnd(span); + const readableSpan = await span.toReadableSpan(); + processor.onEnd(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 6); await processor.shutdown(); assert.strictEqual(exporter.getFinishedSpans().length, 0); }); - it('should force flush when timeout exceeded', done => { + it('should force flush when timeout exceeded', async () => { const clock = sinon.useFakeTimers(); const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.bufferSize; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onEnd(span); + const readableSpan = await span.toReadableSpan(); + processor.onEnd(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 0); } - setTimeout(() => { - assert.strictEqual(exporter.getFinishedSpans().length, 5); - done(); - }, defaultBufferConfig.bufferTimeout + 1000); + await new Promise(resolve => { + setTimeout(() => { + assert.strictEqual(exporter.getFinishedSpans().length, 5); + resolve(); + }, defaultBufferConfig.bufferTimeout + 1000); - clock.tick(defaultBufferConfig.bufferTimeout + 1000); + clock.tick(defaultBufferConfig.bufferTimeout + 1000); + }); clock.restore(); }); - it('should force flush on demand', () => { + it('should force flush on demand', async () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.bufferSize; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onEnd(span); + const readableSpan = await span.toReadableSpan(); + processor.onEnd(readableSpan); } assert.strictEqual(exporter.getFinishedSpans().length, 0); processor.forceFlush(); assert.strictEqual(exporter.getFinishedSpans().length, 5); }); - it('should not export empty span lists', done => { + it('should not export empty span lists', async () => { const spy = sinon.spy(exporter, 'export'); const clock = sinon.useFakeTimers(); @@ -159,20 +166,23 @@ describe('BatchSpanProcessor', () => { // start but do not end spans for (let i = 0; i < defaultBufferConfig.bufferSize; i++) { const span = tracer.startSpan('spanName'); - processor.onStart(span as Span); + const readableSpan = await (span as Span).toReadableSpan(); + processor.onStart(readableSpan); } - setTimeout(() => { - assert.strictEqual(exporter.getFinishedSpans().length, 0); - // after the timeout, export should not have been called - // because no spans are ended - sinon.assert.notCalled(spy); - done(); - }, defaultBufferConfig.bufferTimeout + 1000); + await new Promise(resolve => { + setTimeout(() => { + assert.strictEqual(exporter.getFinishedSpans().length, 0); + // after the timeout, export should not have been called + // because no spans are ended + sinon.assert.notCalled(spy); + resolve(); + }, defaultBufferConfig.bufferTimeout + 1000); - // no spans have been finished - assert.strictEqual(exporter.getFinishedSpans().length, 0); - clock.tick(defaultBufferConfig.bufferTimeout + 1000); + // no spans have been finished + assert.strictEqual(exporter.getFinishedSpans().length, 0); + clock.tick(defaultBufferConfig.bufferTimeout + 1000); + }); clock.restore(); }); @@ -198,11 +208,13 @@ describe('BatchSpanProcessor', () => { describe('spans waiting to flush', () => { let processor: BatchSpanProcessor; - beforeEach(() => { + beforeEach(async () => { processor = new BatchSpanProcessor(exporter); const span = createSampledSpan('test'); - processor.onStart(span); - processor.onEnd(span); + const readableSpan = await span.toReadableSpan(); + + processor.onStart(readableSpan); + processor.onEnd(readableSpan); assert.strictEqual(processor['_finishedSpans'].length, 1); }); @@ -240,20 +252,18 @@ describe('BatchSpanProcessor', () => { context.disable(); }); - it('should prevent instrumentation prior to export', done => { + it('should prevent instrumentation prior to export', async () => { const testTracingExporter = new TestTracingSpanExporter(); const processor = new BatchSpanProcessor(testTracingExporter); const span = createSampledSpan('test'); - processor.onStart(span); - processor.onEnd(span); - - processor.forceFlush().then(() => { - const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); - assert.equal(exporterCreatedSpans.length, 0); + const readableSpan = await span.toReadableSpan(); + processor.onStart(readableSpan); + processor.onEnd(readableSpan); - done(); - }); + await processor.forceFlush(); + const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); + assert.equal(exporterCreatedSpans.length, 0); }); }); }); diff --git a/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts b/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts index 4a4829ec3a4..873e19e86c7 100644 --- a/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts +++ b/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts @@ -37,7 +37,7 @@ describe('ConsoleSpanExporter', () => { }); describe('.export()', () => { - it('should export information about span', () => { + it('should export information about span', done => { assert.doesNotThrow(() => { const basicTracerProvider = new BasicTracerProvider(); consoleExporter = new ConsoleSpanExporter(); @@ -53,32 +53,35 @@ describe('ConsoleSpanExporter', () => { span.addEvent('foobar'); span.end(); - const spans = spyExport.args[0]; - const firstSpan = spans[0][0]; - const firstEvent = firstSpan.events[0]; - const consoleArgs = spyConsole.args[0]; - const consoleSpan = consoleArgs[0]; - const keys = Object.keys(consoleSpan).sort().join(','); + setTimeout(() => { + const spans = spyExport.args[0]; + const firstSpan = spans[0][0]; + const firstEvent = firstSpan.events[0]; + const consoleArgs = spyConsole.args[0]; + const consoleSpan = consoleArgs[0]; + const keys = Object.keys(consoleSpan).sort().join(','); - const expectedKeys = [ - 'attributes', - 'duration', - 'events', - 'id', - 'kind', - 'name', - 'parentId', - 'status', - 'timestamp', - 'traceId', - ].join(','); + const expectedKeys = [ + 'attributes', + 'duration', + 'events', + 'id', + 'kind', + 'name', + 'parentId', + 'status', + 'timestamp', + 'traceId', + ].join(','); - assert.ok(firstSpan.name === 'foo'); - assert.ok(firstEvent.name === 'foobar'); - assert.ok(consoleSpan.id === firstSpan.spanContext.spanId); - assert.ok(keys === expectedKeys); + assert.ok(firstSpan.name === 'foo'); + assert.ok(firstEvent.name === 'foobar'); + assert.ok(consoleSpan.id === firstSpan.spanContext.spanId); + assert.ok(keys === expectedKeys); - assert.ok(spyExport.calledOnce); + assert.ok(spyExport.calledOnce); + done(); + }, 10); }); }); }); diff --git a/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts b/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts index b8c05e4a0fe..adc5fc75ccd 100644 --- a/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts +++ b/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts @@ -33,7 +33,7 @@ describe('InMemorySpanExporter', () => { provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); }); - it('should get finished spans', () => { + it('should get finished spans', done => { const root = provider.getTracer('default').startSpan('root'); const child = provider .getTracer('default') @@ -44,23 +44,36 @@ describe('InMemorySpanExporter', () => { assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); grandChild.end(); - assert.strictEqual(memoryExporter.getFinishedSpans().length, 1); - child.end(); - assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); - root.end(); - assert.strictEqual(memoryExporter.getFinishedSpans().length, 3); + setTimeout(() => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 1); + child.end(); + setTimeout(() => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); + root.end(); + setTimeout(() => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 3); - const [span1, span2, span3] = memoryExporter.getFinishedSpans(); - assert.strictEqual(span1.name, 'grand-child'); - assert.strictEqual(span2.name, 'child'); - assert.strictEqual(span3.name, 'root'); - assert.strictEqual(span1.spanContext.traceId, span2.spanContext.traceId); - assert.strictEqual(span2.spanContext.traceId, span3.spanContext.traceId); - assert.strictEqual(span1.parentSpanId, span2.spanContext.spanId); - assert.strictEqual(span2.parentSpanId, span3.spanContext.spanId); + const [span1, span2, span3] = memoryExporter.getFinishedSpans(); + assert.strictEqual(span1.name, 'grand-child'); + assert.strictEqual(span2.name, 'child'); + assert.strictEqual(span3.name, 'root'); + assert.strictEqual( + span1.spanContext.traceId, + span2.spanContext.traceId + ); + assert.strictEqual( + span2.spanContext.traceId, + span3.spanContext.traceId + ); + assert.strictEqual(span1.parentSpanId, span2.spanContext.spanId); + assert.strictEqual(span2.parentSpanId, span3.spanContext.spanId); + done(); + }, 10); + }, 10); + }, 10); }); - it('should shutdown the exporter', () => { + it('should shutdown the exporter', done => { const root = provider.getTracer('default').startSpan('root'); provider @@ -68,32 +81,39 @@ describe('InMemorySpanExporter', () => { .startSpan('child', {}, setActiveSpan(context.active(), root)) .end(); root.end(); - assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); - memoryExporter.shutdown(); - assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + setTimeout(() => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); + memoryExporter.shutdown(); + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); - // after shutdown no new spans are accepted - provider - .getTracer('default') - .startSpan('child1', {}, setActiveSpan(context.active(), root)) - .end(); - assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + // after shutdown no new spans are accepted + provider + .getTracer('default') + .startSpan('child1', {}, setActiveSpan(context.active(), root)) + .end(); + setTimeout(() => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + done(); + }, 10); + }, 10); }); - it('should return the success result', () => { - const exorter = new InMemorySpanExporter(); - exorter.export([], (result: ExportResult) => { + it('should return the success result', done => { + const exporter = new InMemorySpanExporter(); + exporter.export([], (result: ExportResult) => { assert.strictEqual(result, ExportResult.SUCCESS); + done(); }); }); - it('should return the FailedNotRetryable result after shutdown', () => { - const exorter = new InMemorySpanExporter(); - exorter.shutdown(); + it('should return the FailedNotRetryable result after shutdown', done => { + const exporter = new InMemorySpanExporter(); + exporter.shutdown(); // after shutdown export should fail - exorter.export([], (result: ExportResult) => { + exporter.export([], (result: ExportResult) => { assert.strictEqual(result, ExportResult.FAILED_NOT_RETRYABLE); + done(); }); }); }); diff --git a/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts index c62799428a1..d402e816897 100644 --- a/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts @@ -36,7 +36,7 @@ describe('SimpleSpanProcessor', () => { }); }); - describe('.onStart/.onEnd/.shutdown', () => { + describe('.onStart/.onEnd/.shutdown', async () => { it('should handle span started and ended when SAMPLED', async () => { const processor = new SimpleSpanProcessor(exporter); const spanContext: SpanContext = { @@ -50,10 +50,12 @@ describe('SimpleSpanProcessor', () => { spanContext, SpanKind.CLIENT ); - processor.onStart(span); + const readableSpan = await span.toReadableSpan(); + + processor.onStart(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 0); - processor.onEnd(span); + processor.onEnd(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 1); await processor.shutdown(); @@ -73,10 +75,11 @@ describe('SimpleSpanProcessor', () => { spanContext, SpanKind.CLIENT ); - processor.onStart(span); + const readableSpan = await span.toReadableSpan(); + processor.onStart(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 0); - processor.onEnd(span); + processor.onEnd(readableSpan); assert.strictEqual(exporter.getFinishedSpans().length, 0); await processor.shutdown(); @@ -114,7 +117,7 @@ describe('SimpleSpanProcessor', () => { context.disable(); }); - it('should prevent instrumentation prior to export', () => { + it('should prevent instrumentation prior to export', async () => { const testTracingExporter = new TestTracingSpanExporter(); const processor = new SimpleSpanProcessor(testTracingExporter); @@ -129,8 +132,9 @@ describe('SimpleSpanProcessor', () => { spanContext, SpanKind.CLIENT ); + const readableSpan = await span.toReadableSpan(); - processor.onEnd(span); + processor.onEnd(readableSpan); const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); assert.equal(exporterCreatedSpans.length, 0);