From 9cd8b6749b1cc91cfa0663308a4183fcf93bc70a Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 18 Aug 2020 01:36:14 +0200 Subject: [PATCH 1/7] chore: fixing and unifying the shutdown across all exporters, metric, spans, processors, so that the shutdown will be correctly handle in whole pipeline --- .../src/CollectorExporterBase.ts | 25 ++++-- .../browser/CollectorExporterBrowserBase.ts | 45 +++++++--- .../node/CollectorExporterNodeBase.ts | 34 ++++++-- .../src/jaeger.ts | 45 ++++++++-- .../src/prometheus.ts | 10 ++- .../test/prometheus.test.ts | 84 ++++++++++++++++--- .../src/zipkin.ts | 21 +++-- packages/opentelemetry-metrics/package.json | 2 +- packages/opentelemetry-metrics/src/Meter.ts | 25 +++++- .../src/MeterProvider.ts | 44 +++++++--- .../src/export/ConsoleMetricExporter.ts | 3 +- .../src/export/Controller.ts | 4 +- .../src/export/NoopExporter.ts | 4 +- .../opentelemetry-metrics/src/export/types.ts | 2 +- .../test/MeterProvider.test.ts | 8 +- .../test/fetch.test.ts | 4 +- .../test/xhr.test.ts | 4 +- .../src/BasicTracerProvider.ts | 6 +- .../src/MultiSpanProcessor.ts | 34 ++++---- .../src/NoopSpanProcessor.ts | 8 +- .../src/SpanProcessor.ts | 4 +- .../src/export/BatchSpanProcessor.ts | 50 +++++++---- .../src/export/ConsoleSpanExporter.ts | 5 +- .../src/export/InMemorySpanExporter.ts | 3 +- .../src/export/SimpleSpanProcessor.ts | 25 ++++-- .../src/export/SpanExporter.ts | 2 +- .../test/MultiSpanProcessor.test.ts | 73 +++++++++++++--- .../test/export/BatchSpanProcessor.test.ts | 18 ++-- .../test/export/SimpleSpanProcessor.test.ts | 12 +-- 29 files changed, 446 insertions(+), 158 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts index 1c6ff54f54..a0237eb056 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts @@ -36,6 +36,8 @@ export abstract class CollectorExporterBase< public readonly hostname: string | undefined; public readonly attributes?: Attributes; protected _isShutdown: boolean = false; + private _shuttingDownPromise: Promise = Promise.resolve(); + protected _sendingPromises: Promise[] = []; /** * @param config @@ -98,16 +100,29 @@ export abstract class CollectorExporterBase< /** * Shutdown the exporter. */ - shutdown(): void { + shutdown(): Promise { if (this._isShutdown) { this.logger.debug('shutdown already started'); - return; + return this._shuttingDownPromise; } this._isShutdown = true; this.logger.debug('shutdown started'); - - // platform dependent - this.onShutdown(); + this._shuttingDownPromise = new Promise((resolve, reject) => { + Promise.resolve() + .then(() => { + return this.onShutdown(); + }) + .then(() => { + return Promise.all(this._sendingPromises); + }) + .then(() => { + resolve(); + }) + .catch(e => { + reject(e); + }); + }); + return this._shuttingDownPromise; } abstract onShutdown(): void; diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts index ab35cd98ac..80e7fec598 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts @@ -61,20 +61,41 @@ export abstract class CollectorExporterBrowserBase< onSuccess: () => void, onError: (error: collectorTypes.CollectorExporterError) => void ) { + if (this._isShutdown) { + this.logger.debug('Shutdown already started. Cannot send objects'); + return; + } const serviceRequest = this.convert(items); const body = JSON.stringify(serviceRequest); - if (this._useXHR) { - sendWithXhr( - body, - this.url, - this._headers, - this.logger, - onSuccess, - onError - ); - } else { - sendWithBeacon(body, this.url, this.logger, onSuccess, onError); - } + const promise = new Promise(resolve => { + const _onSuccess = (): void => { + onSuccess(); + _onFinish(); + }; + const _onError = (error: collectorTypes.CollectorExporterError): void => { + onError(error); + _onFinish(); + }; + const _onFinish = () => { + const index = this._sendingPromises.indexOf(promise); + this._sendingPromises.splice(index, 1); + resolve(); + }; + + if (this._useXHR) { + sendWithXhr( + body, + this.url, + this._headers, + this.logger, + _onSuccess, + _onError + ); + } else { + sendWithBeacon(body, this.url, this.logger, _onSuccess, _onError); + } + }); + this._sendingPromises.push(promise); } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts index 743fbad4de..ddc1a51f44 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts @@ -95,13 +95,32 @@ export abstract class CollectorExporterNodeBase< this.logger.debug('Shutdown already started. Cannot send objects'); return; } - if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - sendWithJson(this, objects, onSuccess, onError); - } else if (this._protocol === CollectorProtocolNode.HTTP_PROTO) { - sendWithJsonProto(this, objects, onSuccess, onError); - } else { - sendWithGrpc(this, objects, onSuccess, onError); - } + + const promise = new Promise(resolve => { + const _onSuccess = (): void => { + onSuccess(); + _onFinish(); + }; + const _onError = (error: collectorTypes.CollectorExporterError): void => { + onError(error); + _onFinish(); + }; + const _onFinish = () => { + const index = this._sendingPromises.indexOf(promise); + this._sendingPromises.splice(index, 1); + resolve(); + }; + + if (this._protocol === CollectorProtocolNode.HTTP_JSON) { + sendWithJson(this, objects, _onSuccess, _onError); + } else if (this._protocol === CollectorProtocolNode.HTTP_PROTO) { + sendWithJsonProto(this, objects, _onSuccess, _onError); + } else { + sendWithGrpc(this, objects, _onSuccess, _onError); + } + }); + + this._sendingPromises.push(promise); } onShutdown(): void { @@ -116,5 +135,6 @@ export abstract class CollectorExporterNodeBase< } abstract getServiceProtoPath(): string; + abstract getServiceClientType(): ServiceClientType; } diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index 910bc6d837..1b5cd3cefa 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -30,6 +30,9 @@ export class JaegerExporter implements SpanExporter { private readonly _process: jaegerTypes.ThriftProcess; private readonly _sender: typeof jaegerTypes.UDPSender; private readonly _onShutdownFlushTimeout: number; + private _isShutdown = false; + private _shutdownFlushTimeout: NodeJS.Timeout | undefined; + private _shuttingDownPromise: Promise = Promise.resolve(); constructor(config: jaegerTypes.ExporterConfig) { const localConfig = Object.assign({}, config); @@ -85,14 +88,40 @@ export class JaegerExporter implements SpanExporter { } /** Shutdown exporter. */ - shutdown(): void { - // Make an optimistic flush. - this._flush(); - // Sleeping x seconds before closing the sender's connection to ensure - // all spans are flushed. - setTimeout(() => { - this._sender.close(); - }, this._onShutdownFlushTimeout); + shutdown(): Promise { + if (this._isShutdown) { + return this._shuttingDownPromise; + } + this._isShutdown = true; + + this._shuttingDownPromise = new Promise((resolve, reject) => { + let rejected = false; + this._shutdownFlushTimeout = setTimeout(() => { + rejected = true; + reject('timeout'); + this._sender.close(); + }, this._onShutdownFlushTimeout); + + Promise.resolve() + .then(() => { + // Make an optimistic flush. + return this._flush(); + }) + .then(() => { + if (rejected) { + return; + } else { + this._shutdownFlushTimeout && + clearTimeout(this._shutdownFlushTimeout); + resolve(); + this._sender.close(); + } + }) + .catch(e => { + reject(e); + }); + }); + return this._shuttingDownPromise; } /** Transform spans and sends to Jaeger service. */ diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts index e58499c40b..b85512d818 100644 --- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts +++ b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts @@ -108,12 +108,14 @@ export class PrometheusExporter implements MetricExporter { /** * Shuts down the export server and clears the registry - * - * @param cb called when server is stopped */ - shutdown(cb?: () => void) { + shutdown(): Promise { this._registry.clear(); - this.stopServer(cb); + return new Promise(resolve => { + this.stopServer(() => { + resolve(); + }); + }); } /** diff --git a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts index e642aba620..ee2f9d3ad2 100644 --- a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts +++ b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts @@ -66,7 +66,7 @@ describe('PrometheusExporter', () => { const url = `http://localhost:${port}${endpoint}`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); - exporter.shutdown(() => { + exporter.shutdown().then(() => { return done(); }); }); @@ -86,7 +86,7 @@ describe('PrometheusExporter', () => { port: 9722, }); exporter.startServer(() => { - exporter.shutdown(() => { + exporter.shutdown().then(() => { return done(); }); }); @@ -101,7 +101,7 @@ describe('PrometheusExporter', () => { const url = `http://localhost:${port}${endpoint}`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); - exporter.shutdown(() => { + exporter.shutdown().then(() => { return done(); }); }); @@ -121,7 +121,7 @@ describe('PrometheusExporter', () => { const url = `http://localhost:${port}${endpoint}`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); - exporter.shutdown(() => { + exporter.shutdown().then(() => { return done(); }); }); @@ -141,7 +141,7 @@ describe('PrometheusExporter', () => { const url = `http://localhost:${port}/metric`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); - exporter.shutdown(() => { + exporter.shutdown().then(() => { const exporter2 = new PrometheusExporter({ port, endpoint: `/${endpoint}`, @@ -173,7 +173,7 @@ describe('PrometheusExporter', () => { http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 404); - exporter.shutdown(() => { + exporter.shutdown().then(() => { return done(); }); }); @@ -182,7 +182,7 @@ describe('PrometheusExporter', () => { it('should call a provided callback regardless of if the server is running', done => { const exporter = new PrometheusExporter(); - exporter.shutdown(() => { + exporter.shutdown().then(() => { return done(); }); }); @@ -206,7 +206,7 @@ describe('PrometheusExporter', () => { }); afterEach(done => { - exporter.shutdown(done); + exporter.shutdown().then(done); if (removeEvent) { removeEvent(); removeEvent = undefined; @@ -377,7 +377,71 @@ describe('PrometheusExporter', () => { counter.bind({ counterKey1: 'labelValue1' }).add(10); counter.bind({ counterKey1: 'labelValue2' }).add(20); counter.bind({ counterKey1: 'labelValue3' }).add(30); - meterProvider.shutdown(() => { + meterProvider.shutdown().then(() => { + http + .get('http://localhost:9464/metrics', res => { + res.on('data', chunk => { + const body = chunk.toString(); + const lines = body.split('\n'); + + assert.deepStrictEqual(lines, [ + '# HELP counter a test description', + '# TYPE counter counter', + `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, + `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, + `counter{counterKey1="labelValue3"} 30 ${mockedTimeMS}`, + '', + ]); + + done(); + }); + }) + .on('error', errorHandler(done)); + }); + }); + + it('should export multiple labels on graceful shutdown', done => { + const counter = meter.createCounter('counter', { + description: 'a test description', + }) as CounterMetric; + + counter.bind({ counterKey1: 'labelValue1' }).add(10); + counter.bind({ counterKey1: 'labelValue2' }).add(20); + counter.bind({ counterKey1: 'labelValue3' }).add(30); + + removeEvent = notifyOnGlobalShutdown(() => { + http + .get('http://localhost:9464/metrics', res => { + res.on('data', chunk => { + const body = chunk.toString(); + const lines = body.split('\n'); + + assert.deepStrictEqual(lines, [ + '# HELP counter a test description', + '# TYPE counter counter', + `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, + `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, + `counter{counterKey1="labelValue3"} 30 ${mockedTimeMS}`, + '', + ]); + + done(); + }); + }) + .on('error', errorHandler(done)); + }); + _invokeGlobalShutdown(); + }); + + it('should export multiple labels on manual shutdown', done => { + const counter = meter.createCounter('counter', { + description: 'a test description', + }) as CounterMetric; + + counter.bind({ counterKey1: 'labelValue1' }).add(10); + counter.bind({ counterKey1: 'labelValue2' }).add(20); + counter.bind({ counterKey1: 'labelValue3' }).add(30); + meterProvider.shutdown().then(() => { http .get('http://localhost:9464/metrics', res => { res.on('data', chunk => { @@ -628,7 +692,7 @@ describe('PrometheusExporter', () => { afterEach(done => { if (exporter) { - exporter.shutdown(done); + exporter.shutdown().then(done); exporter = undefined; } else { done(); diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts index 54010b3af3..0a84cb58b8 100644 --- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts +++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts @@ -40,6 +40,7 @@ export class ZipkinExporter implements SpanExporter { private readonly _reqOpts: http.RequestOptions; private _serviceName?: string; private _isShutdown: boolean; + private _sendingPromises: Promise[] = []; constructor(config: zipkinTypes.ExporterConfig = {}) { const urlStr = config.url || ZipkinExporter.DEFAULT_URL; @@ -82,18 +83,28 @@ export class ZipkinExporter implements SpanExporter { setTimeout(() => resultCallback(ExportResult.FAILED_NOT_RETRYABLE)); return; } - return this._sendSpans(spans, this._serviceName, resultCallback); + const promise = new Promise(resolve => { + this._sendSpans(spans, this._serviceName!, result => { + resolve(); + resultCallback(result); + const index = this._sendingPromises.indexOf(promise); + this._sendingPromises.splice(index, 1); + }); + }); + this._sendingPromises.push(promise); } /** * Shutdown exporter. Noop operation in this exporter. */ - shutdown() { + shutdown(): Promise { this._logger.debug('Zipkin exporter shutdown'); - if (this._isShutdown) { - return; - } this._isShutdown = true; + return new Promise((resolve, reject) => { + Promise.all(this._sendingPromises).then(() => { + resolve(); + }, reject); + }); } /** diff --git a/packages/opentelemetry-metrics/package.json b/packages/opentelemetry-metrics/package.json index 8facdc3aac..13f2132802 100644 --- a/packages/opentelemetry-metrics/package.json +++ b/packages/opentelemetry-metrics/package.json @@ -1,4 +1,4 @@ -{ + { "name": "@opentelemetry/metrics", "version": "0.10.2", "description": "OpenTelemetry metrics SDK", diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts index 61261fea01..4bfb3d38f5 100644 --- a/packages/opentelemetry-metrics/src/Meter.ts +++ b/packages/opentelemetry-metrics/src/Meter.ts @@ -41,6 +41,8 @@ export class Meter implements api.Meter { private readonly _resource: Resource; private readonly _instrumentationLibrary: InstrumentationLibrary; private readonly _controller: PushController; + private _isShutdown = false; + private _shuttingDownPromise: Promise = Promise.resolve(); /** * Constructs a new Meter instance. @@ -295,11 +297,11 @@ export class Meter implements api.Meter { * each aggregator belonging to the metrics that were created with this * meter instance. */ - async collect(): Promise { + collect(): Promise { const metrics = Array.from(this._metrics.values()).map(metric => { return metric.getMetricRecord(); }); - await Promise.all(metrics).then(records => { + return Promise.all(metrics).then(records => { records.forEach(metrics => { metrics.forEach(metric => this._batcher.process(metric)); }); @@ -310,8 +312,23 @@ export class Meter implements api.Meter { return this._batcher; } - async shutdown(): Promise { - await this._controller.shutdown(); + shutdown(): Promise { + if (this._isShutdown) { + return this._shuttingDownPromise; + } + this._isShutdown = true; + + this._shuttingDownPromise = new Promise((resolve, reject) => { + Promise.resolve() + .then(() => { + return this._controller.shutdown(); + }) + .then(resolve) + .catch(e => { + reject(e); + }); + }); + return this._shuttingDownPromise; } /** diff --git a/packages/opentelemetry-metrics/src/MeterProvider.ts b/packages/opentelemetry-metrics/src/MeterProvider.ts index 349d3514d5..5fb6b87e4a 100644 --- a/packages/opentelemetry-metrics/src/MeterProvider.ts +++ b/packages/opentelemetry-metrics/src/MeterProvider.ts @@ -27,6 +27,8 @@ export class MeterProvider implements api.MeterProvider { private readonly _config: MeterConfig; private readonly _meters: Map = new Map(); private _cleanNotifyOnGlobalShutdown: Function | undefined; + private _shuttingDownPromise: Promise = Promise.resolve(); + private _isShutdown = false; readonly resource: Resource; readonly logger: api.Logger; @@ -38,9 +40,9 @@ export class MeterProvider implements api.MeterProvider { resource: this.resource, }); if (this._config.gracefulShutdown) { - this._cleanNotifyOnGlobalShutdown = notifyOnGlobalShutdown( - this._shutdownAllMeters.bind(this) - ); + this._cleanNotifyOnGlobalShutdown = notifyOnGlobalShutdown(() => { + this._shutdownAllMeters().catch(); + }); } } @@ -61,22 +63,38 @@ export class MeterProvider implements api.MeterProvider { return this._meters.get(key)!; } - shutdown(cb: () => void = () => {}): void { - this._shutdownAllMeters().then(() => { - setTimeout(cb, 0); - }); + shutdown(): Promise { if (this._cleanNotifyOnGlobalShutdown) { this._cleanNotifyOnGlobalShutdown(); this._cleanNotifyOnGlobalShutdown = undefined; } + return this._shutdownAllMeters(); } - private _shutdownAllMeters() { - if (this._config.exporter) { - this._config.exporter.shutdown(); + private _shutdownAllMeters(): Promise { + if (this._isShutdown) { + return this._shuttingDownPromise; } - return Promise.all( - Array.from(this._meters, ([_, meter]) => meter.shutdown()) - ); + this._isShutdown = true; + + this._shuttingDownPromise = new Promise((resolve, reject) => { + Promise.resolve() + .then(() => { + return Promise.all( + Array.from(this._meters, ([_, meter]) => meter.shutdown()) + ); + }) + .then(() => { + if (this._config.exporter) { + return this._config.exporter.shutdown(); + } + return; + }) + .then(resolve) + .catch(e => { + reject(e); + }); + }); + return this._shuttingDownPromise; } } diff --git a/packages/opentelemetry-metrics/src/export/ConsoleMetricExporter.ts b/packages/opentelemetry-metrics/src/export/ConsoleMetricExporter.ts index 9f0d83eb39..988a527da1 100644 --- a/packages/opentelemetry-metrics/src/export/ConsoleMetricExporter.ts +++ b/packages/opentelemetry-metrics/src/export/ConsoleMetricExporter.ts @@ -54,7 +54,8 @@ export class ConsoleMetricExporter implements MetricExporter { return resultCallback(ExportResult.SUCCESS); } - shutdown(): void { + shutdown(): Promise { // By default does nothing + return Promise.resolve(); } } diff --git a/packages/opentelemetry-metrics/src/export/Controller.ts b/packages/opentelemetry-metrics/src/export/Controller.ts index 2c48b73820..5cc42b1472 100644 --- a/packages/opentelemetry-metrics/src/export/Controller.ts +++ b/packages/opentelemetry-metrics/src/export/Controller.ts @@ -38,8 +38,8 @@ export class PushController extends Controller { unrefTimer(this._timer); } - async shutdown(): Promise { - await this._collect(); + shutdown(): Promise { + return this._collect(); } private async _collect(): Promise { diff --git a/packages/opentelemetry-metrics/src/export/NoopExporter.ts b/packages/opentelemetry-metrics/src/export/NoopExporter.ts index ef748753b7..11e24d2464 100644 --- a/packages/opentelemetry-metrics/src/export/NoopExporter.ts +++ b/packages/opentelemetry-metrics/src/export/NoopExporter.ts @@ -25,5 +25,7 @@ export class NoopExporter implements MetricExporter { ): void {} // By default does nothing - shutdown(): void {} + shutdown(): Promise { + return Promise.resolve(); + } } diff --git a/packages/opentelemetry-metrics/src/export/types.ts b/packages/opentelemetry-metrics/src/export/types.ts index 99ff678047..58059311f1 100644 --- a/packages/opentelemetry-metrics/src/export/types.ts +++ b/packages/opentelemetry-metrics/src/export/types.ts @@ -105,7 +105,7 @@ export interface MetricExporter { ): void; /** Stops the exporter. */ - shutdown(): void; + shutdown(): Promise; } /** diff --git a/packages/opentelemetry-metrics/test/MeterProvider.test.ts b/packages/opentelemetry-metrics/test/MeterProvider.test.ts index 55cdafd66b..1aee28f430 100644 --- a/packages/opentelemetry-metrics/test/MeterProvider.test.ts +++ b/packages/opentelemetry-metrics/test/MeterProvider.test.ts @@ -108,8 +108,10 @@ describe('MeterProvider', () => { 'shutdown' ); removeEvent = notifyOnGlobalShutdown(() => { - sinon.assert.calledOnce(shutdownStub1); - sinon.assert.calledOnce(shutdownStub2); + setImmediate(() => { + sinon.assert.calledOnce(shutdownStub1); + sinon.assert.calledOnce(shutdownStub2); + }); }); _invokeGlobalShutdown(); }); @@ -128,7 +130,7 @@ describe('MeterProvider', () => { meterProvider.getMeter('meter2'), 'shutdown' ); - meterProvider.shutdown(() => { + meterProvider.shutdown().then(() => { sinon.assert.calledOnce(shutdownStub1); sinon.assert.calledOnce(shutdownStub2); }); diff --git a/packages/opentelemetry-plugin-fetch/test/fetch.test.ts b/packages/opentelemetry-plugin-fetch/test/fetch.test.ts index 9f70969be0..e801dfe852 100644 --- a/packages/opentelemetry-plugin-fetch/test/fetch.test.ts +++ b/packages/opentelemetry-plugin-fetch/test/fetch.test.ts @@ -29,7 +29,9 @@ import { AttributeNames } from '../src/enums/AttributeNames'; class DummySpanExporter implements tracing.SpanExporter { export(spans: any) {} - shutdown() {} + shutdown() { + return Promise.resolve(); + } } const getData = (url: string, method?: string) => diff --git a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts index 4491cb0fb9..8cd82b2a19 100644 --- a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts +++ b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts @@ -40,7 +40,9 @@ import { XMLHttpRequestPlugin } from '../src/xhr'; class DummySpanExporter implements tracing.SpanExporter { export(spans: any) {} - shutdown() {} + shutdown() { + return Promise.resolve(); + } } const XHR_TIMEOUT = 2000; diff --git a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts index 87299e30fc..065e74b2b8 100644 --- a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts +++ b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts @@ -107,15 +107,15 @@ export class BasicTracerProvider implements api.TracerProvider { } } - shutdown(cb: () => void = () => {}) { - this.activeSpanProcessor.shutdown(cb); + shutdown() { if (this._cleanNotifyOnGlobalShutdown) { this._cleanNotifyOnGlobalShutdown(); this._cleanNotifyOnGlobalShutdown = undefined; } + return this.activeSpanProcessor.shutdown(); } private _shutdownActiveProcessor() { - this.activeSpanProcessor.shutdown(); + return this.activeSpanProcessor.shutdown(); } } diff --git a/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts b/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts index 3f717f52fa..97341a277a 100644 --- a/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/MultiSpanProcessor.ts @@ -24,16 +24,17 @@ import { ReadableSpan } from './export/ReadableSpan'; export class MultiSpanProcessor implements SpanProcessor { constructor(private readonly _spanProcessors: SpanProcessor[]) {} - forceFlush(cb: () => void = () => {}): void { - let finished = 0; - const total = this._spanProcessors.length; + forceFlush(): Promise { + const promises: Promise[] = []; + for (const spanProcessor of this._spanProcessors) { - spanProcessor.forceFlush(() => { - if (++finished === total) { - cb(); - } - }); + promises.push(spanProcessor.forceFlush()); } + return new Promise((resolve, reject) => { + Promise.all(promises).then(() => { + resolve(); + }, reject); + }); } onStart(span: ReadableSpan): void { @@ -48,15 +49,16 @@ export class MultiSpanProcessor implements SpanProcessor { } } - shutdown(cb: () => void = () => {}): void { - let finished = 0; - const total = this._spanProcessors.length; + shutdown(): Promise { + const promises: Promise[] = []; + for (const spanProcessor of this._spanProcessors) { - spanProcessor.shutdown(() => { - if (++finished === total) { - cb(); - } - }); + promises.push(spanProcessor.shutdown()); } + return new Promise((resolve, reject) => { + Promise.all(promises).then(() => { + resolve(); + }, reject); + }); } } diff --git a/packages/opentelemetry-tracing/src/NoopSpanProcessor.ts b/packages/opentelemetry-tracing/src/NoopSpanProcessor.ts index 66f074f3a9..186779e3c7 100644 --- a/packages/opentelemetry-tracing/src/NoopSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/NoopSpanProcessor.ts @@ -21,10 +21,10 @@ import { ReadableSpan } from './export/ReadableSpan'; export class NoopSpanProcessor implements SpanProcessor { onStart(span: ReadableSpan): void {} onEnd(span: ReadableSpan): void {} - shutdown(cb: () => unknown = () => {}): void { - setTimeout(cb, 0); + shutdown(): Promise { + return Promise.resolve(); } - forceFlush(cb: () => unknown = () => {}): void { - setTimeout(cb, 0); + forceFlush(): Promise { + return Promise.resolve(); } } diff --git a/packages/opentelemetry-tracing/src/SpanProcessor.ts b/packages/opentelemetry-tracing/src/SpanProcessor.ts index 83cc53c0ab..80212ff556 100644 --- a/packages/opentelemetry-tracing/src/SpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/SpanProcessor.ts @@ -24,7 +24,7 @@ export interface SpanProcessor { /** * Forces to export all finished spans */ - forceFlush(callback: () => void): void; + forceFlush(): Promise; /** * Called when a {@link ReadableSpan} is started, if the `span.isRecording()` @@ -44,5 +44,5 @@ export interface SpanProcessor { * Shuts down the processor. Called when SDK is shut down. This is an * opportunity for processor to do any cleanup required. */ - shutdown(callback: () => void): void; + shutdown(): Promise; } diff --git a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts index a81e747ac8..b6e6c4d33e 100644 --- a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { unrefTimer } from '@opentelemetry/core'; +import { ExportResult, unrefTimer } from '@opentelemetry/core'; import { SpanProcessor } from '../SpanProcessor'; import { BufferConfig } from '../types'; import { ReadableSpan } from './ReadableSpan'; @@ -34,6 +34,7 @@ export class BatchSpanProcessor implements SpanProcessor { private _finishedSpans: ReadableSpan[] = []; private _timer: NodeJS.Timeout | undefined; private _isShutdown = false; + private _shuttingDownPromise: Promise = Promise.resolve(); constructor(private readonly _exporter: SpanExporter, config?: BufferConfig) { this._bufferSize = @@ -44,12 +45,11 @@ export class BatchSpanProcessor implements SpanProcessor { : DEFAULT_BUFFER_TIMEOUT_MS; } - forceFlush(cb: () => void = () => {}): void { + forceFlush(): Promise { if (this._isShutdown) { - setTimeout(cb, 0); - return; + return this._shuttingDownPromise; } - this._flush(cb); + return this._flush(); } // does nothing. @@ -62,14 +62,25 @@ export class BatchSpanProcessor implements SpanProcessor { this._addToBuffer(span); } - shutdown(cb: () => void = () => {}): void { + shutdown(): Promise { if (this._isShutdown) { - setTimeout(cb, 0); - return; + return this._shuttingDownPromise; } - this.forceFlush(cb); this._isShutdown = true; - this._exporter.shutdown(); + this._shuttingDownPromise = new Promise((resolve, reject) => { + Promise.resolve() + .then(() => { + return this._flush(); + }) + .then(() => { + return this._exporter.shutdown(); + }) + .then(resolve) + .catch(e => { + reject(e); + }); + }); + return this._shuttingDownPromise; } /** Add a span in the buffer. */ @@ -82,21 +93,28 @@ export class BatchSpanProcessor implements SpanProcessor { } /** Send the span data list to exporter */ - private _flush(cb: () => void = () => {}) { + private _flush(): Promise { this._clearTimer(); if (this._finishedSpans.length === 0) { - setTimeout(cb, 0); - return; + return Promise.resolve(); } - this._exporter.export(this._finishedSpans, cb); - this._finishedSpans = []; + return new Promise((resolve, reject) => { + this._exporter.export(this._finishedSpans, result => { + this._finishedSpans = []; + if (result === ExportResult.SUCCESS) { + resolve(); + } else { + reject(result); + } + }); + }); } private _maybeStartTimer() { if (this._timer !== undefined) return; this._timer = setTimeout(() => { - this._flush(); + this._flush().catch(); }, this._bufferTimeout); unrefTimer(this._timer); } diff --git a/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts b/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts index dfb12a25bf..8be4a90abd 100644 --- a/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts +++ b/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts @@ -38,8 +38,9 @@ export class ConsoleSpanExporter implements SpanExporter { /** * Shutdown the exporter. */ - shutdown(): void { - return this._sendSpans([]); + shutdown(): Promise { + this._sendSpans([]); + return Promise.resolve(); } /** diff --git a/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts b/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts index 04eec13318..a814748a13 100644 --- a/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts +++ b/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts @@ -36,9 +36,10 @@ export class InMemorySpanExporter implements SpanExporter { return resultCallback(ExportResult.SUCCESS); } - shutdown(): void { + shutdown(): Promise { this._stopped = true; this._finishedSpans = []; + return Promise.resolve(); } reset() { diff --git a/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts index 294b61777a..bf6b61fe83 100644 --- a/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts @@ -26,11 +26,13 @@ import { ReadableSpan } from './ReadableSpan'; */ export class SimpleSpanProcessor implements SpanProcessor { constructor(private readonly _exporter: SpanExporter) {} + private _isShutdown = false; + private _shuttingDownPromise: Promise = Promise.resolve(); - forceFlush(cb: () => void = () => {}): void { + forceFlush(): Promise { // do nothing as all spans are being exported without waiting - setTimeout(cb, 0); + return Promise.resolve(); } // does nothing. @@ -43,14 +45,21 @@ export class SimpleSpanProcessor implements SpanProcessor { this._exporter.export([span], () => {}); } - shutdown(cb: () => void = () => {}): void { + shutdown(): Promise { if (this._isShutdown) { - setTimeout(cb, 0); - return; + return this._shuttingDownPromise; } this._isShutdown = true; - - this._exporter.shutdown(); - setTimeout(cb, 0); + this._shuttingDownPromise = new Promise((resolve, reject) => { + Promise.resolve() + .then(() => { + return this._exporter.shutdown(); + }) + .then(resolve) + .catch(e => { + reject(e); + }); + }); + return this._shuttingDownPromise; } } diff --git a/packages/opentelemetry-tracing/src/export/SpanExporter.ts b/packages/opentelemetry-tracing/src/export/SpanExporter.ts index e83621e594..b3b89d4aa6 100644 --- a/packages/opentelemetry-tracing/src/export/SpanExporter.ts +++ b/packages/opentelemetry-tracing/src/export/SpanExporter.ts @@ -35,5 +35,5 @@ export interface SpanExporter { ): void; /** Stops the exporter. */ - shutdown(): void; + shutdown(): Promise; } diff --git a/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts index 7db10cd26e..c0e4c58d7d 100644 --- a/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/MultiSpanProcessor.test.ts @@ -35,10 +35,13 @@ class TestProcessor implements SpanProcessor { onEnd(span: Span): void { this.spans.push(span); } - shutdown(): void { + shutdown(): Promise { this.spans = []; + return Promise.resolve(); + } + forceFlush(): Promise { + return Promise.resolve(); } - forceFlush(): void {} } describe('MultiSpanProcessor', () => { @@ -75,7 +78,7 @@ describe('MultiSpanProcessor', () => { multiSpanProcessor.shutdown(); }); - it('should handle two span processor', () => { + it('should handle two span processor', async () => { const processor1 = new TestProcessor(); const processor2 = new TestProcessor(); const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]); @@ -91,7 +94,7 @@ describe('MultiSpanProcessor', () => { assert.strictEqual(processor1.spans.length, 1); assert.strictEqual(processor1.spans.length, processor2.spans.length); - multiSpanProcessor.shutdown(); + await multiSpanProcessor.shutdown(); assert.strictEqual(processor1.spans.length, 0); assert.strictEqual(processor1.spans.length, processor2.spans.length); }); @@ -135,7 +138,52 @@ describe('MultiSpanProcessor', () => { assert.strictEqual(processor1.spans.length, 1); assert.strictEqual(processor1.spans.length, processor2.spans.length); - tracerProvider.shutdown(() => { + 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]); + + 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); + + 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'); + 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); }); @@ -146,10 +194,13 @@ describe('MultiSpanProcessor', () => { const processor: SpanProcessor = { forceFlush: () => { flushed = true; + return Promise.resolve(); }, onStart: span => {}, onEnd: span => {}, - shutdown: () => {}, + shutdown: () => { + return Promise.resolve(); + }, }; const multiSpanProcessor = new MultiSpanProcessor([processor]); multiSpanProcessor.forceFlush(); @@ -161,17 +212,17 @@ describe('MultiSpanProcessor', () => { const processor1 = new SimpleSpanProcessor(new InMemorySpanExporter()); const processor2 = new SimpleSpanProcessor(new InMemorySpanExporter()); - const spy1 = Sinon.stub(processor1, 'forceFlush').callsFake(cb => { + const spy1 = Sinon.stub(processor1, 'forceFlush').callsFake(() => { flushed++; - cb!(); + return Promise.resolve(); }); - const spy2 = Sinon.stub(processor2, 'forceFlush').callsFake(cb => { + const spy2 = Sinon.stub(processor2, 'forceFlush').callsFake(() => { flushed++; - cb!(); + return Promise.resolve(); }); const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]); - multiSpanProcessor.forceFlush(() => { + multiSpanProcessor.forceFlush().then(() => { Sinon.assert.calledOnce(spy1); Sinon.assert.calledOnce(spy2); assert.strictEqual(flushed, 2); diff --git a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts index 29a9d4117f..41552ac649 100644 --- a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts @@ -69,7 +69,7 @@ describe('BatchSpanProcessor', () => { }); describe('.onStart/.onEnd/.shutdown', () => { - it('should do nothing after processor is shutdown', () => { + it('should do nothing after processor is shutdown', async () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); const spy: sinon.SinonSpy = sinon.spy(exporter, 'export') as any; @@ -78,14 +78,14 @@ describe('BatchSpanProcessor', () => { processor.onEnd(span); assert.strictEqual(processor['_finishedSpans'].length, 1); - processor.forceFlush(); + await processor.forceFlush(); assert.strictEqual(exporter.getFinishedSpans().length, 1); processor.onEnd(span); assert.strictEqual(processor['_finishedSpans'].length, 1); assert.strictEqual(spy.args.length, 1); - processor.shutdown(); + await processor.shutdown(); assert.strictEqual(spy.args.length, 2); assert.strictEqual(exporter.getFinishedSpans().length, 0); @@ -95,7 +95,7 @@ describe('BatchSpanProcessor', () => { assert.strictEqual(exporter.getFinishedSpans().length, 0); }); - it('should export the sampled spans with buffer size reached', () => { + it('should export the sampled spans with buffer size reached', async () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.bufferSize; i++) { const span = createSampledSpan(`${name}_${i}`); @@ -110,7 +110,7 @@ describe('BatchSpanProcessor', () => { processor.onEnd(span); assert.strictEqual(exporter.getFinishedSpans().length, 6); - processor.shutdown(); + await processor.shutdown(); assert.strictEqual(exporter.getFinishedSpans().length, 0); }); @@ -179,14 +179,14 @@ describe('BatchSpanProcessor', () => { describe('no waiting spans', () => { it('should call an async callback when flushing is complete', done => { const processor = new BatchSpanProcessor(exporter); - processor.forceFlush(() => { + processor.forceFlush().then(() => { done(); }); }); it('should call an async callback when shutdown is complete', done => { const processor = new BatchSpanProcessor(exporter); - processor.shutdown(() => { + processor.shutdown().then(() => { done(); }); }); @@ -205,7 +205,7 @@ describe('BatchSpanProcessor', () => { }); it('should call an async callback when flushing is complete', done => { - processor.forceFlush(() => { + processor.forceFlush().then(() => { assert.strictEqual(exporter.getFinishedSpans().length, 1); done(); }); @@ -220,7 +220,7 @@ describe('BatchSpanProcessor', () => { }, 0); }); - processor.shutdown(() => { + processor.shutdown().then(() => { assert.strictEqual(exportedSpans, 1); done(); }); diff --git a/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts index 8b67013153..05c9fcc3b5 100644 --- a/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts @@ -35,7 +35,7 @@ describe('SimpleSpanProcessor', () => { }); describe('.onStart/.onEnd/.shutdown', () => { - it('should handle span started and ended when SAMPLED', () => { + it('should handle span started and ended when SAMPLED', async () => { const processor = new SimpleSpanProcessor(exporter); const spanContext: SpanContext = { traceId: 'a3cda95b652f4a1592b449d5929fda1b', @@ -54,11 +54,11 @@ describe('SimpleSpanProcessor', () => { processor.onEnd(span); assert.strictEqual(exporter.getFinishedSpans().length, 1); - processor.shutdown(); + await processor.shutdown(); assert.strictEqual(exporter.getFinishedSpans().length, 0); }); - it('should handle span started and ended when UNSAMPLED', () => { + it('should handle span started and ended when UNSAMPLED', async () => { const processor = new SimpleSpanProcessor(exporter); const spanContext: SpanContext = { traceId: 'a3cda95b652f4a1592b449d5929fda1b', @@ -77,21 +77,21 @@ describe('SimpleSpanProcessor', () => { processor.onEnd(span); assert.strictEqual(exporter.getFinishedSpans().length, 0); - processor.shutdown(); + await processor.shutdown(); assert.strictEqual(exporter.getFinishedSpans().length, 0); }); describe('force flush', () => { it('should call an async callback when flushing is complete', done => { const processor = new SimpleSpanProcessor(exporter); - processor.forceFlush(() => { + processor.forceFlush().then(() => { done(); }); }); it('should call an async callback when shutdown is complete', done => { const processor = new SimpleSpanProcessor(exporter); - processor.shutdown(() => { + processor.shutdown().then(() => { done(); }); }); From be74fc59643e23f38f1cb28b2af1e4443fffcb86 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 19 Aug 2020 02:34:28 +0200 Subject: [PATCH 2/7] chore: unifying start server and stop server with shutdown for prometheus exporter --- .../src/prometheus.ts | 54 ++++++++++--------- .../test/prometheus.test.ts | 22 ++++---- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts index b85512d818..f42d5d1017 100644 --- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts +++ b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts @@ -70,7 +70,7 @@ export class PrometheusExporter implements MetricExporter { ).replace(/^([^/])/, '/$1'); if (config.startServer || PrometheusExporter.DEFAULT_OPTIONS.startServer) { - this.startServer(callback); + this.startServer().then(callback); } else if (callback) { callback(); } @@ -111,11 +111,7 @@ export class PrometheusExporter implements MetricExporter { */ shutdown(): Promise { this._registry.clear(); - return new Promise(resolve => { - this.stopServer(() => { - resolve(); - }); - }); + return this.stopServer(); } /** @@ -254,39 +250,45 @@ export class PrometheusExporter implements MetricExporter { /** * Stops the Prometheus export server - * @param callback A callback that will be executed once the server is stopped */ - stopServer(callback?: () => void) { + stopServer(): Promise { if (!this._server) { this._logger.debug( 'Prometheus stopServer() was called but server was never started.' ); - if (callback) { - callback(); - } + return Promise.resolve(); } else { - this._server.close(() => { - this._logger.debug('Prometheus exporter was stopped'); - if (callback) { - callback(); - } + return new Promise(resolve => { + this._server.close(err => { + if (!err) { + this._logger.debug('Prometheus exporter was stopped'); + } else { + if ( + ((err as unknown) as { code: string }).code !== + 'ERR_SERVER_NOT_RUNNING' + ) { + this._logger.error( + `Error during stopping the Prometheus Exporter "${err.message}"` + ); + } + } + resolve(); + }); }); } } /** * Starts the Prometheus export server - * - * @param callback called once the server is ready */ - startServer(callback?: () => void) { - this._server.listen(this._port, () => { - this._logger.debug( - `Prometheus exporter started on port ${this._port} at endpoint ${this._endpoint}` - ); - if (callback) { - callback(); - } + startServer(): Promise { + return new Promise(resolve => { + this._server.listen(this._port, () => { + this._logger.debug( + `Prometheus exporter started on port ${this._port} at endpoint ${this._endpoint}` + ); + resolve(); + }); }); } diff --git a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts index ee2f9d3ad2..b60df9c2be 100644 --- a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts +++ b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts @@ -85,7 +85,7 @@ describe('PrometheusExporter', () => { const exporter = new PrometheusExporter({ port: 9722, }); - exporter.startServer(() => { + exporter.startServer().then(() => { exporter.shutdown().then(() => { return done(); }); @@ -97,7 +97,7 @@ describe('PrometheusExporter', () => { const endpoint = PrometheusExporter.DEFAULT_OPTIONS.endpoint; const exporter = new PrometheusExporter(); - exporter.startServer(() => { + exporter.startServer().then(() => { const url = `http://localhost:${port}${endpoint}`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); @@ -117,7 +117,7 @@ describe('PrometheusExporter', () => { endpoint, }); - exporter.startServer(() => { + exporter.startServer().then(() => { const url = `http://localhost:${port}${endpoint}`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); @@ -137,7 +137,7 @@ describe('PrometheusExporter', () => { endpoint, }); - exporter.startServer(() => { + exporter.startServer().then(() => { const url = `http://localhost:${port}/metric`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); @@ -147,11 +147,11 @@ describe('PrometheusExporter', () => { endpoint: `/${endpoint}`, }); - exporter2.startServer(() => { + exporter2.startServer().then(() => { const url = `http://localhost:${port}/metric`; http.get(url, (res: any) => { assert.strictEqual(res.statusCode, 200); - exporter2.stopServer(() => { + exporter2.stopServer().then(() => { return done(); }); }); @@ -168,7 +168,7 @@ describe('PrometheusExporter', () => { port, endpoint, }); - exporter.startServer(() => { + exporter.startServer().then(() => { const url = `http://localhost:${port}/invalid`; http.get(url, (res: any) => { @@ -202,7 +202,7 @@ describe('PrometheusExporter', () => { meter = meterProvider.getMeter('test-prometheus', '1', { exporter: exporter, }); - exporter.startServer(done); + exporter.startServer().then(done); }); afterEach(done => { @@ -704,7 +704,7 @@ describe('PrometheusExporter', () => { prefix: 'test_prefix', }); - exporter.startServer(async () => { + exporter.startServer().then(async () => { await meter.collect(); exporter!.export(meter.getBatcher().checkPointSet(), () => { http @@ -733,7 +733,7 @@ describe('PrometheusExporter', () => { port: 8080, }); - exporter.startServer(async () => { + exporter.startServer().then(async () => { await meter.collect(); exporter!.export(meter.getBatcher().checkPointSet(), () => { http @@ -762,7 +762,7 @@ describe('PrometheusExporter', () => { endpoint: '/test', }); - exporter.startServer(async () => { + exporter.startServer().then(async () => { await meter.collect(); exporter!.export(meter.getBatcher().checkPointSet(), () => { http From ebb122d73646a2a3bf2aa42f453f78414f88107a Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 19 Aug 2020 18:44:47 +0200 Subject: [PATCH 3/7] chore: interval should be cleaned when shutdown starts --- packages/opentelemetry-metrics/src/export/Controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opentelemetry-metrics/src/export/Controller.ts b/packages/opentelemetry-metrics/src/export/Controller.ts index 5cc42b1472..0b631777bf 100644 --- a/packages/opentelemetry-metrics/src/export/Controller.ts +++ b/packages/opentelemetry-metrics/src/export/Controller.ts @@ -39,6 +39,7 @@ export class PushController extends Controller { } shutdown(): Promise { + clearInterval(this._timer); return this._collect(); } From cc56a203be9b97c5d10fad0bcfba9359ebae0cd9 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 24 Aug 2020 20:09:56 +0200 Subject: [PATCH 4/7] chore: lint --- .../common/CollectorMetricExporter.test.ts | 33 ++++++++------- .../common/CollectorTraceExporter.test.ts | 41 ++++++++++--------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts index c84403f0da..edf52252e4 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts @@ -138,21 +138,24 @@ describe('CollectorMetricExporter - common', () => { }); describe('when exporter is shutdown', () => { - it('should not export anything but return callback with code' + - ' "FailedNotRetryable"', async () => { - await collectorExporter.shutdown(); - spySend.resetHistory(); - - const callbackSpy = sinon.spy(); - collectorExporter.export(metrics, callbackSpy); - const returnCode = callbackSpy.args[0][0]; - assert.strictEqual( - returnCode, - ExportResult.FAILED_NOT_RETRYABLE, - 'return value is wrong' - ); - assert.strictEqual(spySend.callCount, 0, 'should not call send'); - }); + it( + 'should not export anything but return callback with code' + + ' "FailedNotRetryable"', + async () => { + await collectorExporter.shutdown(); + spySend.resetHistory(); + + const callbackSpy = sinon.spy(); + collectorExporter.export(metrics, callbackSpy); + const returnCode = callbackSpy.args[0][0]; + assert.strictEqual( + returnCode, + ExportResult.FAILED_NOT_RETRYABLE, + 'return value is wrong' + ); + assert.strictEqual(spySend.callCount, 0, 'should not call send'); + } + ); }); describe('when an error occurs', () => { it('should return a Not Retryable Error', done => { diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts index c4e92eaece..c6f1fad302 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts @@ -133,25 +133,28 @@ describe('CollectorTraceExporter - common', () => { assert.strictEqual(spySend.callCount, 1); }); - describe('when exporter is shutdown', () => { - it('should not export anything but return callback with code' + - ' "FailedNotRetryable"', async () => { - const spans: ReadableSpan[] = []; - spans.push(Object.assign({}, mockedReadableSpan)); - await collectorExporter.shutdown(); - spySend.resetHistory(); + describe('when exporter is shutdown', () => { + it( + 'should not export anything but return callback with code' + + ' "FailedNotRetryable"', + async () => { + const spans: ReadableSpan[] = []; + spans.push(Object.assign({}, mockedReadableSpan)); + await collectorExporter.shutdown(); + spySend.resetHistory(); + + const callbackSpy = sinon.spy(); + collectorExporter.export(spans, callbackSpy); + const returnCode = callbackSpy.args[0][0]; - const callbackSpy = sinon.spy(); - collectorExporter.export(spans, callbackSpy); - const returnCode = callbackSpy.args[0][0]; - - assert.strictEqual( - returnCode, - ExportResult.FAILED_NOT_RETRYABLE, - 'return value is wrong' - ); - assert.strictEqual(spySend.callCount, 0, 'should not call send'); - }); + assert.strictEqual( + returnCode, + ExportResult.FAILED_NOT_RETRYABLE, + 'return value is wrong' + ); + assert.strictEqual(spySend.callCount, 0, 'should not call send'); + } + ); }); describe('when an error occurs', () => { it('should return a Not Retryable Error', done => { @@ -224,7 +227,7 @@ describe('CollectorTraceExporter - common', () => { onShutdownSpy.restore(); }); - it('should call onShutdown', async() => { + it('should call onShutdown', async () => { await collectorExporter.shutdown(); assert.strictEqual(onShutdownSpy.callCount, 1); }); From 52edc4c316d7f0b9d1256ec1bd92bbc9445d4512 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 3 Sep 2020 03:31:51 +0200 Subject: [PATCH 5/7] chore: merge branch 'master' --- .circleci/checksum.sh | 1 + .circleci/config.yml | 14 +- .github/workflows/backcompat.yml | 33 ++ CHANGELOG.md | 89 ++++ RELEASING.md | 2 +- backwards-compatability/node10/index.ts | 10 + backwards-compatability/node10/package.json | 20 + backwards-compatability/node12/index.ts | 10 + backwards-compatability/node12/package.json | 20 + backwards-compatability/node8/index.ts | 10 + backwards-compatability/node8/package.json | 20 + examples/basic-tracer-node/package.json | 8 +- examples/collector-exporter-node/package.json | 16 +- examples/grpc-js/package.json | 14 +- examples/grpc/package.json | 14 +- examples/http/package.json | 14 +- examples/https/package.json | 14 +- examples/metrics/package.json | 8 +- examples/opentracing-shim/package.json | 12 +- examples/prometheus/package.json | 6 +- examples/tracer-web/package.json | 18 +- getting-started/example/package.json | 4 +- .../monitored-example/package.json | 6 +- getting-started/traced-example/package.json | 16 +- getting-started/ts-example/package.json | 20 +- .../package.json | 10 +- lerna.json | 3 +- metapackages/plugins-node-all/package.json | 4 +- metapackages/plugins-node-core/package.json | 10 +- metapackages/plugins-web-core/package.json | 4 +- package.json | 1 + packages/opentelemetry-api/package.json | 6 +- .../opentelemetry-api/src/api/global-utils.ts | 4 +- .../opentelemetry-api/src/api/propagation.ts | 14 +- packages/opentelemetry-api/src/api/trace.ts | 13 +- .../opentelemetry-api/src/common/Exception.ts | 47 ++ ...Propagator.ts => NoopTextMapPropagator.ts} | 8 +- ...TextPropagator.ts => TextMapPropagator.ts} | 4 +- packages/opentelemetry-api/src/index.ts | 13 +- .../src/metrics/BoundInstrument.ts | 13 - .../opentelemetry-api/src/metrics/Metric.ts | 21 - .../src/metrics/NoopMeter.ts | 15 +- .../opentelemetry-api/src/trace/NoopSpan.ts | 14 +- .../src/trace/ProxyTracer.ts | 71 +++ .../src/trace/ProxyTracerProvider.ts | 57 +++ packages/opentelemetry-api/src/trace/span.ts | 9 + .../src/trace/spancontext-utils.ts | 23 +- packages/opentelemetry-api/src/version.ts | 2 +- .../noop-implementations/noop-span.test.ts | 8 +- .../proxy-tracer.test.ts | 129 +++++ .../test/trace/spancontext-utils.test.ts | 10 +- .../package.json | 4 +- .../src/version.ts | 2 +- .../opentelemetry-context-base/package.json | 2 +- .../opentelemetry-context-base/src/version.ts | 2 +- .../package.json | 4 +- .../src/version.ts | 2 +- .../opentelemetry-context-zone/package.json | 4 +- .../opentelemetry-context-zone/src/version.ts | 2 +- packages/opentelemetry-core/package.json | 6 +- .../opentelemetry-core/src/context/context.ts | 37 ++ .../src/context/propagation/B3Propagator.ts | 4 +- .../context/propagation/HttpTraceContext.ts | 4 +- .../src/context/propagation/composite.ts | 6 +- .../src/context/propagation/types.ts | 4 +- .../propagation/HttpCorrelationContext.ts | 4 +- packages/opentelemetry-core/src/index.ts | 1 - .../src/platform/node/timer-util.ts | 2 +- .../src/trace/NoRecordingSpan.ts | 7 +- packages/opentelemetry-core/src/version.ts | 2 +- .../test/context/composite.test.ts | 4 +- .../test/context/context.test.ts | 91 ++++ .../package.json | 14 +- .../src/version.ts | 2 +- .../README.md | 4 +- .../package.json | 14 +- .../src/version.ts | 2 +- .../test/CollectorMetricExporter.test.ts | 20 +- .../test/CollectorTraceExporter.test.ts | 22 +- .../README.md | 4 +- .../package.json | 12 +- .../src/version.ts | 2 +- .../opentelemetry-exporter-jaeger/README.md | 4 +- .../package.json | 10 +- .../src/version.ts | 2 +- .../package.json | 9 +- .../src/PrometheusExporter.ts | 184 +++++++ .../src/PrometheusLabelsBatcher.ts | 65 +++ .../src/PrometheusSerializer.ts | 259 ++++++++++ .../src/index.ts | 2 +- .../src/prometheus.ts | 329 ------------- .../src/types.ts | 27 + .../src/version.ts | 2 +- .../test/ExactBatcher.ts | 49 ++ ...eus.test.ts => PrometheusExporter.test.ts} | 107 ++-- .../test/PrometheusLabelsBatcher.test.ts | 84 ++++ .../test/PrometheusSerializer.test.ts | 462 ++++++++++++++++++ .../test/util.ts | 34 ++ .../opentelemetry-exporter-zipkin/README.md | 4 +- .../package.json | 10 +- .../src/version.ts | 2 +- .../opentelemetry-grpc-utils/package.json | 16 +- .../opentelemetry-grpc-utils/src/version.ts | 2 +- packages/opentelemetry-metrics/package.json | 8 +- .../src/BoundInstrument.ts | 23 +- packages/opentelemetry-metrics/src/Meter.ts | 1 - .../src/ValueRecorderMetric.ts | 6 +- .../src/export/aggregators/Histogram.ts | 2 +- packages/opentelemetry-metrics/src/types.ts | 1 - packages/opentelemetry-metrics/src/version.ts | 2 +- .../opentelemetry-metrics/test/Meter.test.ts | 86 +--- .../test/export/aggregators/Histogram.test.ts | 18 +- packages/opentelemetry-node/package.json | 14 +- packages/opentelemetry-node/src/version.ts | 2 +- .../test/registration.test.ts | 26 +- .../opentelemetry-plugin-fetch/package.json | 12 +- .../opentelemetry-plugin-fetch/src/version.ts | 2 +- .../opentelemetry-plugin-grpc-js/package.json | 18 +- .../src/version.ts | 2 +- .../opentelemetry-plugin-grpc/package.json | 18 +- .../opentelemetry-plugin-grpc/src/version.ts | 2 +- .../opentelemetry-plugin-http/package.json | 19 +- .../opentelemetry-plugin-http/src/version.ts | 2 +- .../test/utils/DummyPropagation.ts | 4 +- .../opentelemetry-plugin-https/package.json | 18 +- .../opentelemetry-plugin-https/src/version.ts | 2 +- .../test/utils/DummyPropagation.ts | 4 +- .../package.json | 14 +- .../src/version.ts | 2 +- .../src/xhr.ts | 3 +- .../test/unmocked.test.ts | 68 +++ .../test/xhr.test.ts | 2 +- .../package.json | 8 +- .../src/version.ts | 2 +- .../README.md | 2 + .../package.json | 15 +- .../src/detectors/GcpDetector.ts | 6 +- .../src/version.ts | 2 +- .../test/detectors/GcpDetector.test.ts | 218 +++++---- packages/opentelemetry-resources/package.json | 6 +- .../opentelemetry-resources/src/version.ts | 2 +- packages/opentelemetry-sdk-node/README.md | 2 +- packages/opentelemetry-sdk-node/package.json | 26 +- packages/opentelemetry-sdk-node/src/sdk.ts | 12 +- packages/opentelemetry-sdk-node/src/types.ts | 2 +- .../opentelemetry-sdk-node/src/version.ts | 2 +- .../opentelemetry-sdk-node/test/sdk.test.ts | 133 ++--- .../package.json | 5 +- .../src/trace/exception.ts | 23 + .../src/trace/index.ts | 5 +- .../src/version.ts | 2 +- .../package.json | 8 +- .../src/version.ts | 2 +- .../test/Shim.test.ts | 3 +- packages/opentelemetry-tracing/package.json | 11 +- packages/opentelemetry-tracing/src/Span.ts | 33 ++ packages/opentelemetry-tracing/src/Tracer.ts | 10 +- .../src/export/BatchSpanProcessor.ts | 20 +- .../src/export/InMemorySpanExporter.ts | 11 +- .../src/export/SimpleSpanProcessor.ts | 8 +- packages/opentelemetry-tracing/src/types.ts | 4 +- packages/opentelemetry-tracing/src/version.ts | 2 +- .../opentelemetry-tracing/test/Span.test.ts | 89 ++++ .../opentelemetry-tracing/test/Tracer.test.ts | 22 + .../test/export/BatchSpanProcessor.test.ts | 30 ++ .../test/export/SimpleSpanProcessor.test.ts | 47 +- .../test/export/TestStackContextManager.ts | 57 +++ .../test/export/TestTracingSpanExporter.ts | 85 ++++ packages/opentelemetry-web/package.json | 16 +- packages/opentelemetry-web/src/utils.ts | 7 + packages/opentelemetry-web/src/version.ts | 2 +- .../test/registration.test.ts | 23 +- renovate.json | 3 +- 173 files changed, 3060 insertions(+), 1152 deletions(-) create mode 100644 .github/workflows/backcompat.yml create mode 100644 backwards-compatability/node10/index.ts create mode 100644 backwards-compatability/node10/package.json create mode 100644 backwards-compatability/node12/index.ts create mode 100644 backwards-compatability/node12/package.json create mode 100644 backwards-compatability/node8/index.ts create mode 100644 backwards-compatability/node8/package.json create mode 100644 packages/opentelemetry-api/src/common/Exception.ts rename packages/opentelemetry-api/src/context/propagation/{NoopHttpTextPropagator.ts => NoopTextMapPropagator.ts} (79%) rename packages/opentelemetry-api/src/context/propagation/{HttpTextPropagator.ts => TextMapPropagator.ts} (95%) create mode 100644 packages/opentelemetry-api/src/trace/ProxyTracer.ts create mode 100644 packages/opentelemetry-api/src/trace/ProxyTracerProvider.ts rename packages/{opentelemetry-core => opentelemetry-api}/src/trace/spancontext-utils.ts (55%) create mode 100644 packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts rename packages/{opentelemetry-core => opentelemetry-api}/test/trace/spancontext-utils.test.ts (85%) create mode 100644 packages/opentelemetry-core/test/context/context.test.ts create mode 100644 packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts create mode 100644 packages/opentelemetry-exporter-prometheus/src/PrometheusLabelsBatcher.ts create mode 100644 packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts delete mode 100644 packages/opentelemetry-exporter-prometheus/src/prometheus.ts create mode 100644 packages/opentelemetry-exporter-prometheus/src/types.ts create mode 100644 packages/opentelemetry-exporter-prometheus/test/ExactBatcher.ts rename packages/opentelemetry-exporter-prometheus/test/{prometheus.test.ts => PrometheusExporter.test.ts} (89%) create mode 100644 packages/opentelemetry-exporter-prometheus/test/PrometheusLabelsBatcher.test.ts create mode 100644 packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts create mode 100644 packages/opentelemetry-exporter-prometheus/test/util.ts create mode 100644 packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts create mode 100644 packages/opentelemetry-semantic-conventions/src/trace/exception.ts create mode 100644 packages/opentelemetry-tracing/test/export/TestStackContextManager.ts create mode 100644 packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts diff --git a/.circleci/checksum.sh b/.circleci/checksum.sh index af2e0f293e..fa7cab9ae9 100644 --- a/.circleci/checksum.sh +++ b/.circleci/checksum.sh @@ -22,5 +22,6 @@ fi openssl md5 package.json >> $FILE find packages/*/package.json | xargs -I{} openssl md5 {} >> $FILE +find metapackages/*/package.json | xargs -I{} openssl md5 {} >> $FILE sort -o $FILE $FILE diff --git a/.circleci/config.yml b/.circleci/config.yml index e19df84e8a..15b55c5a74 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ node_test_env: &node_test_env cache_1: &cache_1 - key: npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + key: npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 paths: - ./node_modules - ./package-lock.json @@ -25,13 +25,15 @@ cache_1: &cache_1 - packages/opentelemetry-web/node_modules cache_2: &cache_2 - key: npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + key: npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 paths: - packages/opentelemetry-plugin-grpc/node_modules - packages/opentelemetry-plugin-http/node_modules - packages/opentelemetry-plugin-https/node_modules - packages/opentelemetry-exporter-collector/node_modules - packages/opentelemetry-plugin-xml-http-request/node_modules + - packages/opentelemetry-resource-detector-aws/node_modules + - packages/opentelemetry-resource-detector-gcp/node_modules - packages/opentelemetry-resources/node_modules node_unit_tests: &node_unit_tests @@ -53,10 +55,10 @@ node_unit_tests: &node_unit_tests echo "CIRCLE_NODE_VERSION=${CIRCLE_NODE_VERSION}" - restore_cache: keys: - - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - restore_cache: keys: - - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - run: name: Install Root Dependencies command: npm install --ignore-scripts @@ -93,10 +95,10 @@ browsers_unit_tests: &browsers_unit_tests echo "CIRCLE_NODE_VERSION=${CIRCLE_NODE_VERSION}" - restore_cache: keys: - - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - restore_cache: keys: - - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - run: name: Install Root Dependencies command: npm install --ignore-scripts diff --git a/.github/workflows/backcompat.yml b/.github/workflows/backcompat.yml new file mode 100644 index 0000000000..58121302e9 --- /dev/null +++ b/.github/workflows/backcompat.yml @@ -0,0 +1,33 @@ +name: Backwards Compatability + +on: [push, pull_request] + +jobs: + types-node: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: restore lerna + uses: actions/cache@master # must use unreleased master to cache multiple paths + id: cache + with: + path: | + node_modules + packages/*/node_modules + metapackages/*/node_modules + key: ${{ runner.os }}-${{ hashFiles('**/package.json') }} + + - name: Bootstrap + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install --only=dev --ignore-scripts + npx lerna bootstrap --no-ci --ignore-scripts -- --only=dev + + - name: Install and Build API Dependencies + run: npx lerna bootstrap --no-ci --scope backcompat-* --include-filtered-dependencies + + - name: + run: | + npm run test:backcompat diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ceab2adb..51d1a86c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,95 @@ All notable changes to this project will be documented in this file. ## Unreleased +## 0.11.0 + +### :boom: Breaking Change + +* `opentelemetry-api`, `opentelemetry-core`, `opentelemetry-node`, `opentelemetry-plugin-http`, `opentelemetry-plugin-https`, `opentelemetry-sdk-node`, `opentelemetry-tracing`, `opentelemetry-web` + * [#1458](https://github.com/open-telemetry/opentelemetry-js/pull/1458) refactor: rename HttpText to TextMap propagator ([@dengliming](https://github.com/dengliming)) +* `opentelemetry-api`, `opentelemetry-core`, `opentelemetry-exporter-collector-grpc`, `opentelemetry-exporter-collector-proto`, `opentelemetry-exporter-collector`, `opentelemetry-metrics` + * [#1446](https://github.com/open-telemetry/opentelemetry-js/pull/1446) Collector split ([@obecny](https://github.com/obecny)) +* `opentelemetry-exporter-collector`, `opentelemetry-exporter-jaeger`, `opentelemetry-exporter-zipkin`, `opentelemetry-node`, `opentelemetry-resources`, `opentelemetry-web` + * [#1419](https://github.com/open-telemetry/opentelemetry-js/pull/1419) chore!: refer to resource labels as attributes ([@mwear](https://github.com/mwear)) + +### :rocket: (Enhancement) + +* `opentelemetry-api`, `opentelemetry-core`, `opentelemetry-shim-opentracing`, `opentelemetry-tracing` + * [#1447](https://github.com/open-telemetry/opentelemetry-js/pull/1447) Move SpanContext isValid to the API ([@srjames90](https://github.com/srjames90)) +* `opentelemetry-plugin-xml-http-request` + * [#1476](https://github.com/open-telemetry/opentelemetry-js/pull/1476) Align xhr span name with spec ([@johnbley](https://github.com/johnbley)) +* `opentelemetry-resource-detector-gcp`, `opentelemetry-sdk-node` + * [#1469](https://github.com/open-telemetry/opentelemetry-js/pull/1469) chore: bump gcp-metadata ([@dyladan](https://github.com/dyladan)) +* `opentelemetry-exporter-prometheus` + * [#1310](https://github.com/open-telemetry/opentelemetry-js/pull/1310) feat: prometheus serializer ([@legendecas](https://github.com/legendecas)) + * [#1428](https://github.com/open-telemetry/opentelemetry-js/pull/1428) feat: Add missing prometheus exports for ValueRecorder, SumObserver & UpDownSumObserver ([@paulfairless](https://github.com/paulfairless)) +* `opentelemetry-core`, `opentelemetry-tracing` + * [#1344](https://github.com/open-telemetry/opentelemetry-js/pull/1344) feat: introduces ability to suppress tracing via context ([@michaelgoin](https://github.com/michaelgoin)) +* `opentelemetry-api`, `opentelemetry-exporter-collector-proto`, `opentelemetry-plugin-http`, `opentelemetry-semantic-conventions`, `opentelemetry-tracing` + * [#1372](https://github.com/open-telemetry/opentelemetry-js/pull/1372) feat: adding possibility of recording exception ([@obecny](https://github.com/obecny)) +* `opentelemetry-api`, `opentelemetry-core`, `opentelemetry-exporter-collector-grpc`, `opentelemetry-exporter-collector-proto`, `opentelemetry-exporter-collector`, `opentelemetry-metrics` + * [#1446](https://github.com/open-telemetry/opentelemetry-js/pull/1446) Collector split ([@obecny](https://github.com/obecny)) +* `opentelemetry-metrics` + * [#1366](https://github.com/open-telemetry/opentelemetry-js/pull/1366) fix: ignore non-number value on BaseBoundInstrument.update ([@legendecas](https://github.com/legendecas)) +* `opentelemetry-node` + * [#1440](https://github.com/open-telemetry/opentelemetry-js/pull/1440) fix: add Hapi and Koa to default supported plugins ([@carolinee21](https://github.com/carolinee21)) +* `opentelemetry-resources` + * [#1408](https://github.com/open-telemetry/opentelemetry-js/pull/1408) Feat: Migrate EC2 Plugin Resource Detector from IMDSv1 to IMDSv2 ([@EdZou](https://github.com/EdZou)) +* `opentelemetry-core` + * [#1349](https://github.com/open-telemetry/opentelemetry-js/pull/1349) feat: faster span and trace id generation ([@dyladan](https://github.com/dyladan)) +* `opentelemetry-context-async-hooks` + * [#1356](https://github.com/open-telemetry/opentelemetry-js/pull/1356) feat: use a symbol to store patched listeners ([@Flarna](https://github.com/Flarna)) +* `opentelemetry-semantic-conventions` + * [#1407](https://github.com/open-telemetry/opentelemetry-js/pull/1407) semantic conventions for operating system ([@obecny](https://github.com/obecny)) + * [#1409](https://github.com/open-telemetry/opentelemetry-js/pull/1409) removing semantic conventions from code coverage ([@obecny](https://github.com/obecny)) + * [#1388](https://github.com/open-telemetry/opentelemetry-js/pull/1388) chore: transpile semantic conventions to es5 ([@dyladan](https://github.com/dyladan)) + +### :bug: (Bug Fix) + +* `opentelemetry-api`, `opentelemetry-metrics` + * [#1373](https://github.com/open-telemetry/opentelemetry-js/pull/1373) fix: updates ValueRecorder to allow negative values ([@michaelgoin](https://github.com/michaelgoin)) +* `opentelemetry-metrics` + * [#1475](https://github.com/open-telemetry/opentelemetry-js/pull/1475) fix: proper histogram boundaries sort ([@AndrewGrachov](https://github.com/AndrewGrachov)) +* `opentelemetry-core` + * [#1336](https://github.com/open-telemetry/opentelemetry-js/pull/1336) fix: correlation context propagation extract for a single entry ([@rubenvp8510](https://github.com/rubenvp8510)) + * [#1406](https://github.com/open-telemetry/opentelemetry-js/pull/1406) Pass W3C Trace Context test suite at strictness 1 ([@michaelgoin](https://github.com/michaelgoin)) +* `opentelemetry-context-base` + * [#1387](https://github.com/open-telemetry/opentelemetry-js/pull/1387) fix: allow multiple instances of core to interact with context ([@dyladan](https://github.com/dyladan)) + +### :books: (Refine Doc) + +* `opentelemetry-exporter-collector` + * [#1432](https://github.com/open-telemetry/opentelemetry-js/pull/1432) docs(exporter-collector): CollectorTransportNode should be CollectorProtocolNode ([@Hongbo-Miao](https://github.com/Hongbo-Miao)) + * [#1361](https://github.com/open-telemetry/opentelemetry-js/pull/1361) chore: adding info about collector compatible version, removing duplicated doc after merge ([@obecny](https://github.com/obecny)) +* `opentelemetry-metrics` + * [#1427](https://github.com/open-telemetry/opentelemetry-js/pull/1427) chore: fix histogram type documentation ([@TigerHe7](https://github.com/TigerHe7)) +* Other + * [#1431](https://github.com/open-telemetry/opentelemetry-js/pull/1431) Fix typo in document. ([@dengliming](https://github.com/dengliming)) + +#### Committers: 21 + +* Andrew ([@AndrewGrachov](https://github.com/AndrewGrachov)) +* Bartlomiej Obecny ([@obecny](https://github.com/obecny)) +* Cong Zou ([@EdZou](https://github.com/EdZou)) +* Daniel Dyla ([@dyladan](https://github.com/dyladan)) +* Gerhard Stöbich ([@Flarna](https://github.com/Flarna)) +* Hongbo Miao ([@Hongbo-Miao](https://github.com/Hongbo-Miao)) +* Igor Konforti ([@confiq](https://github.com/confiq)) +* John Bley ([@johnbley](https://github.com/johnbley)) +* Jonah Rosenblum ([@jonahrosenblum](https://github.com/jonahrosenblum)) +* Mark Wolff ([@markwolff](https://github.com/markwolff)) +* Matthew Wear ([@mwear](https://github.com/mwear)) +* Michael Goin ([@michaelgoin](https://github.com/michaelgoin)) +* Paul Fairless ([@paulfairless](https://github.com/paulfairless)) +* Reginald McDonald ([@reggiemcdonald](https://github.com/reggiemcdonald)) +* Ruben Vargas Palma ([@rubenvp8510](https://github.com/rubenvp8510)) +* Sergio Regueira ([@sergioregueira](https://github.com/sergioregueira)) +* Tiger He ([@TigerHe7](https://github.com/TigerHe7)) +* [@carolinee21](https://github.com/carolinee21) +* [@dengliming](https://github.com/dengliming) +* [@srjames90](https://github.com/srjames90) +* legendecas ([@legendecas](https://github.com/legendecas)) + ## 0.10.2 ### :rocket: (Enhancement) diff --git a/RELEASING.md b/RELEASING.md index 3b66e9465a..fef907d7ae 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -44,7 +44,7 @@ Since we use `lerna`, we can use [lerna-changelog](https://github.com/lerna/lern #### How to use Pass your [github token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) to generate the changelog automatically. -For security reasons, when you create a Github token, select the permissions: under **repo**, select **Access public repositories**, **commit status**. +For security reasons, when you create a Github token, select the permissions: under **repo**, select **Access public repositories**, **Access commit status**. In your terminal, execute the following command: diff --git a/backwards-compatability/node10/index.ts b/backwards-compatability/node10/index.ts new file mode 100644 index 0000000000..985d78f7d2 --- /dev/null +++ b/backwards-compatability/node10/index.ts @@ -0,0 +1,10 @@ +import {NodeSDK, api} from '@opentelemetry/sdk-node'; +import {ConsoleSpanExporter} from '@opentelemetry/tracing'; + +const sdk = new NodeSDK({ + traceExporter: new ConsoleSpanExporter(), + autoDetectResources: false, +}); +sdk.start(); + +api.trace.getTracer('test'); diff --git a/backwards-compatability/node10/package.json b/backwards-compatability/node10/package.json new file mode 100644 index 0000000000..3b3ff18a11 --- /dev/null +++ b/backwards-compatability/node10/package.json @@ -0,0 +1,20 @@ +{ + "name": "backcompat-node10", + "version": "0.11.0", + "private": true, + "description": "Backwards compatability app for node8 types and the OpenTelemetry Node.js SDK", + "main": "index.js", + "scripts": { + "test:backcompat": "tsc --noEmit index.ts" + }, + "dependencies": { + "@opentelemetry/sdk-node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" + }, + "devDependencies": { + "@types/node": "^10.0.0", + "typescript": "^3.9.7" + }, + "author": "OpenTelemetry Authors", + "license": "Apache-2.0" +} diff --git a/backwards-compatability/node12/index.ts b/backwards-compatability/node12/index.ts new file mode 100644 index 0000000000..985d78f7d2 --- /dev/null +++ b/backwards-compatability/node12/index.ts @@ -0,0 +1,10 @@ +import {NodeSDK, api} from '@opentelemetry/sdk-node'; +import {ConsoleSpanExporter} from '@opentelemetry/tracing'; + +const sdk = new NodeSDK({ + traceExporter: new ConsoleSpanExporter(), + autoDetectResources: false, +}); +sdk.start(); + +api.trace.getTracer('test'); diff --git a/backwards-compatability/node12/package.json b/backwards-compatability/node12/package.json new file mode 100644 index 0000000000..15a77a2acb --- /dev/null +++ b/backwards-compatability/node12/package.json @@ -0,0 +1,20 @@ +{ + "name": "backcompat-node12", + "version": "0.11.0", + "private": true, + "description": "Backwards compatability app for node8 types and the OpenTelemetry Node.js SDK", + "main": "index.js", + "scripts": { + "test:backcompat": "tsc --noEmit index.ts" + }, + "dependencies": { + "@opentelemetry/sdk-node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" + }, + "devDependencies": { + "@types/node": "^12.0.0", + "typescript": "^3.9.7" + }, + "author": "OpenTelemetry Authors", + "license": "Apache-2.0" +} diff --git a/backwards-compatability/node8/index.ts b/backwards-compatability/node8/index.ts new file mode 100644 index 0000000000..985d78f7d2 --- /dev/null +++ b/backwards-compatability/node8/index.ts @@ -0,0 +1,10 @@ +import {NodeSDK, api} from '@opentelemetry/sdk-node'; +import {ConsoleSpanExporter} from '@opentelemetry/tracing'; + +const sdk = new NodeSDK({ + traceExporter: new ConsoleSpanExporter(), + autoDetectResources: false, +}); +sdk.start(); + +api.trace.getTracer('test'); diff --git a/backwards-compatability/node8/package.json b/backwards-compatability/node8/package.json new file mode 100644 index 0000000000..b85b710e38 --- /dev/null +++ b/backwards-compatability/node8/package.json @@ -0,0 +1,20 @@ +{ + "name": "backcompat-node8", + "version": "0.11.0", + "private": true, + "description": "Backwards compatability app for node8 types and the OpenTelemetry Node.js SDK", + "main": "index.js", + "scripts": { + "test:backcompat": "tsc --noEmit index.ts" + }, + "dependencies": { + "@opentelemetry/sdk-node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" + }, + "devDependencies": { + "@types/node": "^8.0.0", + "typescript": "^3.9.7" + }, + "author": "OpenTelemetry Authors", + "license": "Apache-2.0" +} diff --git a/examples/basic-tracer-node/package.json b/examples/basic-tracer-node/package.json index 80ae839410..367696eb50 100644 --- a/examples/basic-tracer-node/package.json +++ b/examples/basic-tracer-node/package.json @@ -1,7 +1,7 @@ { "name": "example-basic-tracer-node", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of using @opentelemetry/tracing in Node.js", "main": "index.js", "scripts": { @@ -24,9 +24,9 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/exporter-jaeger": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/exporter-jaeger": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme" } diff --git a/examples/collector-exporter-node/package.json b/examples/collector-exporter-node/package.json index 49ac196434..23bfcce4a5 100644 --- a/examples/collector-exporter-node/package.json +++ b/examples/collector-exporter-node/package.json @@ -1,7 +1,7 @@ { "name": "example-collector-exporter-node", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of using @opentelemetry/collector-exporter in Node.js", "main": "index.js", "scripts": { @@ -28,13 +28,13 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-collector": "^0.10.2", - "@opentelemetry/exporter-collector-grpc": "^0.10.2", - "@opentelemetry/exporter-collector-proto": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-collector": "^0.11.0", + "@opentelemetry/exporter-collector-grpc": "^0.11.0", + "@opentelemetry/exporter-collector-proto": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme" } diff --git a/examples/grpc-js/package.json b/examples/grpc-js/package.json index 423f8801a4..528f008936 100644 --- a/examples/grpc-js/package.json +++ b/examples/grpc-js/package.json @@ -1,7 +1,7 @@ { "name": "grpc-js-example", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of @grpc/grpc-js integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -29,12 +29,12 @@ }, "dependencies": { "@grpc/grpc-js": "^1.0.5", - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/exporter-jaeger": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/plugin-grpc-js": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/exporter-jaeger": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/plugin-grpc-js": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "google-protobuf": "^3.9.2" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", diff --git a/examples/grpc/package.json b/examples/grpc/package.json index 3dad213e26..1addedb200 100644 --- a/examples/grpc/package.json +++ b/examples/grpc/package.json @@ -1,7 +1,7 @@ { "name": "grpc-example", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of gRPC integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -28,12 +28,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/exporter-jaeger": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/plugin-grpc": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/exporter-jaeger": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/plugin-grpc": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "google-protobuf": "^3.9.2", "grpc": "^1.23.3", "node-pre-gyp": "0.12.0" diff --git a/examples/http/package.json b/examples/http/package.json index c593521cdc..46643457a9 100644 --- a/examples/http/package.json +++ b/examples/http/package.json @@ -1,7 +1,7 @@ { "name": "http-example", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of HTTP integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -28,12 +28,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/exporter-jaeger": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/plugin-http": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/exporter-jaeger": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/plugin-http": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/https/package.json b/examples/https/package.json index ddcfd1c17e..124fb01f7d 100644 --- a/examples/https/package.json +++ b/examples/https/package.json @@ -1,7 +1,7 @@ { "name": "https-example", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of HTTPs integration with OpenTelemetry", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -29,12 +29,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/exporter-jaeger": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/plugin-https": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/exporter-jaeger": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/plugin-https": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/metrics/package.json b/examples/metrics/package.json index 80a6487062..d29f50124c 100644 --- a/examples/metrics/package.json +++ b/examples/metrics/package.json @@ -1,7 +1,7 @@ { "name": "example-metrics", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of using @opentelemetry/metrics", "main": "index.js", "scripts": { @@ -26,9 +26,9 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-prometheus": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2" + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-prometheus": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme" } diff --git a/examples/opentracing-shim/package.json b/examples/opentracing-shim/package.json index 79cfdc38e5..78543f0a08 100644 --- a/examples/opentracing-shim/package.json +++ b/examples/opentracing-shim/package.json @@ -1,7 +1,7 @@ { "name": "opentracing-shim", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of using @opentelemetry/shim-opentracing in Node.js", "main": "index.js", "scripts": { @@ -29,11 +29,11 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/exporter-jaeger": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/shim-opentracing": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/exporter-jaeger": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/shim-opentracing": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "opentracing": "^0.14.4" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", diff --git a/examples/prometheus/package.json b/examples/prometheus/package.json index 3a073fd844..b643727702 100644 --- a/examples/prometheus/package.json +++ b/examples/prometheus/package.json @@ -1,6 +1,6 @@ { "name": "prometheus-example", - "version": "0.10.2", + "version": "0.11.0", "description": "Example of using @opentelemetry/metrics and @opentelemetry/exporter-prometheus", "main": "index.js", "scripts": { @@ -9,7 +9,7 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/exporter-prometheus": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2" + "@opentelemetry/exporter-prometheus": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0" } } diff --git a/examples/tracer-web/package.json b/examples/tracer-web/package.json index f144e0464e..005ca3216a 100644 --- a/examples/tracer-web/package.json +++ b/examples/tracer-web/package.json @@ -1,7 +1,7 @@ { "name": "web-tracer-example", "private": true, - "version": "0.10.2", + "version": "0.11.0", "description": "Example of using @opentelemetry/web in browser", "main": "index.js", "scripts": { @@ -34,16 +34,16 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/context-zone": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-collector": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", + "@opentelemetry/context-zone": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-collector": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", "@opentelemetry/plugin-document-load": "^0.9.0", - "@opentelemetry/plugin-fetch": "^0.10.2", + "@opentelemetry/plugin-fetch": "^0.11.0", "@opentelemetry/plugin-user-interaction": "^0.9.0", - "@opentelemetry/plugin-xml-http-request": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", - "@opentelemetry/web": "^0.10.2" + "@opentelemetry/plugin-xml-http-request": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", + "@opentelemetry/web": "^0.11.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme" } diff --git a/getting-started/example/package.json b/getting-started/example/package.json index f0949e9575..fec7ae8e28 100644 --- a/getting-started/example/package.json +++ b/getting-started/example/package.json @@ -1,6 +1,6 @@ { - "name": "@opentelemetry/getting-started", - "version": "1.0.0", + "name": "@opentelemetry/getting-started-example", + "version": "0.11.0", "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", "main": "app.js", "scripts": { diff --git a/getting-started/monitored-example/package.json b/getting-started/monitored-example/package.json index ed35cae927..2da55c3d77 100644 --- a/getting-started/monitored-example/package.json +++ b/getting-started/monitored-example/package.json @@ -1,6 +1,6 @@ { - "name": "@opentelemetry/getting-started", - "version": "1.0.0", + "name": "@opentelemetry/getting-started-monitored-example", + "version": "0.11.0", "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", "main": "app.js", "scripts": { @@ -9,7 +9,7 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/metrics": "^0.10.2", + "@opentelemetry/metrics": "^0.11.0", "axios": "^0.19.0", "express": "^4.17.1" } diff --git a/getting-started/traced-example/package.json b/getting-started/traced-example/package.json index 1687e52ddd..36b918ebaa 100644 --- a/getting-started/traced-example/package.json +++ b/getting-started/traced-example/package.json @@ -1,6 +1,6 @@ { - "name": "@opentelemetry/getting-started", - "version": "1.0.0", + "name": "@opentelemetry/getting-started-traced-example", + "version": "0.11.0", "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", "main": "app.js", "scripts": { @@ -9,13 +9,13 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/node": "^0.10.2", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/node": "^0.11.0", "@opentelemetry/plugin-express": "^0.9.0", - "@opentelemetry/plugin-http": "^0.10.2", - "@opentelemetry/plugin-https": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/plugin-http": "^0.11.0", + "@opentelemetry/plugin-https": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "axios": "^0.19.0", "express": "^4.17.1" } diff --git a/getting-started/ts-example/package.json b/getting-started/ts-example/package.json index 1f31173308..285a84617f 100644 --- a/getting-started/ts-example/package.json +++ b/getting-started/ts-example/package.json @@ -1,6 +1,6 @@ { - "name": "@opentelemetry/getting-started-ts", - "version": "1.0.0", + "name": "@opentelemetry/getting-started-ts-example", + "version": "0.11.0", "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", "main": "app.ts", "scripts": { @@ -15,14 +15,14 @@ "ts-node": "8.10.2" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-prometheus": "^0.10.2", - "@opentelemetry/exporter-zipkin": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/plugin-http": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-prometheus": "^0.11.0", + "@opentelemetry/exporter-zipkin": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/plugin-http": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "axios": "^0.19.1", "express": "^4.17.1" } diff --git a/integration-tests/propagation-validation-server/package.json b/integration-tests/propagation-validation-server/package.json index 8db10f0108..ba7dd9d59f 100644 --- a/integration-tests/propagation-validation-server/package.json +++ b/integration-tests/propagation-validation-server/package.json @@ -1,6 +1,6 @@ { "name": "propagation-validation-server", - "version": "0.10.2", + "version": "0.11.0", "description": "server for w3c tests", "main": "validation_server.js", "private": true, @@ -8,10 +8,10 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "axios": "0.19.2", "body-parser": "1.19.0", "express": "4.17.1" diff --git a/lerna.json b/lerna.json index addb3cac49..4e651f4a2c 100644 --- a/lerna.json +++ b/lerna.json @@ -3,11 +3,12 @@ "npmClient": "npm", "packages": [ "benchmark/*", + "backwards-compatability/*", "metapackages/*", "packages/*", "integration-tests/*" ], - "version": "0.10.2", + "version": "0.11.0", "changelog": { "repo": "open-telemetry/opentelemetry-js", "labels": { diff --git a/metapackages/plugins-node-all/package.json b/metapackages/plugins-node-all/package.json index e512c07b26..da362a55a6 100644 --- a/metapackages/plugins-node-all/package.json +++ b/metapackages/plugins-node-all/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugins-node-all", - "version": "0.10.2", + "version": "0.11.0", "description": "Metapackage which bundles opentelemetry node core and contrib plugins", "author": "OpenTelemetry Authors", "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", @@ -23,6 +23,6 @@ "@opentelemetry/plugin-pg": "0.9.0", "@opentelemetry/plugin-pg-pool": "0.9.0", "@opentelemetry/plugin-redis": "0.9.0", - "@opentelemetry/plugins-node-core": "^0.10.2" + "@opentelemetry/plugins-node-core": "^0.11.0" } } diff --git a/metapackages/plugins-node-core/package.json b/metapackages/plugins-node-core/package.json index d504b4bf1d..c5572c166f 100644 --- a/metapackages/plugins-node-core/package.json +++ b/metapackages/plugins-node-core/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugins-node-core", - "version": "0.10.2", + "version": "0.11.0", "description": "Metapackage which bundles all opentelemetry node core plugins", "author": "OpenTelemetry Authors", "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", @@ -16,9 +16,9 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/plugin-grpc": "^0.10.2", - "@opentelemetry/plugin-grpc-js": "^0.10.2", - "@opentelemetry/plugin-http": "^0.10.2", - "@opentelemetry/plugin-https": "^0.10.2" + "@opentelemetry/plugin-grpc": "^0.11.0", + "@opentelemetry/plugin-grpc-js": "^0.11.0", + "@opentelemetry/plugin-http": "^0.11.0", + "@opentelemetry/plugin-https": "^0.11.0" } } diff --git a/metapackages/plugins-web-core/package.json b/metapackages/plugins-web-core/package.json index 1c9f460697..309f86b593 100644 --- a/metapackages/plugins-web-core/package.json +++ b/metapackages/plugins-web-core/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugins-web-core", - "version": "0.10.2", + "version": "0.11.0", "description": "Metapackage which bundles all opentelemetry web core plugins", "author": "OpenTelemetry Authors", "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", @@ -16,6 +16,6 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/plugin-xml-http-request": "^0.10.2" + "@opentelemetry/plugin-xml-http-request": "^0.11.0" } } diff --git a/package.json b/package.json index 72645861f4..22057d4771 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "compile": "lerna run compile", "test": "lerna run test", "test:browser": "lerna run test:browser", + "test:backcompat": "lerna run test:backcompat", "bootstrap": "lerna bootstrap", "bump": "lerna publish", "codecov": "lerna run codecov", diff --git a/packages/opentelemetry-api/package.json b/packages/opentelemetry-api/package.json index f3f5761e5c..3cd3b7aaff 100644 --- a/packages/opentelemetry-api/package.json +++ b/packages/opentelemetry-api/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/api", - "version": "0.10.2", + "version": "0.11.0", "description": "Public API for OpenTelemetry", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -52,11 +52,12 @@ "access": "public" }, "dependencies": { - "@opentelemetry/context-base": "^0.10.2" + "@opentelemetry/context-base": "^0.11.0" }, "devDependencies": { "@types/mocha": "8.0.2", "@types/node": "14.0.27", + "@types/sinon": "4.3.1", "@types/webpack-env": "1.15.2", "codecov": "3.7.2", "gts": "2.0.2", @@ -70,6 +71,7 @@ "linkinator": "2.1.1", "mocha": "7.2.0", "nyc": "15.1.0", + "sinon": "9.0.3", "ts-loader": "8.0.2", "ts-mocha": "7.0.0", "typedoc": "0.18.0", diff --git a/packages/opentelemetry-api/src/api/global-utils.ts b/packages/opentelemetry-api/src/api/global-utils.ts index a294f345d5..407399c8fb 100644 --- a/packages/opentelemetry-api/src/api/global-utils.ts +++ b/packages/opentelemetry-api/src/api/global-utils.ts @@ -15,7 +15,7 @@ */ import { ContextManager } from '@opentelemetry/context-base'; -import { HttpTextPropagator } from '../context/propagation/HttpTextPropagator'; +import { TextMapPropagator } from '../context/propagation/TextMapPropagator'; import { MeterProvider } from '../metrics/MeterProvider'; import { TracerProvider } from '../trace/tracer_provider'; import { _globalThis } from '../platform'; @@ -35,7 +35,7 @@ type Get = (version: number) => T; type OtelGlobal = Partial<{ [GLOBAL_CONTEXT_MANAGER_API_KEY]: Get; [GLOBAL_METRICS_API_KEY]: Get; - [GLOBAL_PROPAGATION_API_KEY]: Get; + [GLOBAL_PROPAGATION_API_KEY]: Get; [GLOBAL_TRACE_API_KEY]: Get; }>; diff --git a/packages/opentelemetry-api/src/api/propagation.ts b/packages/opentelemetry-api/src/api/propagation.ts index 989e497b66..92a046a659 100644 --- a/packages/opentelemetry-api/src/api/propagation.ts +++ b/packages/opentelemetry-api/src/api/propagation.ts @@ -16,8 +16,8 @@ import { Context } from '@opentelemetry/context-base'; import { defaultGetter, GetterFunction } from '../context/propagation/getter'; -import { HttpTextPropagator } from '../context/propagation/HttpTextPropagator'; -import { NOOP_HTTP_TEXT_PROPAGATOR } from '../context/propagation/NoopHttpTextPropagator'; +import { TextMapPropagator } from '../context/propagation/TextMapPropagator'; +import { NOOP_TEXT_MAP_PROPAGATOR } from '../context/propagation/NoopTextMapPropagator'; import { defaultSetter, SetterFunction } from '../context/propagation/setter'; import { ContextAPI } from './context'; import { @@ -50,9 +50,7 @@ export class PropagationAPI { /** * Set the current propagator. Returns the initialized propagator */ - public setGlobalPropagator( - propagator: HttpTextPropagator - ): HttpTextPropagator { + public setGlobalPropagator(propagator: TextMapPropagator): TextMapPropagator { if (_global[GLOBAL_PROPAGATION_API_KEY]) { // global propagator has already been set return this._getGlobalPropagator(); @@ -61,7 +59,7 @@ export class PropagationAPI { _global[GLOBAL_PROPAGATION_API_KEY] = makeGetter( API_BACKWARDS_COMPATIBILITY_VERSION, propagator, - NOOP_HTTP_TEXT_PROPAGATOR + NOOP_TEXT_MAP_PROPAGATOR ); return propagator; @@ -102,11 +100,11 @@ export class PropagationAPI { delete _global[GLOBAL_PROPAGATION_API_KEY]; } - private _getGlobalPropagator(): HttpTextPropagator { + private _getGlobalPropagator(): TextMapPropagator { return ( _global[GLOBAL_PROPAGATION_API_KEY]?.( API_BACKWARDS_COMPATIBILITY_VERSION - ) ?? NOOP_HTTP_TEXT_PROPAGATOR + ) ?? NOOP_TEXT_MAP_PROPAGATOR ); } } diff --git a/packages/opentelemetry-api/src/api/trace.ts b/packages/opentelemetry-api/src/api/trace.ts index 0ba58af77a..f0c2005577 100644 --- a/packages/opentelemetry-api/src/api/trace.ts +++ b/packages/opentelemetry-api/src/api/trace.ts @@ -15,8 +15,10 @@ */ import { NOOP_TRACER_PROVIDER } from '../trace/NoopTracerProvider'; +import { ProxyTracerProvider } from '../trace/ProxyTracerProvider'; import { Tracer } from '../trace/tracer'; import { TracerProvider } from '../trace/tracer_provider'; +import { isSpanContextValid } from '../trace/spancontext-utils'; import { API_BACKWARDS_COMPATIBILITY_VERSION, GLOBAL_TRACE_API_KEY, @@ -30,6 +32,8 @@ import { export class TraceAPI { private static _instance?: TraceAPI; + private _proxyTracerProvider = new ProxyTracerProvider(); + /** Empty private constructor prevents end users from constructing a new instance of the API */ private constructor() {} @@ -51,9 +55,11 @@ export class TraceAPI { return this.getTracerProvider(); } + this._proxyTracerProvider.setDelegate(provider); + _global[GLOBAL_TRACE_API_KEY] = makeGetter( API_BACKWARDS_COMPATIBILITY_VERSION, - provider, + this._proxyTracerProvider, NOOP_TRACER_PROVIDER ); @@ -66,7 +72,7 @@ export class TraceAPI { public getTracerProvider(): TracerProvider { return ( _global[GLOBAL_TRACE_API_KEY]?.(API_BACKWARDS_COMPATIBILITY_VERSION) ?? - NOOP_TRACER_PROVIDER + this._proxyTracerProvider ); } @@ -80,5 +86,8 @@ export class TraceAPI { /** Remove the global tracer provider */ public disable() { delete _global[GLOBAL_TRACE_API_KEY]; + this._proxyTracerProvider = new ProxyTracerProvider(); } + + public isSpanContextValid = isSpanContextValid; } diff --git a/packages/opentelemetry-api/src/common/Exception.ts b/packages/opentelemetry-api/src/common/Exception.ts new file mode 100644 index 0000000000..0fa98e741a --- /dev/null +++ b/packages/opentelemetry-api/src/common/Exception.ts @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +interface ExceptionWithCode { + code: string; + name?: string; + message?: string; + stack?: string; +} + +interface ExceptionWithMessage { + code?: string; + message: string; + name?: string; + stack?: string; +} + +interface ExceptionWithName { + code?: string; + message?: string; + name: string; + stack?: string; +} + +/** + * Defines Exception. + * + * string or an object with one of (message or name or code) and optional stack + */ +export type Exception = + | ExceptionWithCode + | ExceptionWithMessage + | ExceptionWithName + | string; diff --git a/packages/opentelemetry-api/src/context/propagation/NoopHttpTextPropagator.ts b/packages/opentelemetry-api/src/context/propagation/NoopTextMapPropagator.ts similarity index 79% rename from packages/opentelemetry-api/src/context/propagation/NoopHttpTextPropagator.ts rename to packages/opentelemetry-api/src/context/propagation/NoopTextMapPropagator.ts index 7782ba30b9..c2155fadc9 100644 --- a/packages/opentelemetry-api/src/context/propagation/NoopHttpTextPropagator.ts +++ b/packages/opentelemetry-api/src/context/propagation/NoopTextMapPropagator.ts @@ -15,12 +15,12 @@ */ import { Context } from '@opentelemetry/context-base'; -import { HttpTextPropagator } from './HttpTextPropagator'; +import { TextMapPropagator } from './TextMapPropagator'; /** - * No-op implementations of {@link HttpTextPropagator}. + * No-op implementations of {@link TextMapPropagator}. */ -export class NoopHttpTextPropagator implements HttpTextPropagator { +export class NoopTextMapPropagator implements TextMapPropagator { /** Noop inject function does nothing */ inject(context: Context, carrier: unknown, setter: Function): void {} /** Noop extract function does nothing and returns the input context */ @@ -29,4 +29,4 @@ export class NoopHttpTextPropagator implements HttpTextPropagator { } } -export const NOOP_HTTP_TEXT_PROPAGATOR = new NoopHttpTextPropagator(); +export const NOOP_TEXT_MAP_PROPAGATOR = new NoopTextMapPropagator(); diff --git a/packages/opentelemetry-api/src/context/propagation/HttpTextPropagator.ts b/packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts similarity index 95% rename from packages/opentelemetry-api/src/context/propagation/HttpTextPropagator.ts rename to packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts index 6c6f61d668..f56112495d 100644 --- a/packages/opentelemetry-api/src/context/propagation/HttpTextPropagator.ts +++ b/packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts @@ -29,11 +29,11 @@ import { GetterFunction } from './getter'; * usually implemented via library-specific request interceptors, where the * client-side injects values and the server-side extracts them. */ -export interface HttpTextPropagator { +export interface TextMapPropagator { /** * Injects values from a given `Context` into a carrier. * - * OpenTelemetry defines a common set of format values (HttpTextPropagator), + * OpenTelemetry defines a common set of format values (TextMapPropagator), * and each has an expected `carrier` type. * * @param context the Context from which to extract values to transmit over diff --git a/packages/opentelemetry-api/src/index.ts b/packages/opentelemetry-api/src/index.ts index f9bcf40664..0a7e8bbd85 100644 --- a/packages/opentelemetry-api/src/index.ts +++ b/packages/opentelemetry-api/src/index.ts @@ -14,11 +14,12 @@ * limitations under the License. */ +export * from './common/Exception'; export * from './common/Logger'; export * from './common/Time'; export * from './context/propagation/getter'; -export * from './context/propagation/HttpTextPropagator'; -export * from './context/propagation/NoopHttpTextPropagator'; +export * from './context/propagation/TextMapPropagator'; +export * from './context/propagation/NoopTextMapPropagator'; export * from './context/propagation/setter'; export * from './correlation_context/CorrelationContext'; export * from './correlation_context/EntryValue'; @@ -39,6 +40,8 @@ export * from './trace/link'; export * from './trace/NoopSpan'; export * from './trace/NoopTracer'; export * from './trace/NoopTracerProvider'; +export * from './trace/ProxyTracer'; +export * from './trace/ProxyTracerProvider'; export * from './trace/Sampler'; export * from './trace/SamplingResult'; export * from './trace/span_context'; @@ -52,6 +55,12 @@ export * from './trace/trace_state'; export * from './trace/tracer_provider'; export * from './trace/tracer'; +export { + INVALID_SPANID, + INVALID_TRACEID, + INVALID_SPAN_CONTEXT, +} from './trace/spancontext-utils'; + export { Context } from '@opentelemetry/context-base'; import { ContextAPI } from './api/context'; diff --git a/packages/opentelemetry-api/src/metrics/BoundInstrument.ts b/packages/opentelemetry-api/src/metrics/BoundInstrument.ts index 4ffbd143c7..0d5554771e 100644 --- a/packages/opentelemetry-api/src/metrics/BoundInstrument.ts +++ b/packages/opentelemetry-api/src/metrics/BoundInstrument.ts @@ -14,9 +14,6 @@ * limitations under the License. */ -import { CorrelationContext } from '../correlation_context/CorrelationContext'; -import { SpanContext } from '../trace/span_context'; - /** An Instrument for Counter Metric. */ export interface BoundCounter { /** @@ -31,18 +28,8 @@ export interface BoundValueRecorder { /** * Records the given value to this value recorder. * @param value to record. - * @param correlationContext the correlationContext associated with the - * values. - * @param spanContext the {@link SpanContext} that identifies the {@link Span} - * which the values are associated with. */ record(value: number): void; - record(value: number, correlationContext: CorrelationContext): void; - record( - value: number, - correlationContext: CorrelationContext, - spanContext: SpanContext - ): void; } /** An Instrument for Base Observer */ diff --git a/packages/opentelemetry-api/src/metrics/Metric.ts b/packages/opentelemetry-api/src/metrics/Metric.ts index ee7fd29ab4..0022c2a5b6 100644 --- a/packages/opentelemetry-api/src/metrics/Metric.ts +++ b/packages/opentelemetry-api/src/metrics/Metric.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { CorrelationContext } from '../correlation_context/CorrelationContext'; -import { SpanContext } from '../trace/span_context'; import { BoundBaseObserver, BoundCounter, @@ -51,12 +49,6 @@ export interface MetricOptions { */ disabled?: boolean; - /** - * (Measure only, default true) Asserts that this metric will only accept - * non-negative values (e.g. disk usage). - */ - absolute?: boolean; - /** * Indicates the type of the recorded value. * @default {@link ValueType.DOUBLE} @@ -148,19 +140,6 @@ export interface ValueRecorder extends UnboundMetric { * Records the given value to this value recorder. */ record(value: number, labels?: Labels): void; - - record( - value: number, - labels: Labels, - correlationContext: CorrelationContext - ): void; - - record( - value: number, - labels: Labels, - correlationContext: CorrelationContext, - spanContext: SpanContext - ): void; } /** Base interface for the Observer metrics. */ diff --git a/packages/opentelemetry-api/src/metrics/NoopMeter.ts b/packages/opentelemetry-api/src/metrics/NoopMeter.ts index d09a6ba742..765a0fd8ae 100644 --- a/packages/opentelemetry-api/src/metrics/NoopMeter.ts +++ b/packages/opentelemetry-api/src/metrics/NoopMeter.ts @@ -142,19 +142,8 @@ export class NoopCounterMetric export class NoopValueRecorderMetric extends NoopMetric implements ValueRecorder { - record( - value: number, - labels: Labels, - correlationContext?: CorrelationContext, - spanContext?: SpanContext - ) { - if (typeof correlationContext === 'undefined') { - this.bind(labels).record(value); - } else if (typeof spanContext === 'undefined') { - this.bind(labels).record(value, correlationContext); - } else { - this.bind(labels).record(value, correlationContext, spanContext); - } + record(value: number, labels: Labels) { + this.bind(labels).record(value); } } diff --git a/packages/opentelemetry-api/src/trace/NoopSpan.ts b/packages/opentelemetry-api/src/trace/NoopSpan.ts index a2a3bcef9f..5f599dfda9 100644 --- a/packages/opentelemetry-api/src/trace/NoopSpan.ts +++ b/packages/opentelemetry-api/src/trace/NoopSpan.ts @@ -14,20 +14,13 @@ * limitations under the License. */ +import { Exception } from '../common/Exception'; import { TimeInput } from '../common/Time'; import { Attributes } from './attributes'; import { Span } from './span'; import { SpanContext } from './span_context'; import { Status } from './status'; -import { TraceFlags } from './trace_flags'; - -export const INVALID_TRACE_ID = '0'; -export const INVALID_SPAN_ID = '0'; -const INVALID_SPAN_CONTEXT: SpanContext = { - traceId: INVALID_TRACE_ID, - spanId: INVALID_SPAN_ID, - traceFlags: TraceFlags.NONE, -}; +import { INVALID_SPAN_CONTEXT } from './spancontext-utils'; /** * The NoopSpan is the default {@link Span} that is used when no Span @@ -76,6 +69,9 @@ export class NoopSpan implements Span { isRecording(): boolean { return false; } + + // By default does nothing + recordException(exception: Exception, time?: TimeInput): void {} } export const NOOP_SPAN = new NoopSpan(); diff --git a/packages/opentelemetry-api/src/trace/ProxyTracer.ts b/packages/opentelemetry-api/src/trace/ProxyTracer.ts new file mode 100644 index 0000000000..e2216eed5e --- /dev/null +++ b/packages/opentelemetry-api/src/trace/ProxyTracer.ts @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Span, SpanOptions, Tracer } from '..'; +import { NOOP_TRACER } from './NoopTracer'; +import { ProxyTracerProvider } from './ProxyTracerProvider'; + +/** + * Proxy tracer provided by the proxy tracer provider + */ +export class ProxyTracer implements Tracer { + // When a real implementation is provided, this will be it + private _delegate?: Tracer; + + constructor( + private _provider: ProxyTracerProvider, + public readonly name: string, + public readonly version?: string + ) {} + + getCurrentSpan(): Span | undefined { + return this._getTracer().getCurrentSpan(); + } + + startSpan(name: string, options?: SpanOptions): Span { + return this._getTracer().startSpan(name, options); + } + + withSpan ReturnType>( + span: Span, + fn: T + ): ReturnType { + return this._getTracer().withSpan(span, fn); + } + + bind(target: T, span?: Span): T { + return this._getTracer().bind(target, span); + } + + /** + * Try to get a tracer from the proxy tracer provider. + * If the proxy tracer provider has no delegate, return a noop tracer. + */ + private _getTracer() { + if (this._delegate) { + return this._delegate; + } + + const tracer = this._provider.getDelegateTracer(this.name, this.version); + + if (!tracer) { + return NOOP_TRACER; + } + + this._delegate = tracer; + return this._delegate; + } +} diff --git a/packages/opentelemetry-api/src/trace/ProxyTracerProvider.ts b/packages/opentelemetry-api/src/trace/ProxyTracerProvider.ts new file mode 100644 index 0000000000..e124dc9506 --- /dev/null +++ b/packages/opentelemetry-api/src/trace/ProxyTracerProvider.ts @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Tracer } from './tracer'; +import { TracerProvider } from './tracer_provider'; +import { ProxyTracer } from './ProxyTracer'; +import { NOOP_TRACER_PROVIDER } from './NoopTracerProvider'; + +/** + * Tracer provider which provides {@link ProxyTracer}s. + * + * Before a delegate is set, tracers provided are NoOp. + * When a delegate is set, traces are provided from the delegate. + * When a delegate is set after tracers have already been provided, + * all tracers already provided will use the provided delegate implementation. + */ +export class ProxyTracerProvider implements TracerProvider { + private _delegate?: TracerProvider; + + /** + * Get a {@link ProxyTracer} + */ + getTracer(name: string, version?: string): Tracer { + return ( + this.getDelegateTracer(name, version) ?? + new ProxyTracer(this, name, version) + ); + } + + getDelegate(): TracerProvider { + return this._delegate ?? NOOP_TRACER_PROVIDER; + } + + /** + * Set the delegate tracer provider + */ + setDelegate(delegate: TracerProvider) { + this._delegate = delegate; + } + + getDelegateTracer(name: string, version?: string): Tracer | undefined { + return this._delegate?.getTracer(name, version); + } +} diff --git a/packages/opentelemetry-api/src/trace/span.ts b/packages/opentelemetry-api/src/trace/span.ts index 13124fff3c..966cd1a95d 100644 --- a/packages/opentelemetry-api/src/trace/span.ts +++ b/packages/opentelemetry-api/src/trace/span.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Exception } from '../common/Exception'; import { Attributes } from './attributes'; import { SpanContext } from './span_context'; import { Status } from './status'; @@ -114,4 +115,12 @@ export interface Span { * with the `AddEvent` operation and attributes using `setAttributes`. */ isRecording(): boolean; + + /** + * Sets exception as a span event + * @param exception the exception the only accepted values are string or Error + * @param [time] the time to set as Span's event time. If not provided, + * use the current time. + */ + recordException(exception: Exception, time?: TimeInput): void; } diff --git a/packages/opentelemetry-core/src/trace/spancontext-utils.ts b/packages/opentelemetry-api/src/trace/spancontext-utils.ts similarity index 55% rename from packages/opentelemetry-core/src/trace/spancontext-utils.ts rename to packages/opentelemetry-api/src/trace/spancontext-utils.ts index 31719aa6e1..ebcc3ce24f 100644 --- a/packages/opentelemetry-core/src/trace/spancontext-utils.ts +++ b/packages/opentelemetry-api/src/trace/spancontext-utils.ts @@ -13,24 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { SpanContext } from './span_context'; +import { TraceFlags } from './trace_flags'; -import { SpanContext, TraceFlags } from '@opentelemetry/api'; - -export const INVALID_SPANID = '0'; -export const INVALID_TRACEID = '0'; +const VALID_TRACEID_REGEX = /^([0-9a-f]{32})$/i; +const VALID_SPANID_REGEX = /^[0-9a-f]{16}$/i; +export const INVALID_SPANID = '0000000000000000'; +export const INVALID_TRACEID = '00000000000000000000000000000000'; export const INVALID_SPAN_CONTEXT: SpanContext = { traceId: INVALID_TRACEID, spanId: INVALID_SPANID, traceFlags: TraceFlags.NONE, }; +export function isValidTraceId(traceId: string): boolean { + return VALID_TRACEID_REGEX.test(traceId) && traceId !== INVALID_TRACEID; +} + +export function isValidSpanId(spanId: string): boolean { + return VALID_SPANID_REGEX.test(spanId) && spanId !== INVALID_SPANID; +} + /** * Returns true if this {@link SpanContext} is valid. * @return true if this {@link SpanContext} is valid. */ -export function isValid(spanContext: SpanContext): boolean { +export function isSpanContextValid(spanContext: SpanContext): boolean { return ( - spanContext.traceId !== INVALID_TRACEID && - spanContext.spanId !== INVALID_SPANID + isValidTraceId(spanContext.traceId) && isValidSpanId(spanContext.spanId) ); } diff --git a/packages/opentelemetry-api/src/version.ts b/packages/opentelemetry-api/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-api/src/version.ts +++ b/packages/opentelemetry-api/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-api/test/noop-implementations/noop-span.test.ts b/packages/opentelemetry-api/test/noop-implementations/noop-span.test.ts index 177d60d7bf..8d224123a4 100644 --- a/packages/opentelemetry-api/test/noop-implementations/noop-span.test.ts +++ b/packages/opentelemetry-api/test/noop-implementations/noop-span.test.ts @@ -17,8 +17,8 @@ import * as assert from 'assert'; import { CanonicalCode, - INVALID_SPAN_ID, - INVALID_TRACE_ID, + INVALID_SPANID, + INVALID_TRACEID, NoopSpan, TraceFlags, } from '../../src'; @@ -45,8 +45,8 @@ describe('NoopSpan', () => { assert.ok(!span.isRecording()); assert.deepStrictEqual(span.context(), { - traceId: INVALID_TRACE_ID, - spanId: INVALID_SPAN_ID, + traceId: INVALID_TRACEID, + spanId: INVALID_SPANID, traceFlags: TraceFlags.NONE, }); span.end(); diff --git a/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts b/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts new file mode 100644 index 0000000000..7a110cbfa3 --- /dev/null +++ b/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts @@ -0,0 +1,129 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { + NoopSpan, + NOOP_SPAN, + ProxyTracerProvider, + SpanKind, + TracerProvider, + ProxyTracer, + Tracer, + Span, + NoopTracer, +} from '../../src'; + +describe('ProxyTracer', () => { + let provider: ProxyTracerProvider; + + beforeEach(() => { + provider = new ProxyTracerProvider(); + }); + + describe('when no delegate is set', () => { + it('should return proxy tracers', () => { + const tracer = provider.getTracer('test'); + + assert.ok(tracer instanceof ProxyTracer); + }); + + it('startSpan should return Noop Spans', () => { + const tracer = provider.getTracer('test'); + + assert.deepStrictEqual(tracer.startSpan('span-name'), NOOP_SPAN); + assert.deepStrictEqual( + tracer.startSpan('span-name1', { kind: SpanKind.CLIENT }), + NOOP_SPAN + ); + assert.deepStrictEqual( + tracer.startSpan('span-name2', { + kind: SpanKind.CLIENT, + }), + NOOP_SPAN + ); + + assert.deepStrictEqual(tracer.getCurrentSpan(), NOOP_SPAN); + }); + }); + + describe('when delegate is set before getTracer', () => { + let delegate: TracerProvider; + const sandbox = sinon.createSandbox(); + let getTracerStub: sinon.SinonStub; + + beforeEach(() => { + getTracerStub = sandbox.stub().returns(new NoopTracer()); + delegate = { + getTracer: getTracerStub, + }; + provider.setDelegate(delegate); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return tracers directly from the delegate', () => { + const tracer = provider.getTracer('test', 'v0'); + + sandbox.assert.calledOnce(getTracerStub); + assert.strictEqual(getTracerStub.firstCall.returnValue, tracer); + assert.deepEqual(getTracerStub.firstCall.args, ['test', 'v0']); + }); + }); + + describe('when delegate is set after getTracer', () => { + let tracer: Tracer; + let delegate: TracerProvider; + let delegateSpan: Span; + let delegateTracer: Tracer; + + beforeEach(() => { + delegateSpan = new NoopSpan(); + delegateTracer = { + bind(target) { + return target; + }, + getCurrentSpan() { + return delegateSpan; + }, + startSpan() { + return delegateSpan; + }, + withSpan(span, fn) { + return fn(); + }, + }; + + tracer = provider.getTracer('test'); + + delegate = { + getTracer() { + return delegateTracer; + }, + }; + provider.setDelegate(delegate); + }); + + it('should create spans using the delegate tracer', () => { + const span = tracer.startSpan('test'); + + assert.strictEqual(span, delegateSpan); + }); + }); +}); diff --git a/packages/opentelemetry-core/test/trace/spancontext-utils.test.ts b/packages/opentelemetry-api/test/trace/spancontext-utils.test.ts similarity index 85% rename from packages/opentelemetry-core/test/trace/spancontext-utils.test.ts rename to packages/opentelemetry-api/test/trace/spancontext-utils.test.ts index 9ac47df565..e194f1d38b 100644 --- a/packages/opentelemetry-core/test/trace/spancontext-utils.test.ts +++ b/packages/opentelemetry-api/test/trace/spancontext-utils.test.ts @@ -16,7 +16,7 @@ import * as assert from 'assert'; import * as context from '../../src/trace/spancontext-utils'; -import { TraceFlags } from '@opentelemetry/api'; +import { TraceFlags } from '../../src'; describe('spancontext-utils', () => { it('should return true for valid spancontext', () => { @@ -25,7 +25,7 @@ describe('spancontext-utils', () => { spanId: '6e0c63257de34c92', traceFlags: TraceFlags.NONE, }; - assert.ok(context.isValid(spanContext)); + assert.ok(context.isSpanContextValid(spanContext)); }); it('should return false when traceId is invalid', () => { @@ -34,7 +34,7 @@ describe('spancontext-utils', () => { spanId: '6e0c63257de34c92', traceFlags: TraceFlags.NONE, }; - assert.ok(!context.isValid(spanContext)); + assert.ok(!context.isSpanContextValid(spanContext)); }); it('should return false when spanId is invalid', () => { @@ -43,7 +43,7 @@ describe('spancontext-utils', () => { spanId: context.INVALID_SPANID, traceFlags: TraceFlags.NONE, }; - assert.ok(!context.isValid(spanContext)); + assert.ok(!context.isSpanContextValid(spanContext)); }); it('should return false when traceId & spanId is invalid', () => { @@ -52,6 +52,6 @@ describe('spancontext-utils', () => { spanId: context.INVALID_SPANID, traceFlags: TraceFlags.NONE, }; - assert.ok(!context.isValid(spanContext)); + assert.ok(!context.isSpanContextValid(spanContext)); }); }); diff --git a/packages/opentelemetry-context-async-hooks/package.json b/packages/opentelemetry-context-async-hooks/package.json index 1233454123..9817cf9285 100644 --- a/packages/opentelemetry-context-async-hooks/package.json +++ b/packages/opentelemetry-context-async-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-async-hooks", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry AsyncHooks-based Context Manager", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -55,6 +55,6 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/context-base": "^0.10.2" + "@opentelemetry/context-base": "^0.11.0" } } diff --git a/packages/opentelemetry-context-async-hooks/src/version.ts b/packages/opentelemetry-context-async-hooks/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-context-async-hooks/src/version.ts +++ b/packages/opentelemetry-context-async-hooks/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-context-base/package.json b/packages/opentelemetry-context-base/package.json index 908d83c88b..35f9ac4375 100644 --- a/packages/opentelemetry-context-base/package.json +++ b/packages/opentelemetry-context-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-base", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Base Context Manager", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-context-base/src/version.ts b/packages/opentelemetry-context-base/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-context-base/src/version.ts +++ b/packages/opentelemetry-context-base/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-context-zone-peer-dep/package.json b/packages/opentelemetry-context-zone-peer-dep/package.json index 9d82bd4259..4b87f66514 100644 --- a/packages/opentelemetry-context-zone-peer-dep/package.json +++ b/packages/opentelemetry-context-zone-peer-dep/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-zone-peer-dep", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Context Zone with peer dependency for zone.js", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -71,7 +71,7 @@ "zone.js": "0.10.3" }, "dependencies": { - "@opentelemetry/context-base": "^0.10.2" + "@opentelemetry/context-base": "^0.11.0" }, "peerDependencies": { "zone.js": "^0.10.2" diff --git a/packages/opentelemetry-context-zone-peer-dep/src/version.ts b/packages/opentelemetry-context-zone-peer-dep/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-context-zone-peer-dep/src/version.ts +++ b/packages/opentelemetry-context-zone-peer-dep/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-context-zone/package.json b/packages/opentelemetry-context-zone/package.json index 7d1fb7113d..835d2f37ef 100644 --- a/packages/opentelemetry-context-zone/package.json +++ b/packages/opentelemetry-context-zone/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-zone", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Context Zone", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -65,7 +65,7 @@ "webpack-merge": "5.1.1" }, "dependencies": { - "@opentelemetry/context-zone-peer-dep": "^0.10.2", + "@opentelemetry/context-zone-peer-dep": "^0.11.0", "zone.js": "^0.10.2" }, "sideEffects": true diff --git a/packages/opentelemetry-context-zone/src/version.ts b/packages/opentelemetry-context-zone/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-context-zone/src/version.ts +++ b/packages/opentelemetry-context-zone/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index f0b2832e06..01274d37d0 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/core", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Core provides default and no-op implementations of the OpenTelemetry types for trace and metrics", "main": "build/src/index.js", "browser": { @@ -77,8 +77,8 @@ "webpack": "4.44.1" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", "semver": "^7.1.3" } } diff --git a/packages/opentelemetry-core/src/context/context.ts b/packages/opentelemetry-core/src/context/context.ts index c3a6dc33f5..6edbfa0a43 100644 --- a/packages/opentelemetry-core/src/context/context.ts +++ b/packages/opentelemetry-core/src/context/context.ts @@ -26,6 +26,13 @@ export const ACTIVE_SPAN_KEY = Context.createKey( const EXTRACTED_SPAN_CONTEXT_KEY = Context.createKey( 'OpenTelemetry Context Key EXTRACTED_SPAN_CONTEXT' ); +/** + * Shared key for indicating if instrumentation should be suppressed beyond + * this current scope. + */ +export const SUPPRESS_INSTRUMENTATION_KEY = Context.createKey( + 'OpenTelemetry Context Key SUPPRESS_INSTRUMENTATION' +); /** * Return the active span if one exists @@ -84,3 +91,33 @@ export function getParentSpanContext( ): SpanContext | undefined { return getActiveSpan(context)?.context() || getExtractedSpanContext(context); } + +/** + * Sets value on context to indicate that instrumentation should + * be suppressed beyond this current scope. + * + * @param context context to set the suppress instrumentation value on. + */ +export function suppressInstrumentation(context: Context): Context { + return context.setValue(SUPPRESS_INSTRUMENTATION_KEY, true); +} + +/** + * Sets value on context to indicate that instrumentation should + * no-longer be suppressed beyond this current scope. + * + * @param context context to set the suppress instrumentation value on. + */ +export function unsuppressInstrumentation(context: Context): Context { + return context.setValue(SUPPRESS_INSTRUMENTATION_KEY, false); +} + +/** + * Return current suppress instrumentation value for the given context, + * if it exists. + * + * @param context context check for the suppress instrumentation value. + */ +export function isInstrumentationSuppressed(context: Context): boolean { + return Boolean(context.getValue(SUPPRESS_INSTRUMENTATION_KEY)); +} diff --git a/packages/opentelemetry-core/src/context/propagation/B3Propagator.ts b/packages/opentelemetry-core/src/context/propagation/B3Propagator.ts index 129e457cb6..43da0b1189 100644 --- a/packages/opentelemetry-core/src/context/propagation/B3Propagator.ts +++ b/packages/opentelemetry-core/src/context/propagation/B3Propagator.ts @@ -17,7 +17,7 @@ import { Context, GetterFunction, - HttpTextPropagator, + TextMapPropagator, SetterFunction, TraceFlags, } from '@opentelemetry/api'; @@ -120,7 +120,7 @@ function getTraceFlags( * Propagator for the B3 HTTP header format. * Based on: https://github.com/openzipkin/b3-propagation */ -export class B3Propagator implements HttpTextPropagator { +export class B3Propagator implements TextMapPropagator { inject(context: Context, carrier: unknown, setter: SetterFunction) { const spanContext = getParentSpanContext(context); if (!spanContext) return; diff --git a/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts b/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts index c5fe4a8e7d..2ba75607d6 100644 --- a/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts +++ b/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts @@ -17,7 +17,7 @@ import { Context, GetterFunction, - HttpTextPropagator, + TextMapPropagator, SetterFunction, SpanContext, TraceFlags, @@ -83,7 +83,7 @@ export function parseTraceParent(traceParent: string): SpanContext | null { * Based on the Trace Context specification: * https://www.w3.org/TR/trace-context/ */ -export class HttpTraceContext implements HttpTextPropagator { +export class HttpTraceContext implements TextMapPropagator { inject(context: Context, carrier: unknown, setter: SetterFunction) { const spanContext = getParentSpanContext(context); if (!spanContext) return; diff --git a/packages/opentelemetry-core/src/context/propagation/composite.ts b/packages/opentelemetry-core/src/context/propagation/composite.ts index 8b5d5d0857..e0adfb585e 100644 --- a/packages/opentelemetry-core/src/context/propagation/composite.ts +++ b/packages/opentelemetry-core/src/context/propagation/composite.ts @@ -17,7 +17,7 @@ import { Context, GetterFunction, - HttpTextPropagator, + TextMapPropagator, Logger, SetterFunction, } from '@opentelemetry/api'; @@ -25,8 +25,8 @@ import { NoopLogger } from '../../common/NoopLogger'; import { CompositePropagatorConfig } from './types'; /** Combines multiple propagators into a single propagator. */ -export class CompositePropagator implements HttpTextPropagator { - private readonly _propagators: HttpTextPropagator[]; +export class CompositePropagator implements TextMapPropagator { + private readonly _propagators: TextMapPropagator[]; private readonly _logger: Logger; /** diff --git a/packages/opentelemetry-core/src/context/propagation/types.ts b/packages/opentelemetry-core/src/context/propagation/types.ts index 9b7b2213d7..490c6ee56f 100644 --- a/packages/opentelemetry-core/src/context/propagation/types.ts +++ b/packages/opentelemetry-core/src/context/propagation/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HttpTextPropagator, Logger } from '@opentelemetry/api'; +import { TextMapPropagator, Logger } from '@opentelemetry/api'; /** Configuration object for composite propagator */ export interface CompositePropagatorConfig { @@ -23,7 +23,7 @@ export interface CompositePropagatorConfig { * list order. If a propagator later in the list writes the same context * key as a propagator earlier in the list, the later on will "win". */ - propagators?: HttpTextPropagator[]; + propagators?: TextMapPropagator[]; /** Instance of logger */ logger?: Logger; diff --git a/packages/opentelemetry-core/src/correlation-context/propagation/HttpCorrelationContext.ts b/packages/opentelemetry-core/src/correlation-context/propagation/HttpCorrelationContext.ts index 4c67accb97..c2db665176 100644 --- a/packages/opentelemetry-core/src/correlation-context/propagation/HttpCorrelationContext.ts +++ b/packages/opentelemetry-core/src/correlation-context/propagation/HttpCorrelationContext.ts @@ -18,7 +18,7 @@ import { Context, CorrelationContext, GetterFunction, - HttpTextPropagator, + TextMapPropagator, SetterFunction, } from '@opentelemetry/api'; import { @@ -49,7 +49,7 @@ type KeyPair = { * Based on the Correlation Context specification: * https://w3c.github.io/correlation-context/ */ -export class HttpCorrelationContext implements HttpTextPropagator { +export class HttpCorrelationContext implements TextMapPropagator { inject(context: Context, carrier: unknown, setter: SetterFunction) { const correlationContext = getCorrelationContext(context); if (!correlationContext) return; diff --git a/packages/opentelemetry-core/src/index.ts b/packages/opentelemetry-core/src/index.ts index d1bf17a580..ecdf972421 100644 --- a/packages/opentelemetry-core/src/index.ts +++ b/packages/opentelemetry-core/src/index.ts @@ -33,7 +33,6 @@ export * from './trace/sampler/AlwaysOffSampler'; export * from './trace/sampler/AlwaysOnSampler'; export * from './trace/sampler/ParentOrElseSampler'; export * from './trace/sampler/ProbabilitySampler'; -export * from './trace/spancontext-utils'; export * from './trace/TraceState'; export * from './trace/IdGenerator'; export * from './utils/url'; diff --git a/packages/opentelemetry-core/src/platform/node/timer-util.ts b/packages/opentelemetry-core/src/platform/node/timer-util.ts index 92668d5ba1..155562146f 100644 --- a/packages/opentelemetry-core/src/platform/node/timer-util.ts +++ b/packages/opentelemetry-core/src/platform/node/timer-util.ts @@ -13,6 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export function unrefTimer(timer: NodeJS.Timeout): void { +export function unrefTimer(timer: NodeJS.Timer): void { timer.unref(); } diff --git a/packages/opentelemetry-core/src/trace/NoRecordingSpan.ts b/packages/opentelemetry-core/src/trace/NoRecordingSpan.ts index 57c91f4108..589ea5c1cf 100644 --- a/packages/opentelemetry-core/src/trace/NoRecordingSpan.ts +++ b/packages/opentelemetry-core/src/trace/NoRecordingSpan.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { SpanContext, NoopSpan } from '@opentelemetry/api'; -import { INVALID_SPAN_CONTEXT } from '../trace/spancontext-utils'; +import { + SpanContext, + NoopSpan, + INVALID_SPAN_CONTEXT, +} from '@opentelemetry/api'; /** * The NoRecordingSpan extends the {@link NoopSpan}, making all operations no-op diff --git a/packages/opentelemetry-core/src/version.ts b/packages/opentelemetry-core/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-core/src/version.ts +++ b/packages/opentelemetry-core/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-core/test/context/composite.test.ts b/packages/opentelemetry-core/test/context/composite.test.ts index a26c6c5ac3..6047f40fba 100644 --- a/packages/opentelemetry-core/test/context/composite.test.ts +++ b/packages/opentelemetry-core/test/context/composite.test.ts @@ -17,7 +17,7 @@ import { defaultGetter, defaultSetter, - HttpTextPropagator, + TextMapPropagator, SpanContext, } from '@opentelemetry/api'; import { Context } from '@opentelemetry/context-base'; @@ -154,7 +154,7 @@ describe('Composite Propagator', () => { }); }); -class ThrowingPropagator implements HttpTextPropagator { +class ThrowingPropagator implements TextMapPropagator { inject(context: Context, carrier: unknown) { throw new Error('this propagator throws'); } diff --git a/packages/opentelemetry-core/test/context/context.test.ts b/packages/opentelemetry-core/test/context/context.test.ts new file mode 100644 index 0000000000..1755727d49 --- /dev/null +++ b/packages/opentelemetry-core/test/context/context.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; + +import { + SUPPRESS_INSTRUMENTATION_KEY, + suppressInstrumentation, + unsuppressInstrumentation, + isInstrumentationSuppressed, +} from '../../src/context/context'; +import { Context } from '@opentelemetry/api'; + +describe('Context Helpers', () => { + describe('suppressInstrumentation', () => { + it('should set suppress to true', () => { + const expectedValue = true; + const context = suppressInstrumentation(Context.ROOT_CONTEXT); + + const value = context.getValue(SUPPRESS_INSTRUMENTATION_KEY); + const boolValue = value as boolean; + + assert.equal(boolValue, expectedValue); + }); + }); + + describe('unsuppressInstrumentation', () => { + it('should set suppress to false', () => { + const expectedValue = false; + const context = unsuppressInstrumentation(Context.ROOT_CONTEXT); + + const value = context.getValue(SUPPRESS_INSTRUMENTATION_KEY); + const boolValue = value as boolean; + + assert.equal(boolValue, expectedValue); + }); + }); + + describe('isInstrumentationSuppressed', () => { + it('should get value as bool', () => { + const expectedValue = true; + const context = Context.ROOT_CONTEXT.setValue( + SUPPRESS_INSTRUMENTATION_KEY, + expectedValue + ); + + const value = isInstrumentationSuppressed(context); + + assert.equal(value, expectedValue); + }); + + describe('when suppress instrumentation set to null', () => { + const context = Context.ROOT_CONTEXT.setValue( + SUPPRESS_INSTRUMENTATION_KEY, + null + ); + + it('should return false', () => { + const value = isInstrumentationSuppressed(context); + + assert.equal(value, false); + }); + }); + + describe('when suppress instrumentation set to undefined', () => { + const context = Context.ROOT_CONTEXT.setValue( + SUPPRESS_INSTRUMENTATION_KEY, + undefined + ); + + it('should return false', () => { + const value = isInstrumentationSuppressed(context); + + assert.equal(value, false); + }); + }); + }); +}); diff --git a/packages/opentelemetry-exporter-collector-grpc/package.json b/packages/opentelemetry-exporter-collector-grpc/package.json index 9810965cda..3c4f708a07 100644 --- a/packages/opentelemetry-exporter-collector-grpc/package.json +++ b/packages/opentelemetry-exporter-collector-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-collector-grpc", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -65,12 +65,12 @@ }, "dependencies": { "@grpc/proto-loader": "^0.5.4", - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-collector": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-collector": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "grpc": "^1.24.2" } } diff --git a/packages/opentelemetry-exporter-collector-grpc/src/version.ts b/packages/opentelemetry-exporter-collector-grpc/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-exporter-collector-grpc/src/version.ts +++ b/packages/opentelemetry-exporter-collector-grpc/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-exporter-collector-proto/README.md b/packages/opentelemetry-exporter-collector-proto/README.md index 3331b2b476..4018016e84 100644 --- a/packages/opentelemetry-exporter-collector-proto/README.md +++ b/packages/opentelemetry-exporter-collector-proto/README.md @@ -18,7 +18,7 @@ npm install --save @opentelemetry/exporter-collector-proto ```js const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); -const { CollectorExporter } = require('@opentelemetry/exporter-collector-proto'); +const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector-proto'); const collectorOptions = { serviceName: 'basic-service', @@ -29,7 +29,7 @@ const collectorOptions = { }; const provider = new BasicTracerProvider(); -const exporter = new CollectorExporter(collectorOptions); +const exporter = new CollectorTraceExporter(collectorOptions); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.register(); diff --git a/packages/opentelemetry-exporter-collector-proto/package.json b/packages/opentelemetry-exporter-collector-proto/package.json index 59350ba925..0c12da0e25 100644 --- a/packages/opentelemetry-exporter-collector-proto/package.json +++ b/packages/opentelemetry-exporter-collector-proto/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-collector-proto", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -65,12 +65,12 @@ }, "dependencies": { "@grpc/proto-loader": "^0.5.4", - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/exporter-collector": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/exporter-collector": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "protobufjs": "^6.9.0" } } diff --git a/packages/opentelemetry-exporter-collector-proto/src/version.ts b/packages/opentelemetry-exporter-collector-proto/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-exporter-collector-proto/src/version.ts +++ b/packages/opentelemetry-exporter-collector-proto/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-exporter-collector-proto/test/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector-proto/test/CollectorMetricExporter.test.ts index c6c8cf958b..83f3f7a101 100644 --- a/packages/opentelemetry-exporter-collector-proto/test/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector-proto/test/CollectorMetricExporter.test.ts @@ -50,6 +50,9 @@ const mockResError = { statusCode: 400, }; +// send is lazy loading file so need to wait a bit +const waitTimeMS = 20; + describe('CollectorMetricExporter - node with proto over http', () => { let collectorExporter: CollectorMetricExporter; let collectorExporterConfig: collectorTypes.CollectorExporterConfigBase; @@ -57,7 +60,7 @@ describe('CollectorMetricExporter - node with proto over http', () => { let spyWrite: sinon.SinonSpy; let metrics: MetricRecord[]; describe('export', () => { - beforeEach(done => { + beforeEach(() => { spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any); spyWrite = sinon.stub(fakeRequest, 'write'); collectorExporterConfig = { @@ -86,11 +89,6 @@ describe('CollectorMetricExporter - node with proto over http', () => { metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); metrics[3].aggregator.update(5); - - // due to lazy loading ensure to wait to next tick - setImmediate(() => { - done(); - }); }); afterEach(() => { spyRequest.restore(); @@ -108,7 +106,7 @@ describe('CollectorMetricExporter - node with proto over http', () => { assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); done(); - }); + }, waitTimeMS); }); it('should set custom headers', done => { @@ -119,7 +117,7 @@ describe('CollectorMetricExporter - node with proto over http', () => { const options = args[0]; assert.strictEqual(options.headers['foo'], 'bar'); done(); - }); + }, waitTimeMS); }); it('should successfully send metrics', done => { @@ -154,7 +152,7 @@ describe('CollectorMetricExporter - node with proto over http', () => { ensureExportMetricsServiceRequestIsSet(json); done(); - }); + }, waitTimeMS); }); it('should log the successful message', done => { @@ -175,7 +173,7 @@ describe('CollectorMetricExporter - node with proto over http', () => { assert.strictEqual(responseSpy.args[0][0], 0); done(); }); - }); + }, waitTimeMS); }); it('should log the error message', done => { @@ -195,7 +193,7 @@ describe('CollectorMetricExporter - node with proto over http', () => { assert.strictEqual(responseSpy.args[0][0], 1); done(); }); - }); + }, waitTimeMS); }); }); }); diff --git a/packages/opentelemetry-exporter-collector-proto/test/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector-proto/test/CollectorTraceExporter.test.ts index 27803d9c16..662bd25e4c 100644 --- a/packages/opentelemetry-exporter-collector-proto/test/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector-proto/test/CollectorTraceExporter.test.ts @@ -44,14 +44,17 @@ const mockResError = { statusCode: 400, }; -describe('CollectorExporter - node with proto over http', () => { +// send is lazy loading file so need to wait a bit +const waitTimeMS = 20; + +describe('CollectorTraceExporter - node with proto over http', () => { let collectorExporter: CollectorTraceExporter; let collectorExporterConfig: collectorTypes.CollectorExporterConfigBase; let spyRequest: sinon.SinonSpy; let spyWrite: sinon.SinonSpy; let spans: ReadableSpan[]; describe('export', () => { - beforeEach(done => { + beforeEach(() => { spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any); spyWrite = sinon.stub(fakeRequest, 'write'); collectorExporterConfig = { @@ -67,11 +70,6 @@ describe('CollectorExporter - node with proto over http', () => { collectorExporter = new CollectorTraceExporter(collectorExporterConfig); spans = []; spans.push(Object.assign({}, mockedReadableSpan)); - - // due to lazy loading ensure to wait to next tick - setImmediate(() => { - done(); - }); }); afterEach(() => { spyRequest.restore(); @@ -89,7 +87,7 @@ describe('CollectorExporter - node with proto over http', () => { assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); done(); - }); + }, waitTimeMS); }); it('should set custom headers', done => { @@ -100,7 +98,7 @@ describe('CollectorExporter - node with proto over http', () => { const options = args[0]; assert.strictEqual(options.headers['foo'], 'bar'); done(); - }); + }, waitTimeMS); }); it('should successfully send the spans', done => { @@ -121,7 +119,7 @@ describe('CollectorExporter - node with proto over http', () => { ensureExportTraceServiceRequestIsSet(json); done(); - }); + }, waitTimeMS); }); it('should log the successful message', done => { @@ -142,7 +140,7 @@ describe('CollectorExporter - node with proto over http', () => { assert.strictEqual(responseSpy.args[0][0], 0); done(); }); - }); + }, waitTimeMS); }); it('should log the error message', done => { @@ -162,7 +160,7 @@ describe('CollectorExporter - node with proto over http', () => { assert.strictEqual(responseSpy.args[0][0], 1); done(); }); - }); + }, waitTimeMS); }); }); }); diff --git a/packages/opentelemetry-exporter-collector/README.md b/packages/opentelemetry-exporter-collector/README.md index c398fc56ab..076c817ce1 100644 --- a/packages/opentelemetry-exporter-collector/README.md +++ b/packages/opentelemetry-exporter-collector/README.md @@ -65,7 +65,7 @@ counter.add(10, { 'key': 'value' }); ```js const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); -const { CollectorExporter } = require('@opentelemetry/exporter-collector'); +const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector'); const collectorOptions = { serviceName: 'basic-service', @@ -76,7 +76,7 @@ const collectorOptions = { }; const provider = new BasicTracerProvider(); -const exporter = new CollectorExporter(collectorOptions); +const exporter = new CollectorTraceExporter(collectorOptions); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.register(); diff --git a/packages/opentelemetry-exporter-collector/package.json b/packages/opentelemetry-exporter-collector/package.json index 4bf9bed3b7..1d76e5effd 100644 --- a/packages/opentelemetry-exporter-collector/package.json +++ b/packages/opentelemetry-exporter-collector/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-collector", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -79,11 +79,11 @@ "webpack-merge": "5.1.1" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "axios": "^0.19.2" } } diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-exporter-collector/src/version.ts +++ b/packages/opentelemetry-exporter-collector/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-exporter-jaeger/README.md b/packages/opentelemetry-exporter-jaeger/README.md index e875c5a031..a00da2606c 100644 --- a/packages/opentelemetry-exporter-jaeger/README.md +++ b/packages/opentelemetry-exporter-jaeger/README.md @@ -83,8 +83,8 @@ tracer.addSpanProcessor(new BatchSpanProcessor(exporter)); You can use built-in `SimpleSpanProcessor` or `BatchSpanProcessor` or write your own. -- [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`. -- [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization. +- [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`. +- [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization. ## Useful links diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index ada8a6cbd2..e24cf1368f 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-jaeger", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Exporter Jaeger allows user to send collected traces to Jaeger", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -42,7 +42,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/resources": "^0.10.2", + "@opentelemetry/resources": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "codecov": "3.7.2", @@ -56,9 +56,9 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "jaeger-client": "^3.15.0" } } diff --git a/packages/opentelemetry-exporter-jaeger/src/version.ts b/packages/opentelemetry-exporter-jaeger/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-exporter-jaeger/src/version.ts +++ b/packages/opentelemetry-exporter-jaeger/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-exporter-prometheus/package.json b/packages/opentelemetry-exporter-prometheus/package.json index 8ccb14430e..5c30a2d03c 100644 --- a/packages/opentelemetry-exporter-prometheus/package.json +++ b/packages/opentelemetry-exporter-prometheus/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-prometheus", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Exporter Prometheus provides a metrics endpoint for Prometheus", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -53,9 +53,8 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "prom-client": "^11.5.3" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0" } } diff --git a/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts b/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts new file mode 100644 index 0000000000..3d6035cc4f --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts @@ -0,0 +1,184 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as api from '@opentelemetry/api'; +import { ExportResult, NoopLogger } from '@opentelemetry/core'; +import { MetricExporter, MetricRecord } from '@opentelemetry/metrics'; +import { createServer, IncomingMessage, Server, ServerResponse } from 'http'; +import * as url from 'url'; +import { ExporterConfig } from './export/types'; +import { PrometheusSerializer } from './PrometheusSerializer'; +import { PrometheusLabelsBatcher } from './PrometheusLabelsBatcher'; + +export class PrometheusExporter implements MetricExporter { + static readonly DEFAULT_OPTIONS = { + port: 9464, + startServer: false, + endpoint: '/metrics', + prefix: '', + }; + + private readonly _logger: api.Logger; + private readonly _port: number; + private readonly _endpoint: string; + private readonly _server: Server; + private readonly _prefix?: string; + private _serializer: PrometheusSerializer; + private _batcher = new PrometheusLabelsBatcher(); + + // This will be required when histogram is implemented. Leaving here so it is not forgotten + // Histogram cannot have a label named 'le' + // private static readonly RESERVED_HISTOGRAM_LABEL = 'le'; + + /** + * Constructor + * @param config Exporter configuration + * @param callback Callback to be called after a server was started + */ + constructor(config: ExporterConfig = {}, callback?: () => void) { + this._logger = config.logger || new NoopLogger(); + this._port = config.port || PrometheusExporter.DEFAULT_OPTIONS.port; + this._prefix = config.prefix || PrometheusExporter.DEFAULT_OPTIONS.prefix; + this._server = createServer(this._requestHandler); + this._serializer = new PrometheusSerializer(this._prefix); + + this._endpoint = ( + config.endpoint || PrometheusExporter.DEFAULT_OPTIONS.endpoint + ).replace(/^([^/])/, '/$1'); + + if (config.startServer || PrometheusExporter.DEFAULT_OPTIONS.startServer) { + this.startServer(callback); + } else if (callback) { + callback(); + } + } + + /** + * Saves the current values of all exported {@link MetricRecord}s so that + * they can be pulled by the Prometheus backend. + * + * In its current state, the exporter saves the current values of all metrics + * when export is called and returns them when the export endpoint is called. + * In the future, this should be a no-op and the exporter should reach into + * the metrics when the export endpoint is called. As there is currently no + * interface to do this, this is our only option. + * + * @param records Metrics to be sent to the prometheus backend + * @param cb result callback to be called on finish + */ + export(records: MetricRecord[], cb: (result: ExportResult) => void) { + if (!this._server) { + // It is conceivable that the _server may not be started as it is an async startup + // However unlikely, if this happens the caller may retry the export + cb(ExportResult.FAILED_RETRYABLE); + return; + } + + this._logger.debug('Prometheus exporter export'); + + for (const record of records) { + this._batcher.process(record); + } + + cb(ExportResult.SUCCESS); + } + + /** + * Shuts down the export server and clears the registry + * + * @param cb called when server is stopped + */ + shutdown(cb?: () => void) { + this.stopServer(cb); + } + + /** + * Stops the Prometheus export server + * @param callback A callback that will be executed once the server is stopped + */ + stopServer(callback?: () => void) { + if (!this._server) { + this._logger.debug( + 'Prometheus stopServer() was called but server was never started.' + ); + if (callback) { + callback(); + } + } else { + this._server.close(() => { + this._logger.debug('Prometheus exporter was stopped'); + if (callback) { + callback(); + } + }); + } + } + + /** + * Starts the Prometheus export server + * + * @param callback called once the server is ready + */ + startServer(callback?: () => void) { + this._server.listen(this._port, () => { + this._logger.debug( + `Prometheus exporter started on port ${this._port} at endpoint ${this._endpoint}` + ); + if (callback) { + callback(); + } + }); + } + + /** + * Request handler used by http library to respond to incoming requests + * for the current state of metrics by the Prometheus backend. + * + * @param request Incoming HTTP request to export server + * @param response HTTP response object used to respond to request + */ + private _requestHandler = ( + request: IncomingMessage, + response: ServerResponse + ) => { + if (url.parse(request.url!).pathname === this._endpoint) { + this._exportMetrics(response); + } else { + this._notFound(response); + } + }; + + /** + * Responds to incoming message with current state of all metrics. + */ + private _exportMetrics = (response: ServerResponse) => { + response.statusCode = 200; + response.setHeader('content-type', 'text/plain'); + if (!this._batcher.hasMetric) { + response.end('# no registered metrics'); + return; + } + response.end(this._serializer.serialize(this._batcher.checkPointSet())); + }; + + /** + * Responds with 404 status code to all requests that do not match the configured endpoint. + */ + private _notFound = (response: ServerResponse) => { + response.statusCode = 404; + response.end(); + }; +} diff --git a/packages/opentelemetry-exporter-prometheus/src/PrometheusLabelsBatcher.ts b/packages/opentelemetry-exporter-prometheus/src/PrometheusLabelsBatcher.ts new file mode 100644 index 0000000000..5d123d4ef4 --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/src/PrometheusLabelsBatcher.ts @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + MetricRecord, + MetricDescriptor, + AggregatorKind, +} from '@opentelemetry/metrics'; +import { PrometheusCheckpoint } from './types'; + +interface BatcherCheckpoint { + descriptor: MetricDescriptor; + aggregatorKind: AggregatorKind; + records: Map; +} + +export class PrometheusLabelsBatcher { + private _batchMap = new Map(); + + get hasMetric() { + return this._batchMap.size > 0; + } + + process(record: MetricRecord) { + const name = record.descriptor.name; + let item = this._batchMap.get(name); + if (item === undefined) { + item = { + descriptor: record.descriptor, + aggregatorKind: record.aggregator.kind, + records: new Map(), + }; + this._batchMap.set(name, item); + } + const recordMap = item.records; + const labels = Object.keys(record.labels) + .map(k => `${k}=${record.labels[k]}`) + .join(','); + recordMap.set(labels, record); + } + + checkPointSet(): PrometheusCheckpoint[] { + return Array.from(this._batchMap.values()).map( + ({ descriptor, aggregatorKind, records }) => { + return { + descriptor, + aggregatorKind, + records: Array.from(records.values()), + }; + } + ); + } +} diff --git a/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts b/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts new file mode 100644 index 0000000000..7e70eb986f --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts @@ -0,0 +1,259 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + MetricRecord, + AggregatorKind, + Distribution, + MetricKind, +} from '@opentelemetry/metrics'; +import { PrometheusCheckpoint } from './types'; +import { Labels } from '@opentelemetry/api'; +import { hrTimeToMilliseconds } from '@opentelemetry/core'; + +type PrometheusDataTypeLiteral = + | 'counter' + | 'gauge' + | 'histogram' + | 'summary' + | 'untyped'; + +function escapeString(str: string) { + return str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); +} + +function escapeLabelValue(str: string) { + if (typeof str !== 'string') { + str = String(str); + } + return escapeString(str).replace(/"/g, '\\"'); +} + +const invalidCharacterRegex = /[^a-z0-9_]/gi; +/** + * Ensures metric names are valid Prometheus metric names by removing + * characters allowed by OpenTelemetry but disallowed by Prometheus. + * + * https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels + * + * 1. Names must match `[a-zA-Z_:][a-zA-Z0-9_:]*` + * + * 2. Colons are reserved for user defined recording rules. + * They should not be used by exporters or direct instrumentation. + * + * OpenTelemetry metric names are already validated in the Meter when they are created, + * and they match the format `[a-zA-Z][a-zA-Z0-9_.\-]*` which is very close to a valid + * prometheus metric name, so we only need to strip characters valid in OpenTelemetry + * but not valid in prometheus and replace them with '_'. + * + * @param name name to be sanitized + */ +function sanitizePrometheusMetricName(name: string): string { + return name.replace(invalidCharacterRegex, '_'); // replace all invalid characters with '_' +} + +function valueString(value: number) { + if (Number.isNaN(value)) { + return 'Nan'; + } else if (!Number.isFinite(value)) { + if (value < 0) { + return '-Inf'; + } else { + return '+Inf'; + } + } else { + return `${value}`; + } +} + +function toPrometheusType( + metricKind: MetricKind, + aggregatorKind: AggregatorKind +): PrometheusDataTypeLiteral { + switch (aggregatorKind) { + case AggregatorKind.SUM: + if ( + metricKind === MetricKind.COUNTER || + metricKind === MetricKind.SUM_OBSERVER + ) { + return 'counter'; + } + /** MetricKind.UP_DOWN_COUNTER and MetricKind.UP_DOWN_SUM_OBSERVER */ + return 'gauge'; + case AggregatorKind.LAST_VALUE: + return 'gauge'; + case AggregatorKind.DISTRIBUTION: + return 'summary'; + case AggregatorKind.HISTOGRAM: + return 'histogram'; + default: + return 'untyped'; + } +} + +function stringify( + metricName: string, + labels: Labels, + value: number, + timestamp?: number, + additionalLabels?: Labels +) { + let hasLabel = false; + let labelsStr = ''; + + for (const [key, val] of Object.entries(labels)) { + hasLabel = true; + labelsStr += `${labelsStr.length > 0 ? ',' : ''}${key}="${escapeLabelValue( + val + )}"`; + } + if (additionalLabels) { + for (const [key, val] of Object.entries(additionalLabels)) { + hasLabel = true; + labelsStr += `${ + labelsStr.length > 0 ? ',' : '' + }${key}="${escapeLabelValue(val)}"`; + } + } + + if (hasLabel) { + metricName += `{${labelsStr}}`; + } + + return `${metricName} ${valueString(value)}${ + timestamp !== undefined ? ' ' + String(timestamp) : '' + }\n`; +} + +export class PrometheusSerializer { + private _prefix: string | undefined; + private _appendTimestamp: boolean; + + constructor(prefix?: string, appendTimestamp = true) { + if (prefix) { + this._prefix = prefix + '_'; + } + this._appendTimestamp = appendTimestamp; + } + + serialize(checkpointSet: PrometheusCheckpoint[]): string { + let str = ''; + for (const checkpoint of checkpointSet) { + str += this.serializeCheckpointSet(checkpoint) + '\n'; + } + return str; + } + + serializeCheckpointSet(checkpoint: PrometheusCheckpoint): string { + let name = sanitizePrometheusMetricName( + escapeString(checkpoint.descriptor.name) + ); + if (this._prefix) { + name = `${this._prefix}${name}`; + } + const help = `# HELP ${name} ${escapeString( + checkpoint.descriptor.description || 'description missing' + )}`; + const type = `# TYPE ${name} ${toPrometheusType( + checkpoint.descriptor.metricKind, + checkpoint.aggregatorKind + )}`; + + const results = checkpoint.records + .map(it => this.serializeRecord(name, it)) + .join(''); + + return `${help}\n${type}\n${results}`.trim(); + } + + serializeRecord(name: string, record: MetricRecord): string { + let results = ''; + switch (record.aggregator.kind) { + case AggregatorKind.SUM: + case AggregatorKind.LAST_VALUE: { + const { value, timestamp: hrtime } = record.aggregator.toPoint(); + const timestamp = hrTimeToMilliseconds(hrtime); + results += stringify( + name, + record.labels, + value, + this._appendTimestamp ? timestamp : undefined, + undefined + ); + break; + } + case AggregatorKind.DISTRIBUTION: { + const { value, timestamp: hrtime } = record.aggregator.toPoint(); + const timestamp = hrTimeToMilliseconds(hrtime); + for (const key of ['count', 'sum'] as (keyof Distribution)[]) { + results += stringify( + name + '_' + key, + record.labels, + value[key], + this._appendTimestamp ? timestamp : undefined, + undefined + ); + } + results += stringify( + name, + record.labels, + value.min, + this._appendTimestamp ? timestamp : undefined, + { + quantile: '0', + } + ); + results += stringify( + name, + record.labels, + value.max, + this._appendTimestamp ? timestamp : undefined, + { + quantile: '1', + } + ); + break; + } + case AggregatorKind.HISTOGRAM: { + const { value, timestamp: hrtime } = record.aggregator.toPoint(); + const timestamp = hrTimeToMilliseconds(hrtime); + /** Histogram["bucket"] is not typed with `number` */ + for (const key of ['count', 'sum'] as ('count' | 'sum')[]) { + results += stringify( + name + '_' + key, + record.labels, + value[key], + this._appendTimestamp ? timestamp : undefined, + undefined + ); + } + for (const [idx, val] of value.buckets.counts.entries()) { + const upperBound = value.buckets.boundaries[idx]; + results += stringify( + name + '_bucket', + record.labels, + val, + this._appendTimestamp ? timestamp : undefined, + { + le: upperBound === undefined ? '+Inf' : String(upperBound), + } + ); + } + break; + } + } + return results; + } +} diff --git a/packages/opentelemetry-exporter-prometheus/src/index.ts b/packages/opentelemetry-exporter-prometheus/src/index.ts index be7bd5f868..bcf661b337 100644 --- a/packages/opentelemetry-exporter-prometheus/src/index.ts +++ b/packages/opentelemetry-exporter-prometheus/src/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export * from './prometheus'; +export * from './PrometheusExporter'; export * from './export/types'; diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts deleted file mode 100644 index f42d5d1017..0000000000 --- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as api from '@opentelemetry/api'; -import { - ExportResult, - hrTimeToMilliseconds, - NoopLogger, -} from '@opentelemetry/core'; -import { - Distribution, - Histogram, - MetricDescriptor, - MetricExporter, - MetricKind, - MetricRecord, - Sum, -} from '@opentelemetry/metrics'; -import { createServer, IncomingMessage, Server, ServerResponse } from 'http'; -import { Counter, Gauge, Metric, Registry } from 'prom-client'; -import * as url from 'url'; -import { ExporterConfig } from './export/types'; - -export class PrometheusExporter implements MetricExporter { - static readonly DEFAULT_OPTIONS = { - port: 9464, - startServer: false, - endpoint: '/metrics', - prefix: '', - }; - - private readonly _registry = new Registry(); - private readonly _logger: api.Logger; - private readonly _port: number; - private readonly _endpoint: string; - private readonly _server: Server; - private readonly _prefix?: string; - private readonly _invalidCharacterRegex = /[^a-z0-9_]/gi; - - // This will be required when histogram is implemented. Leaving here so it is not forgotten - // Histogram cannot have a label named 'le' - // private static readonly RESERVED_HISTOGRAM_LABEL = 'le'; - - /** - * Constructor - * @param config Exporter configuration - * @param callback Callback to be called after a server was started - */ - constructor(config: ExporterConfig = {}, callback?: () => void) { - this._logger = config.logger || new NoopLogger(); - this._port = config.port || PrometheusExporter.DEFAULT_OPTIONS.port; - this._prefix = config.prefix || PrometheusExporter.DEFAULT_OPTIONS.prefix; - this._server = createServer(this._requestHandler); - - this._endpoint = ( - config.endpoint || PrometheusExporter.DEFAULT_OPTIONS.endpoint - ).replace(/^([^/])/, '/$1'); - - if (config.startServer || PrometheusExporter.DEFAULT_OPTIONS.startServer) { - this.startServer().then(callback); - } else if (callback) { - callback(); - } - } - - /** - * Saves the current values of all exported {@link MetricRecord}s so that - * they can be pulled by the Prometheus backend. - * - * In its current state, the exporter saves the current values of all metrics - * when export is called and returns them when the export endpoint is called. - * In the future, this should be a no-op and the exporter should reach into - * the metrics when the export endpoint is called. As there is currently no - * interface to do this, this is our only option. - * - * @param records Metrics to be sent to the prometheus backend - * @param cb result callback to be called on finish - */ - export(records: MetricRecord[], cb: (result: ExportResult) => void) { - if (!this._server) { - // It is conceivable that the _server may not be started as it is an async startup - // However unlikely, if this happens the caller may retry the export - cb(ExportResult.FAILED_RETRYABLE); - return; - } - - this._logger.debug('Prometheus exporter export'); - - for (const record of records) { - this._updateMetric(record); - } - - cb(ExportResult.SUCCESS); - } - - /** - * Shuts down the export server and clears the registry - */ - shutdown(): Promise { - this._registry.clear(); - return this.stopServer(); - } - - /** - * Updates the value of a single metric in the registry - * - * @param record Metric value to be saved - */ - private _updateMetric(record: MetricRecord) { - const metric = this._registerMetric(record); - if (!metric) return; - - const point = record.aggregator.toPoint(); - - const labels = record.labels; - - if (metric instanceof Counter) { - // Prometheus counter saves internal state and increments by given value. - // MetricRecord value is the current state, not the delta to be incremented by. - // Currently, _registerMetric creates a new counter every time the value changes, - // so the increment here behaves as a set value (increment from 0) - metric.inc( - labels, - point.value as Sum, - hrTimeToMilliseconds(point.timestamp) - ); - } - - if (metric instanceof Gauge) { - if (typeof point.value === 'number') { - if ( - record.descriptor.metricKind === MetricKind.VALUE_OBSERVER || - record.descriptor.metricKind === MetricKind.VALUE_RECORDER - ) { - metric.set( - labels, - point.value, - hrTimeToMilliseconds(point.timestamp) - ); - } else { - metric.set(labels, point.value); - } - } else if ((point.value as Histogram).buckets) { - metric.set( - labels, - (point.value as Histogram).sum, - hrTimeToMilliseconds(point.timestamp) - ); - } else if (typeof (point.value as Distribution).last === 'number') { - metric.set( - labels, - (point.value as Distribution).last, - hrTimeToMilliseconds(point.timestamp) - ); - } - } - } - - private _registerMetric(record: MetricRecord): Metric | undefined { - const metricName = this._getPrometheusMetricName(record.descriptor); - const metric = this._registry.getSingleMetric(metricName); - - /** - * Prometheus library does aggregation, which means its inc method must be called with - * the value to be incremented by. It does not have a set method. As our MetricRecord - * contains the current value, not the value to be incremented by, we destroy and - * recreate counters when they are updated. - * - * This works because counters are identified by their name and no other internal ID - * https://prometheus.io/docs/instrumenting/exposition_formats/ - */ - if (metric instanceof Counter) { - metric.remove(...Object.values(record.labels)); - } - - if (metric) return metric; - - return this._newMetric(record, metricName); - } - - private _newMetric(record: MetricRecord, name: string): Metric | undefined { - const metricObject = { - name, - // prom-client throws with empty description which is our default - help: record.descriptor.description || 'description missing', - labelNames: Object.keys(record.labels), - // list of registries to register the newly created metric - registers: [this._registry], - }; - - switch (record.descriptor.metricKind) { - case MetricKind.COUNTER: - return new Counter(metricObject); - case MetricKind.UP_DOWN_COUNTER: - return new Gauge(metricObject); - case MetricKind.VALUE_RECORDER: - return new Gauge(metricObject); - case MetricKind.SUM_OBSERVER: - return new Counter(metricObject); - case MetricKind.UP_DOWN_SUM_OBSERVER: - return new Gauge(metricObject); - case MetricKind.VALUE_OBSERVER: - return new Gauge(metricObject); - default: - // Other metric types are currently unimplemented - return undefined; - } - } - - private _getPrometheusMetricName(descriptor: MetricDescriptor): string { - return this._sanitizePrometheusMetricName( - this._prefix ? `${this._prefix}_${descriptor.name}` : descriptor.name - ); - } - - /** - * Ensures metric names are valid Prometheus metric names by removing - * characters allowed by OpenTelemetry but disallowed by Prometheus. - * - * https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels - * - * 1. Names must match `[a-zA-Z_:][a-zA-Z0-9_:]*` - * - * 2. Colons are reserved for user defined recording rules. - * They should not be used by exporters or direct instrumentation. - * - * OpenTelemetry metric names are already validated in the Meter when they are created, - * and they match the format `[a-zA-Z][a-zA-Z0-9_.\-]*` which is very close to a valid - * prometheus metric name, so we only need to strip characters valid in OpenTelemetry - * but not valid in prometheus and replace them with '_'. - * - * @param name name to be sanitized - */ - private _sanitizePrometheusMetricName(name: string): string { - return name.replace(this._invalidCharacterRegex, '_'); // replace all invalid characters with '_' - } - - /** - * Stops the Prometheus export server - */ - stopServer(): Promise { - if (!this._server) { - this._logger.debug( - 'Prometheus stopServer() was called but server was never started.' - ); - return Promise.resolve(); - } else { - return new Promise(resolve => { - this._server.close(err => { - if (!err) { - this._logger.debug('Prometheus exporter was stopped'); - } else { - if ( - ((err as unknown) as { code: string }).code !== - 'ERR_SERVER_NOT_RUNNING' - ) { - this._logger.error( - `Error during stopping the Prometheus Exporter "${err.message}"` - ); - } - } - resolve(); - }); - }); - } - } - - /** - * Starts the Prometheus export server - */ - startServer(): Promise { - return new Promise(resolve => { - this._server.listen(this._port, () => { - this._logger.debug( - `Prometheus exporter started on port ${this._port} at endpoint ${this._endpoint}` - ); - resolve(); - }); - }); - } - - /** - * Request handler used by http library to respond to incoming requests - * for the current state of metrics by the Prometheus backend. - * - * @param request Incoming HTTP request to export server - * @param response HTTP response object used to respond to request - */ - private _requestHandler = ( - request: IncomingMessage, - response: ServerResponse - ) => { - if (url.parse(request.url!).pathname === this._endpoint) { - this._exportMetrics(response); - } else { - this._notFound(response); - } - }; - - /** - * Responds to incoming message with current state of all metrics. - */ - private _exportMetrics = (response: ServerResponse) => { - response.statusCode = 200; - response.setHeader('content-type', this._registry.contentType); - response.end(this._registry.metrics() || '# no registered metrics'); - }; - - /** - * Responds with 404 status code to all requests that do not match the configured endpoint. - */ - private _notFound = (response: ServerResponse) => { - response.statusCode = 404; - response.end(); - }; -} diff --git a/packages/opentelemetry-exporter-prometheus/src/types.ts b/packages/opentelemetry-exporter-prometheus/src/types.ts new file mode 100644 index 0000000000..343dc99197 --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/src/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + MetricDescriptor, + AggregatorKind, + MetricRecord, +} from '@opentelemetry/metrics'; + +export interface PrometheusCheckpoint { + descriptor: MetricDescriptor; + aggregatorKind: AggregatorKind; + records: MetricRecord[]; +} diff --git a/packages/opentelemetry-exporter-prometheus/src/version.ts b/packages/opentelemetry-exporter-prometheus/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-exporter-prometheus/src/version.ts +++ b/packages/opentelemetry-exporter-prometheus/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-exporter-prometheus/test/ExactBatcher.ts b/packages/opentelemetry-exporter-prometheus/test/ExactBatcher.ts new file mode 100644 index 0000000000..4d4a6fa972 --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/test/ExactBatcher.ts @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Batcher, + MetricDescriptor, + Aggregator, + MetricRecord, +} from '@opentelemetry/metrics'; + +type Constructor = new (...args: T[]) => R; + +export class ExactBatcher extends Batcher { + private readonly args: ConstructorParameters>; + public aggregators: R[] = []; + + constructor( + private readonly aggregator: Constructor, + ...args: ConstructorParameters> + ) { + super(); + this.args = args; + } + + aggregatorFor(metricDescriptor: MetricDescriptor): Aggregator { + const aggregator = new this.aggregator(...this.args); + this.aggregators.push(aggregator); + return aggregator; + } + + process(record: MetricRecord): void { + const labels = Object.keys(record.labels) + .map(k => `${k}=${record.labels[k]}`) + .join(','); + this._batchMap.set(record.descriptor.name + labels, record); + } +} diff --git a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts b/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts similarity index 89% rename from packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts rename to packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts index b60df9c2be..309b47df11 100644 --- a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts +++ b/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HrTime, ObserverResult } from '@opentelemetry/api'; +import { ObserverResult } from '@opentelemetry/api'; import { notifyOnGlobalShutdown, _invokeGlobalShutdown, @@ -24,30 +24,18 @@ import { SumAggregator, Meter, MeterProvider, - Point, - Sum, + MinMaxLastSumCountAggregator, } from '@opentelemetry/metrics'; import * as assert from 'assert'; import * as http from 'http'; import { PrometheusExporter } from '../src'; - -const mockedHrTime: HrTime = [1586347902211, 0]; -const mockedTimeMS = 1586347902211000; +import { mockAggregator, mockedHrTimeMs } from './util'; describe('PrometheusExporter', () => { - let toPoint: () => Point; let removeEvent: Function | undefined; - before(() => { - toPoint = SumAggregator.prototype.toPoint; - SumAggregator.prototype.toPoint = function (): Point { - const point = toPoint.apply(this); - point.timestamp = mockedHrTime; - return point; - }; - }); - after(() => { - SumAggregator.prototype.toPoint = toPoint; - }); + mockAggregator(SumAggregator); + mockAggregator(MinMaxLastSumCountAggregator); + describe('constructor', () => { it('should construct an exporter', () => { const exporter = new PrometheusExporter(); @@ -222,6 +210,7 @@ describe('PrometheusExporter', () => { boundCounter.add(10); meter.collect().then(() => { exporter.export(meter.getBatcher().checkPointSet(), () => { + // TODO: Remove this special case once the PR is ready. // This is to test the special case where counters are destroyed // and recreated in the exporter in order to get around prom-client's // aggregation and use ours. @@ -241,7 +230,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter a test description', '# TYPE counter counter', - `counter{key1="labelValue1"} 20 ${mockedTimeMS}`, + `counter{key1="labelValue1"} 20 ${mockedHrTimeMs}`, '', ]); @@ -256,7 +245,7 @@ describe('PrometheusExporter', () => { it('should export an observer aggregation', done => { function getCpuUsage() { - return Math.random(); + return 0.999; } meter.createValueObserver( @@ -281,20 +270,15 @@ describe('PrometheusExporter', () => { const body = chunk.toString(); const lines = body.split('\n'); - assert.strictEqual( - lines[0], - '# HELP metric_observer a test description' - ); - assert.strictEqual(lines[1], '# TYPE metric_observer gauge'); - - const line3 = lines[2].split(' '); - assert.strictEqual( - line3[0], - 'metric_observer{pid="123",core="1"}' - ); - assert.ok( - parseFloat(line3[1]) >= 0 && parseFloat(line3[1]) <= 1 - ); + assert.deepStrictEqual(lines, [ + '# HELP metric_observer a test description', + '# TYPE metric_observer summary', + `metric_observer_count{pid="123",core="1"} 1 ${mockedHrTimeMs}`, + `metric_observer_sum{pid="123",core="1"} 0.999 ${mockedHrTimeMs}`, + `metric_observer{pid="123",core="1",quantile="0"} 0.999 ${mockedHrTimeMs}`, + `metric_observer{pid="123",core="1",quantile="1"} 0.999 ${mockedHrTimeMs}`, + '', + ]); done(); }); @@ -323,8 +307,8 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter a test description', '# TYPE counter counter', - `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, - `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, + `counter{counterKey1="labelValue1"} 10 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue2"} 20 ${mockedHrTimeMs}`, '', ]); @@ -355,9 +339,9 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter a test description', '# TYPE counter counter', - `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, - `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, - `counter{counterKey1="labelValue3"} 30 ${mockedTimeMS}`, + `counter{counterKey1="labelValue1"} 10 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue2"} 20 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue3"} 30 ${mockedHrTimeMs}`, '', ]); @@ -451,9 +435,9 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter a test description', '# TYPE counter counter', - `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, - `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, - `counter{counterKey1="labelValue3"} 30 ${mockedTimeMS}`, + `counter{counterKey1="labelValue1"} 10 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue2"} 20 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue3"} 30 ${mockedHrTimeMs}`, '', ]); @@ -497,7 +481,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter description missing', '# TYPE counter counter', - `counter{key1="labelValue1"} 10 ${mockedTimeMS}`, + `counter{key1="labelValue1"} 10 ${mockedHrTimeMs}`, '', ]); @@ -524,7 +508,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter_bad_name description missing', '# TYPE counter_bad_name counter', - `counter_bad_name{key1="labelValue1"} 10 ${mockedTimeMS}`, + `counter_bad_name{key1="labelValue1"} 10 ${mockedHrTimeMs}`, '', ]); @@ -550,7 +534,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(chunk.toString().split('\n'), [ '# HELP counter a test description', '# TYPE counter gauge', - 'counter{key1="labelValue1"} 20', + `counter{key1="labelValue1"} 20 ${mockedHrTimeMs}`, '', ]); @@ -590,7 +574,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP sum_observer a test description', '# TYPE sum_observer counter', - `sum_observer{key1="labelValue1"} 20 ${mockedTimeMS}`, + `sum_observer{key1="labelValue1"} 20 ${mockedHrTimeMs}`, '', ]); }); @@ -630,7 +614,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP updown_observer a test description', '# TYPE updown_observer gauge', - 'updown_observer{key1="labelValue1"} 20', + `updown_observer{key1="labelValue1"} 20 ${mockedHrTimeMs}`, '', ]); }); @@ -642,7 +626,7 @@ describe('PrometheusExporter', () => { }); }); - it('should export a ValueRecorder as a gauge', done => { + it('should export a ValueRecorder as a summary', done => { const valueRecorder = meter.createValueRecorder('value_recorder', { description: 'a test description', }); @@ -657,18 +641,15 @@ describe('PrometheusExporter', () => { const body = chunk.toString(); const lines = body.split('\n'); - assert.strictEqual( - lines[0], - '# HELP value_recorder a test description' - ); - assert.strictEqual(lines[1], '# TYPE value_recorder gauge'); - - const line3 = lines[2].split(' '); - assert.strictEqual( - line3[0], - 'value_recorder{key1="labelValue1"}' - ); - assert.equal(line3[1], 20); + assert.deepStrictEqual(lines, [ + '# HELP value_recorder a test description', + '# TYPE value_recorder summary', + `value_recorder_count{key1="labelValue1"} 1 ${mockedHrTimeMs}`, + `value_recorder_sum{key1="labelValue1"} 20 ${mockedHrTimeMs}`, + `value_recorder{key1="labelValue1",quantile="0"} 20 ${mockedHrTimeMs}`, + `value_recorder{key1="labelValue1",quantile="1"} 20 ${mockedHrTimeMs}`, + '', + ]); done(); }); @@ -716,7 +697,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP test_prefix_counter description missing', '# TYPE test_prefix_counter counter', - `test_prefix_counter{key1="labelValue1"} 10 ${mockedTimeMS}`, + `test_prefix_counter{key1="labelValue1"} 10 ${mockedHrTimeMs}`, '', ]); @@ -745,7 +726,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter description missing', '# TYPE counter counter', - `counter{key1="labelValue1"} 10 ${mockedTimeMS}`, + `counter{key1="labelValue1"} 10 ${mockedHrTimeMs}`, '', ]); @@ -774,7 +755,7 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter description missing', '# TYPE counter counter', - `counter{key1="labelValue1"} 10 ${mockedTimeMS}`, + `counter{key1="labelValue1"} 10 ${mockedHrTimeMs}`, '', ]); diff --git a/packages/opentelemetry-exporter-prometheus/test/PrometheusLabelsBatcher.test.ts b/packages/opentelemetry-exporter-prometheus/test/PrometheusLabelsBatcher.test.ts new file mode 100644 index 0000000000..27a500700b --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/test/PrometheusLabelsBatcher.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as assert from 'assert'; +import { PrometheusLabelsBatcher } from '../src/PrometheusLabelsBatcher'; +import { + CounterMetric, + AggregatorKind, + MeterProvider, + Meter, +} from '@opentelemetry/metrics'; +import { Labels } from '@opentelemetry/api'; + +describe('PrometheusBatcher', () => { + let meter: Meter; + before(() => { + meter = new MeterProvider({}).getMeter('test'); + }); + + describe('constructor', () => { + it('should construct a batcher', () => { + const batcher = new PrometheusLabelsBatcher(); + assert(batcher instanceof PrometheusLabelsBatcher); + }); + }); + + describe('process', () => { + it('should aggregate metric records with same metric name', async () => { + const batcher = new PrometheusLabelsBatcher(); + const counter = meter.createCounter('test_counter') as CounterMetric; + counter.bind({ val: '1' }).add(1); + counter.bind({ val: '2' }).add(1); + + const records = await counter.getMetricRecord(); + records.forEach(it => batcher.process(it)); + + const checkPointSet = batcher.checkPointSet(); + assert.strictEqual(checkPointSet.length, 1); + assert.strictEqual(checkPointSet[0].descriptor.name, 'test_counter'); + assert.strictEqual(checkPointSet[0].aggregatorKind, AggregatorKind.SUM); + assert.strictEqual(checkPointSet[0].records.length, 2); + }); + + it('should recognize identical labels with different key-insertion order', async () => { + const batcher = new PrometheusLabelsBatcher(); + const counter = meter.createCounter('test_counter') as CounterMetric; + + const label1: Labels = {}; + label1.key1 = '1'; + label1.key2 = '2'; + + const label2: Labels = {}; + label2.key2 = '2'; + label2.key1 = '1'; + + counter.bind(label1).add(1); + counter.bind(label2).add(1); + + const records = await counter.getMetricRecord(); + records.forEach(it => batcher.process(it)); + + const checkPointSet = batcher.checkPointSet(); + assert.strictEqual(checkPointSet.length, 1); + const checkPoint = checkPointSet[0]; + assert.strictEqual(checkPoint.descriptor.name, 'test_counter'); + assert.strictEqual(checkPoint.aggregatorKind, AggregatorKind.SUM); + assert.strictEqual(checkPoint.records.length, 1); + const record = checkPoint.records[0]; + assert.strictEqual(record.aggregator.toPoint().value, 2); + }); + }); +}); diff --git a/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts b/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts new file mode 100644 index 0000000000..b706e635d9 --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts @@ -0,0 +1,462 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + SumAggregator, + MinMaxLastSumCountAggregator, + HistogramAggregator, + MeterProvider, + CounterMetric, + ValueRecorderMetric, + UpDownCounterMetric, +} from '@opentelemetry/metrics'; +import * as assert from 'assert'; +import { Labels } from '@opentelemetry/api'; +import { PrometheusSerializer } from '../src/PrometheusSerializer'; +import { PrometheusLabelsBatcher } from '../src/PrometheusLabelsBatcher'; +import { ExactBatcher } from './ExactBatcher'; +import { mockedHrTimeMs, mockAggregator } from './util'; + +const labels = { + foo1: 'bar1', + foo2: 'bar2', +}; + +describe('PrometheusSerializer', () => { + describe('constructor', () => { + it('should construct a serializer', () => { + const serializer = new PrometheusSerializer(); + assert(serializer instanceof PrometheusSerializer); + }); + }); + + describe('serialize a metric record', () => { + describe('with SumAggregator', () => { + mockAggregator(SumAggregator); + + it('should serialize metric record with sum aggregator', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter.bind(labels).add(1); + + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + `test{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n` + ); + }); + + it('serialize metric record with sum aggregator without timestamp', async () => { + const serializer = new PrometheusSerializer(undefined, false); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter.bind(labels).add(1); + + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual(result, 'test{foo1="bar1",foo2="bar2"} 1\n'); + }); + }); + + describe('with MinMaxLastSumCountAggregator', () => { + mockAggregator(MinMaxLastSumCountAggregator); + + it('should serialize metric record with sum aggregator', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(MinMaxLastSumCountAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter.bind(labels).add(1); + + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + `test_count{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n` + + `test_sum{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n` + + `test{foo1="bar1",foo2="bar2",quantile="0"} 1 ${mockedHrTimeMs}\n` + + `test{foo1="bar1",foo2="bar2",quantile="1"} 1 ${mockedHrTimeMs}\n` + ); + }); + + it('serialize metric record with sum aggregator without timestamp', async () => { + const serializer = new PrometheusSerializer(undefined, false); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(MinMaxLastSumCountAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter.bind(labels).add(1); + + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + 'test_count{foo1="bar1",foo2="bar2"} 1\n' + + 'test_sum{foo1="bar1",foo2="bar2"} 1\n' + + 'test{foo1="bar1",foo2="bar2",quantile="0"} 1\n' + + 'test{foo1="bar1",foo2="bar2",quantile="1"} 1\n' + ); + }); + }); + + describe('with HistogramAggregator', () => { + mockAggregator(HistogramAggregator); + + it('should serialize metric record with sum aggregator', async () => { + const serializer = new PrometheusSerializer(); + + const batcher = new ExactBatcher(HistogramAggregator, [1, 10, 100]); + const meter = new MeterProvider({ batcher }).getMeter('test'); + const recorder = meter.createValueRecorder('test', { + description: 'foobar', + }) as ValueRecorderMetric; + recorder.bind(labels).record(5); + + const records = await recorder.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + `test_count{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n` + + `test_sum{foo1="bar1",foo2="bar2"} 5 ${mockedHrTimeMs}\n` + + `test_bucket{foo1="bar1",foo2="bar2",le="1"} 0 ${mockedHrTimeMs}\n` + + `test_bucket{foo1="bar1",foo2="bar2",le="10"} 1 ${mockedHrTimeMs}\n` + + `test_bucket{foo1="bar1",foo2="bar2",le="100"} 0 ${mockedHrTimeMs}\n` + + `test_bucket{foo1="bar1",foo2="bar2",le="+Inf"} 0 ${mockedHrTimeMs}\n` + ); + }); + + it('serialize metric record with sum aggregator without timestamp', async () => { + const serializer = new PrometheusSerializer(undefined, false); + + const batcher = new ExactBatcher(HistogramAggregator, [1, 10, 100]); + const meter = new MeterProvider({ batcher }).getMeter('test'); + const recorder = meter.createValueRecorder('test', { + description: 'foobar', + }) as ValueRecorderMetric; + recorder.bind(labels).record(5); + + const records = await recorder.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + 'test_count{foo1="bar1",foo2="bar2"} 1\n' + + 'test_sum{foo1="bar1",foo2="bar2"} 5\n' + + 'test_bucket{foo1="bar1",foo2="bar2",le="1"} 0\n' + + 'test_bucket{foo1="bar1",foo2="bar2",le="10"} 1\n' + + 'test_bucket{foo1="bar1",foo2="bar2",le="100"} 0\n' + + 'test_bucket{foo1="bar1",foo2="bar2",le="+Inf"} 0\n' + ); + }); + }); + }); + + describe('serialize a checkpoint set', () => { + describe('with SumAggregator', () => { + mockAggregator(SumAggregator); + + it('should serialize metric record with sum aggregator', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const batcher = new PrometheusLabelsBatcher(); + const counter = meter.createCounter('test', { + description: 'foobar', + }) as CounterMetric; + counter.bind({ val: '1' }).add(1); + counter.bind({ val: '2' }).add(1); + + const records = await counter.getMetricRecord(); + records.forEach(it => batcher.process(it)); + const checkPointSet = batcher.checkPointSet(); + + const result = serializer.serialize(checkPointSet); + assert.strictEqual( + result, + '# HELP test foobar\n' + + '# TYPE test counter\n' + + `test{val="1"} 1 ${mockedHrTimeMs}\n` + + `test{val="2"} 1 ${mockedHrTimeMs}\n` + ); + }); + + it('serialize metric record with sum aggregator without timestamp', async () => { + const serializer = new PrometheusSerializer(undefined, false); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const batcher = new PrometheusLabelsBatcher(); + const counter = meter.createCounter('test', { + description: 'foobar', + }) as CounterMetric; + counter.bind({ val: '1' }).add(1); + counter.bind({ val: '2' }).add(1); + + const records = await counter.getMetricRecord(); + records.forEach(it => batcher.process(it)); + const checkPointSet = batcher.checkPointSet(); + + const result = serializer.serialize(checkPointSet); + assert.strictEqual( + result, + '# HELP test foobar\n' + + '# TYPE test counter\n' + + 'test{val="1"} 1\n' + + 'test{val="2"} 1\n' + ); + }); + }); + + describe('with MinMaxLastSumCountAggregator', () => { + mockAggregator(MinMaxLastSumCountAggregator); + + it('serialize metric record with MinMaxLastSumCountAggregator aggregator', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(MinMaxLastSumCountAggregator), + }).getMeter('test'); + const batcher = new PrometheusLabelsBatcher(); + const counter = meter.createCounter('test', { + description: 'foobar', + }) as CounterMetric; + counter.bind({ val: '1' }).add(1); + counter.bind({ val: '2' }).add(1); + + const records = await counter.getMetricRecord(); + records.forEach(it => batcher.process(it)); + const checkPointSet = batcher.checkPointSet(); + + const result = serializer.serialize(checkPointSet); + assert.strictEqual( + result, + '# HELP test foobar\n' + + '# TYPE test summary\n' + + `test_count{val="1"} 1 ${mockedHrTimeMs}\n` + + `test_sum{val="1"} 1 ${mockedHrTimeMs}\n` + + `test{val="1",quantile="0"} 1 ${mockedHrTimeMs}\n` + + `test{val="1",quantile="1"} 1 ${mockedHrTimeMs}\n` + + `test_count{val="2"} 1 ${mockedHrTimeMs}\n` + + `test_sum{val="2"} 1 ${mockedHrTimeMs}\n` + + `test{val="2",quantile="0"} 1 ${mockedHrTimeMs}\n` + + `test{val="2",quantile="1"} 1 ${mockedHrTimeMs}\n` + ); + }); + }); + + describe('with HistogramAggregator', () => { + mockAggregator(HistogramAggregator); + + it('serialize metric record with MinMaxLastSumCountAggregator aggregator', async () => { + const serializer = new PrometheusSerializer(); + + const batcher = new ExactBatcher(HistogramAggregator, [1, 10, 100]); + const meter = new MeterProvider({ batcher }).getMeter('test'); + const recorder = meter.createValueRecorder('test', { + description: 'foobar', + }) as ValueRecorderMetric; + recorder.bind({ val: '1' }).record(5); + recorder.bind({ val: '2' }).record(5); + + const records = await recorder.getMetricRecord(); + const labelBatcher = new PrometheusLabelsBatcher(); + records.forEach(it => labelBatcher.process(it)); + const checkPointSet = labelBatcher.checkPointSet(); + + const result = serializer.serialize(checkPointSet); + assert.strictEqual( + result, + '# HELP test foobar\n' + + '# TYPE test histogram\n' + + `test_count{val="1"} 1 ${mockedHrTimeMs}\n` + + `test_sum{val="1"} 5 ${mockedHrTimeMs}\n` + + `test_bucket{val="1",le="1"} 0 ${mockedHrTimeMs}\n` + + `test_bucket{val="1",le="10"} 1 ${mockedHrTimeMs}\n` + + `test_bucket{val="1",le="100"} 0 ${mockedHrTimeMs}\n` + + `test_bucket{val="1",le="+Inf"} 0 ${mockedHrTimeMs}\n` + + `test_count{val="2"} 1 ${mockedHrTimeMs}\n` + + `test_sum{val="2"} 5 ${mockedHrTimeMs}\n` + + `test_bucket{val="2",le="1"} 0 ${mockedHrTimeMs}\n` + + `test_bucket{val="2",le="10"} 1 ${mockedHrTimeMs}\n` + + `test_bucket{val="2",le="100"} 0 ${mockedHrTimeMs}\n` + + `test_bucket{val="2",le="+Inf"} 0 ${mockedHrTimeMs}\n` + ); + }); + }); + }); + + describe('serialize non-normalized values', () => { + describe('with SumAggregator', () => { + mockAggregator(SumAggregator); + + it('should serialize records without labels', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter.bind({}).add(1); + + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual(result, `test 1 ${mockedHrTimeMs}\n`); + }); + + it('should serialize non-string label values', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter + .bind(({ + object: {}, + NaN: NaN, + null: null, + undefined: undefined, + } as unknown) as Labels) + .add(1); + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + `test{object="[object Object]",NaN="NaN",null="null",undefined="undefined"} 1 ${mockedHrTimeMs}\n` + ); + }); + + it('should serialize non-finite values', async () => { + const serializer = new PrometheusSerializer(); + const cases = [ + [NaN, 'Nan'], + [-Infinity, '-Inf'], + [+Infinity, '+Inf'], + ] as [number, string][]; + + for (const esac of cases) { + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const counter = meter.createUpDownCounter( + 'test' + ) as UpDownCounterMetric; + counter.bind(labels).add(esac[0]); + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + `test{foo1="bar1",foo2="bar2"} ${esac[1]} ${mockedHrTimeMs}\n` + ); + } + }); + + it('should escape backslash (\\), double-quote ("), and line feed (\\n) in label values', async () => { + const serializer = new PrometheusSerializer(); + + const meter = new MeterProvider({ + batcher: new ExactBatcher(SumAggregator), + }).getMeter('test'); + const counter = meter.createCounter('test') as CounterMetric; + counter + .bind(({ + backslash: '\u005c', // \ => \\ (\u005c\u005c) + doubleQuote: '\u0022', // " => \" (\u005c\u0022) + lineFeed: '\u000a', // ↵ => \n (\u005c\u006e) + backslashN: '\u005c\u006e', // \n => \\n (\u005c\u005c\u006e) + backslashDoubleQuote: '\u005c\u0022', // \" => \\\" (\u005c\u005c\u005c\u0022) + backslashLineFeed: '\u005c\u000a', // \↵ => \\\n (\u005c\u005c\u005c\u006e) + } as unknown) as Labels) + .add(1); + const records = await counter.getMetricRecord(); + const record = records[0]; + + const result = serializer.serializeRecord( + record.descriptor.name, + record + ); + assert.strictEqual( + result, + 'test{' + + 'backslash="\u005c\u005c",' + + 'doubleQuote="\u005c\u0022",' + + 'lineFeed="\u005c\u006e",' + + 'backslashN="\u005c\u005c\u006e",' + + 'backslashDoubleQuote="\u005c\u005c\u005c\u0022",' + + 'backslashLineFeed="\u005c\u005c\u005c\u006e"' + + `} 1 ${mockedHrTimeMs}\n` + ); + }); + }); + }); +}); diff --git a/packages/opentelemetry-exporter-prometheus/test/util.ts b/packages/opentelemetry-exporter-prometheus/test/util.ts new file mode 100644 index 0000000000..697dd8ee54 --- /dev/null +++ b/packages/opentelemetry-exporter-prometheus/test/util.ts @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Point, Sum } from '@opentelemetry/metrics'; +import { HrTime } from '@opentelemetry/api'; + +export const mockedHrTime: HrTime = [1586347902, 211_000_000]; +export const mockedHrTimeMs = 1586347902211; +export function mockAggregator(Aggregator: any) { + let toPoint: () => Point; + before(() => { + toPoint = Aggregator.prototype.toPoint; + Aggregator.prototype.toPoint = function (): Point { + const point = toPoint.apply(this); + point.timestamp = mockedHrTime; + return point; + }; + }); + after(() => { + Aggregator.prototype.toPoint = toPoint; + }); +} diff --git a/packages/opentelemetry-exporter-zipkin/README.md b/packages/opentelemetry-exporter-zipkin/README.md index f6b78c6a1c..ccd4404c67 100644 --- a/packages/opentelemetry-exporter-zipkin/README.md +++ b/packages/opentelemetry-exporter-zipkin/README.md @@ -44,8 +44,8 @@ tracer.addSpanProcessor(new BatchSpanProcessor(exporter)); You can use built-in `SimpleSpanProcessor` or `BatchSpanProcessor` or write your own. -- [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`. -- [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization. +- [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`. +- [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization. ## Viewing your traces diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json index c306b0ba37..a662012391 100644 --- a/packages/opentelemetry-exporter-zipkin/package.json +++ b/packages/opentelemetry-exporter-zipkin/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-zipkin", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Zipkin Exporter allows the user to send collected traces to Zipkin.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -53,9 +53,9 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" } } diff --git a/packages/opentelemetry-exporter-zipkin/src/version.ts b/packages/opentelemetry-exporter-zipkin/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-exporter-zipkin/src/version.ts +++ b/packages/opentelemetry-exporter-zipkin/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-grpc-utils/package.json b/packages/opentelemetry-grpc-utils/package.json index 5ba29de58a..7779775313 100644 --- a/packages/opentelemetry-grpc-utils/package.json +++ b/packages/opentelemetry-grpc-utils/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/grpc-utils", - "version": "0.10.2", + "version": "0.11.0", "private": true, "description": "OpenTelemetry grpc plugin utility functions.", "main": "build/src/index.js", @@ -44,10 +44,10 @@ "devDependencies": { "@grpc/grpc-js": "1.1.3", "@grpc/proto-loader": "0.5.5", - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/mocha": "7.0.2", "@types/node": "14.0.27", "@types/semver": "7.3.2", @@ -67,9 +67,9 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", "shimmer": "1.2.1" } } diff --git a/packages/opentelemetry-grpc-utils/src/version.ts b/packages/opentelemetry-grpc-utils/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-grpc-utils/src/version.ts +++ b/packages/opentelemetry-grpc-utils/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-metrics/package.json b/packages/opentelemetry-metrics/package.json index 13f2132802..2cb2d4fa16 100644 --- a/packages/opentelemetry-metrics/package.json +++ b/packages/opentelemetry-metrics/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/metrics", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry metrics SDK", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -56,8 +56,8 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/resources": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/resources": "^0.11.0" } } diff --git a/packages/opentelemetry-metrics/src/BoundInstrument.ts b/packages/opentelemetry-metrics/src/BoundInstrument.ts index 087c6312bd..e84bb72487 100644 --- a/packages/opentelemetry-metrics/src/BoundInstrument.ts +++ b/packages/opentelemetry-metrics/src/BoundInstrument.ts @@ -126,34 +126,17 @@ export class BoundUpDownCounter export class BoundValueRecorder extends BaseBoundInstrument implements api.BoundValueRecorder { - private readonly _absolute: boolean; - constructor( labels: api.Labels, disabled: boolean, - absolute: boolean, valueType: api.ValueType, logger: api.Logger, aggregator: Aggregator ) { super(labels, logger, disabled, valueType, aggregator); - this._absolute = absolute; } - record( - value: number, - correlationContext?: api.CorrelationContext, - spanContext?: api.SpanContext - ): void { - if (this._absolute && value < 0) { - this._logger.error( - `Absolute ValueRecorder cannot contain negative values for $${Object.values( - this._labels - )}` - ); - return; - } - + record(value: number): void { this.update(value); } } @@ -161,7 +144,9 @@ export class BoundValueRecorder /** * BoundObserver is an implementation of the {@link BoundObserver} interface. */ -export class BoundObserver extends BaseBoundInstrument { +export class BoundObserver + extends BaseBoundInstrument + implements api.BoundBaseObserver { constructor( labels: api.Labels, disabled: boolean, diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts index 4bfb3d38f5..0ab0d78c95 100644 --- a/packages/opentelemetry-metrics/src/Meter.ts +++ b/packages/opentelemetry-metrics/src/Meter.ts @@ -79,7 +79,6 @@ export class Meter implements api.Meter { const opt: api.MetricOptions = { logger: this._logger, ...DEFAULT_METRIC_OPTIONS, - absolute: true, // value recorders are defined as absolute by default ...options, }; diff --git a/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts b/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts index a65581dadc..8275764105 100644 --- a/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts +++ b/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts @@ -26,8 +26,6 @@ import { Metric } from './Metric'; export class ValueRecorderMetric extends Metric implements api.ValueRecorder { - protected readonly _absolute: boolean; - constructor( name: string, options: api.MetricOptions, @@ -42,14 +40,12 @@ export class ValueRecorderMetric resource, instrumentationLibrary ); - - this._absolute = options.absolute !== undefined ? options.absolute : true; // Absolute default is true } + protected _makeInstrument(labels: api.Labels): BoundValueRecorder { return new BoundValueRecorder( labels, this._disabled, - this._absolute, this._valueType, this._logger, this._batcher.aggregatorFor(this._descriptor) diff --git a/packages/opentelemetry-metrics/src/export/aggregators/Histogram.ts b/packages/opentelemetry-metrics/src/export/aggregators/Histogram.ts index 1770952177..a44ea9836b 100644 --- a/packages/opentelemetry-metrics/src/export/aggregators/Histogram.ts +++ b/packages/opentelemetry-metrics/src/export/aggregators/Histogram.ts @@ -39,7 +39,7 @@ export class HistogramAggregator implements HistogramAggregatorType { } // we need to an ordered set to be able to correctly compute count for each // boundary since we'll iterate on each in order. - this._boundaries = boundaries.sort(); + this._boundaries = boundaries.sort((a, b) => a - b); this._current = this._newEmptyCheckpoint(); this._lastUpdateTime = hrTime(); } diff --git a/packages/opentelemetry-metrics/src/types.ts b/packages/opentelemetry-metrics/src/types.ts index 87e07df10f..d8e5768a48 100644 --- a/packages/opentelemetry-metrics/src/types.ts +++ b/packages/opentelemetry-metrics/src/types.ts @@ -53,7 +53,6 @@ export const DEFAULT_CONFIG = { /** The default metric creation options value. */ export const DEFAULT_METRIC_OPTIONS = { disabled: false, - absolute: false, description: '', unit: '1', valueType: api.ValueType.DOUBLE, diff --git a/packages/opentelemetry-metrics/src/version.ts b/packages/opentelemetry-metrics/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-metrics/src/version.ts +++ b/packages/opentelemetry-metrics/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts index 19960fcb9f..f137b7fba7 100644 --- a/packages/opentelemetry-metrics/test/Meter.test.ts +++ b/packages/opentelemetry-metrics/test/Meter.test.ts @@ -555,31 +555,6 @@ describe('Meter', () => { assert.ok(valueRecorder instanceof Metric); }); - it('should be absolute by default', () => { - const valueRecorder = meter.createValueRecorder('name', { - description: 'desc', - unit: '1', - disabled: false, - }); - assert.strictEqual( - (valueRecorder as ValueRecorderMetric)['_absolute'], - true - ); - }); - - it('should be able to set absolute to false', () => { - const valueRecorder = meter.createValueRecorder('name', { - description: 'desc', - unit: '1', - disabled: false, - absolute: false, - }); - assert.strictEqual( - (valueRecorder as ValueRecorderMetric)['_absolute'], - false - ); - }); - it('should pipe through resource', async () => { const valueRecorder = meter.createValueRecorder( 'name' @@ -638,10 +613,12 @@ describe('Meter', () => { assert.doesNotThrow(() => boundValueRecorder.record(10)); }); - it('should not accept negative values by default', async () => { - const valueRecorder = meter.createValueRecorder('name'); + it('should not set the instrument data when disabled', async () => { + const valueRecorder = meter.createValueRecorder('name', { + disabled: true, + }) as ValueRecorderMetric; const boundValueRecorder = valueRecorder.bind(labels); - boundValueRecorder.record(-10); + boundValueRecorder.record(10); await meter.collect(); const [record1] = meter.getBatcher().checkPointSet(); @@ -657,57 +634,30 @@ describe('Meter', () => { ); }); - it('should not set the instrument data when disabled', async () => { - const valueRecorder = meter.createValueRecorder('name', { - disabled: true, - }) as ValueRecorderMetric; + it('should accept negative (and positive) values', async () => { + const valueRecorder = meter.createValueRecorder('name'); const boundValueRecorder = valueRecorder.bind(labels); - boundValueRecorder.record(10); + boundValueRecorder.record(-10); + boundValueRecorder.record(50); await meter.collect(); const [record1] = meter.getBatcher().checkPointSet(); assert.deepStrictEqual( record1.aggregator.toPoint().value as Distribution, { - count: 0, - last: 0, - max: -Infinity, - min: Infinity, - sum: 0, + count: 2, + last: 50, + max: 50, + min: -10, + sum: 40, } ); + assert.ok( + hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) > + hrTimeToNanoseconds(performanceTimeOrigin) + ); }); - it( - 'should accept negative (and positive) values when absolute is set' + - ' to false', - async () => { - const valueRecorder = meter.createValueRecorder('name', { - absolute: false, - }); - const boundValueRecorder = valueRecorder.bind(labels); - boundValueRecorder.record(-10); - boundValueRecorder.record(50); - - await meter.collect(); - const [record1] = meter.getBatcher().checkPointSet(); - assert.deepStrictEqual( - record1.aggregator.toPoint().value as Distribution, - { - count: 2, - last: 50, - max: 50, - min: -10, - sum: 40, - } - ); - assert.ok( - hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) > - hrTimeToNanoseconds(performanceTimeOrigin) - ); - } - ); - it('should return same instrument on same label values', async () => { const valueRecorder = meter.createValueRecorder( 'name' diff --git a/packages/opentelemetry-metrics/test/export/aggregators/Histogram.test.ts b/packages/opentelemetry-metrics/test/export/aggregators/Histogram.test.ts index c2397ae0b8..f2882858c2 100644 --- a/packages/opentelemetry-metrics/test/export/aggregators/Histogram.test.ts +++ b/packages/opentelemetry-metrics/test/export/aggregators/Histogram.test.ts @@ -27,9 +27,23 @@ describe('HistogramAggregator', () => { }); it('should sort boundaries', () => { - const aggregator = new HistogramAggregator([500, 300, 700]); + const aggregator = new HistogramAggregator([ + 200, + 500, + 300, + 700, + 1000, + 1500, + ]); const point = aggregator.toPoint().value as Histogram; - assert.deepEqual(point.buckets.boundaries, [300, 500, 700]); + assert.deepEqual(point.buckets.boundaries, [ + 200, + 300, + 500, + 700, + 1000, + 1500, + ]); }); it('should throw if no boundaries are defined', () => { diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index 02fef94268..4c8dfda4fa 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/node", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Node SDK provides automatic telemetry (tracing, metrics, etc) for Node.js applications", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -42,8 +42,8 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "@types/semver": "7.3.2", @@ -59,10 +59,10 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "require-in-the-middle": "^5.0.0", "semver": "^7.1.3" } diff --git a/packages/opentelemetry-node/src/version.ts b/packages/opentelemetry-node/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-node/src/version.ts +++ b/packages/opentelemetry-node/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-node/test/registration.test.ts b/packages/opentelemetry-node/test/registration.test.ts index af4f08183c..aef79760f5 100644 --- a/packages/opentelemetry-node/test/registration.test.ts +++ b/packages/opentelemetry-node/test/registration.test.ts @@ -16,9 +16,10 @@ import { context, - NoopHttpTextPropagator, + NoopTextMapPropagator, propagation, trace, + ProxyTracerProvider, } from '@opentelemetry/api'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; import { NoopContextManager } from '@opentelemetry/context-base'; @@ -43,14 +44,17 @@ describe('API registration', () => { assert.ok( propagation['_getGlobalPropagator']() instanceof CompositePropagator ); - assert.ok(trace.getTracerProvider() === tracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); it('should register configured implementations', () => { const tracerProvider = new NodeTracerProvider(); const contextManager = new NoopContextManager(); - const propagator = new NoopHttpTextPropagator(); + const propagator = new NoopTextMapPropagator(); tracerProvider.register({ contextManager, @@ -60,7 +64,9 @@ describe('API registration', () => { assert.ok(context['_getContextManager']() === contextManager); assert.ok(propagation['_getGlobalPropagator']() === propagator); - assert.ok(trace.getTracerProvider() === tracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); it('should skip null context manager', () => { @@ -74,7 +80,10 @@ describe('API registration', () => { assert.ok( propagation['_getGlobalPropagator']() instanceof CompositePropagator ); - assert.ok(trace.getTracerProvider() === tracerProvider); + + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); it('should skip null propagator', () => { @@ -84,12 +93,15 @@ describe('API registration', () => { }); assert.ok( - propagation['_getGlobalPropagator']() instanceof NoopHttpTextPropagator + propagation['_getGlobalPropagator']() instanceof NoopTextMapPropagator ); assert.ok( context['_getContextManager']() instanceof AsyncHooksContextManager ); - assert.ok(trace.getTracerProvider() === tracerProvider); + + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); }); diff --git a/packages/opentelemetry-plugin-fetch/package.json b/packages/opentelemetry-plugin-fetch/package.json index 62eda21d84..e0a56a96c5 100644 --- a/packages/opentelemetry-plugin-fetch/package.json +++ b/packages/opentelemetry-plugin-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-fetch", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry fetch automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -45,8 +45,8 @@ }, "devDependencies": { "@babel/core": "7.11.1", - "@opentelemetry/context-zone": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-zone": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "@types/shimmer": "1.0.1", @@ -75,9 +75,9 @@ "webpack-merge": "5.1.1" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/web": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/web": "^0.11.0", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-fetch/src/version.ts b/packages/opentelemetry-plugin-fetch/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-plugin-fetch/src/version.ts +++ b/packages/opentelemetry-plugin-fetch/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-plugin-grpc-js/package.json b/packages/opentelemetry-plugin-grpc-js/package.json index 7493356fa4..4edac510ce 100644 --- a/packages/opentelemetry-plugin-grpc-js/package.json +++ b/packages/opentelemetry-plugin-grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-grpc-js", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry @grpc/grpc-js automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -45,11 +45,11 @@ }, "devDependencies": { "@grpc/grpc-js": "1.1.3", - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/grpc-utils": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/grpc-utils": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "@types/semver": "7.3.2", @@ -67,9 +67,9 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", "shimmer": "1.2.1" } } diff --git a/packages/opentelemetry-plugin-grpc-js/src/version.ts b/packages/opentelemetry-plugin-grpc-js/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-plugin-grpc-js/src/version.ts +++ b/packages/opentelemetry-plugin-grpc-js/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-plugin-grpc/package.json b/packages/opentelemetry-plugin-grpc/package.json index 30f6d389aa..5a49f8a85c 100644 --- a/packages/opentelemetry-plugin-grpc/package.json +++ b/packages/opentelemetry-plugin-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-grpc", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry grpc automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -42,11 +42,11 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/grpc-utils": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/grpc-utils": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "@types/semver": "7.3.2", @@ -66,9 +66,9 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-grpc/src/version.ts b/packages/opentelemetry-plugin-grpc/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-plugin-grpc/src/version.ts +++ b/packages/opentelemetry-plugin-grpc/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-plugin-http/package.json b/packages/opentelemetry-plugin-http/package.json index 17cf452ca5..2939ce8aaa 100644 --- a/packages/opentelemetry-plugin-http/package.json +++ b/packages/opentelemetry-plugin-http/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-http", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry http automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -15,7 +15,8 @@ "precompile": "tsc --version", "version:update": "node ../../scripts/version-update.js", "compile": "npm run version:update && tsc -p .", - "prepare": "npm run compile" + "prepare": "npm run compile", + "watch": "tsc -w" }, "keywords": [ "opentelemetry", @@ -42,10 +43,10 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/got": "9.6.11", "@types/mocha": "8.0.2", "@types/node": "14.0.27", @@ -71,9 +72,9 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", "semver": "^7.1.3", "shimmer": "^1.2.1" } diff --git a/packages/opentelemetry-plugin-http/src/version.ts b/packages/opentelemetry-plugin-http/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-plugin-http/src/version.ts +++ b/packages/opentelemetry-plugin-http/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts b/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts index fe7ef01e51..0c787501a9 100644 --- a/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts +++ b/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Context, HttpTextPropagator, TraceFlags } from '@opentelemetry/api'; +import { Context, TextMapPropagator, TraceFlags } from '@opentelemetry/api'; import { getParentSpanContext, setExtractedSpanContext, } from '@opentelemetry/core'; import * as http from 'http'; -export class DummyPropagation implements HttpTextPropagator { +export class DummyPropagation implements TextMapPropagator { static TRACE_CONTEXT_KEY = 'x-dummy-trace-id'; static SPAN_CONTEXT_KEY = 'x-dummy-span-id'; extract(context: Context, carrier: http.OutgoingHttpHeaders) { diff --git a/packages/opentelemetry-plugin-https/package.json b/packages/opentelemetry-plugin-https/package.json index cea443846a..c15cb07cc4 100644 --- a/packages/opentelemetry-plugin-https/package.json +++ b/packages/opentelemetry-plugin-https/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-https", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry https automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -42,10 +42,10 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/context-async-hooks": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-async-hooks": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/got": "9.6.11", "@types/mocha": "8.0.2", "@types/node": "14.0.27", @@ -71,10 +71,10 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/plugin-http": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/plugin-http": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", "semver": "^7.1.3", "shimmer": "^1.2.1" } diff --git a/packages/opentelemetry-plugin-https/src/version.ts b/packages/opentelemetry-plugin-https/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-plugin-https/src/version.ts +++ b/packages/opentelemetry-plugin-https/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts b/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts index 906efe7968..4acd3835dc 100644 --- a/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts +++ b/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Context, HttpTextPropagator, TraceFlags } from '@opentelemetry/api'; +import { Context, TextMapPropagator, TraceFlags } from '@opentelemetry/api'; import { setExtractedSpanContext, getParentSpanContext, } from '@opentelemetry/core'; import * as http from 'http'; -export class DummyPropagation implements HttpTextPropagator { +export class DummyPropagation implements TextMapPropagator { static TRACE_CONTEXT_KEY = 'x-dummy-trace-id'; static SPAN_CONTEXT_KEY = 'x-dummy-span-id'; extract(context: Context, carrier: http.OutgoingHttpHeaders) { diff --git a/packages/opentelemetry-plugin-xml-http-request/package.json b/packages/opentelemetry-plugin-xml-http-request/package.json index 4dfca75f1e..2c508d95bc 100644 --- a/packages/opentelemetry-plugin-xml-http-request/package.json +++ b/packages/opentelemetry-plugin-xml-http-request/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-xml-http-request", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry XMLHttpRequest automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -45,8 +45,8 @@ }, "devDependencies": { "@babel/core": "7.11.1", - "@opentelemetry/context-zone": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/context-zone": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "@types/shimmer": "1.0.1", @@ -75,10 +75,10 @@ "webpack-merge": "5.1.1" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", - "@opentelemetry/web": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", + "@opentelemetry/web": "^0.11.0", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-xml-http-request/src/version.ts b/packages/opentelemetry-plugin-xml-http-request/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-plugin-xml-http-request/src/version.ts +++ b/packages/opentelemetry-plugin-xml-http-request/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts b/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts index c28d658f7b..8252b15fc8 100644 --- a/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts +++ b/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts @@ -272,8 +272,9 @@ export class XMLHttpRequestPlugin extends BasePlugin { this._logger.debug('ignoring span as url matches ignored url'); return; } + const spanName = `HTTP ${method.toUpperCase()}`; - const currentSpan = this._tracer.startSpan(url, { + const currentSpan = this._tracer.startSpan(spanName, { kind: api.SpanKind.CLIENT, attributes: { [HttpAttribute.HTTP_METHOD]: method, diff --git a/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts b/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts new file mode 100644 index 0000000000..2423eda95a --- /dev/null +++ b/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { XMLHttpRequestPlugin } from '../src'; +import { ReadableSpan, SpanProcessor } from '@opentelemetry/tracing'; +import { WebTracerProvider } from '@opentelemetry/web'; +import assert = require('assert'); +import { HttpAttribute } from '@opentelemetry/semantic-conventions'; + +class TestSpanProcessor implements SpanProcessor { + spans: ReadableSpan[] = []; + + forceFlush(callback: () => void): void {} + onStart(span: ReadableSpan): void {} + shutdown(callback: () => void): void {} + + onEnd(span: ReadableSpan): void { + this.spans.push(span); + } +} + +describe('unmocked xhr', () => { + let testSpans: TestSpanProcessor; + let provider: WebTracerProvider; + beforeEach(() => { + provider = new WebTracerProvider({ + plugins: [new XMLHttpRequestPlugin()], + }); + testSpans = new TestSpanProcessor(); + provider.addSpanProcessor(testSpans); + }); + afterEach(() => { + // nop + }); + + it('should find resource with a relative url', done => { + const xhr = new XMLHttpRequest(); + let path = location.pathname; + path = path.substring(path.lastIndexOf('/') + 1); + xhr.open('GET', path); + xhr.addEventListener('loadend', () => { + setTimeout(() => { + assert.strictEqual(testSpans.spans.length, 1); + const span = testSpans.spans[0]; + // content length comes from the PerformanceTiming resource; this ensures that our + // matching logic found the right one + assert.ok( + (span.attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] as any) > + 0 + ); + done(); + }, 500); + }); + xhr.send(); + }); +}); diff --git a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts index 8cd82b2a19..3f20238252 100644 --- a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts +++ b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts @@ -265,7 +265,7 @@ describe('xhr', () => { it('span should have correct name', () => { const span: tracing.ReadableSpan = exportSpy.args[1][0][0]; - assert.strictEqual(span.name, url, 'span has wrong name'); + assert.strictEqual(span.name, 'HTTP GET', 'span has wrong name'); }); it('span should have correct kind', () => { diff --git a/packages/opentelemetry-resource-detector-aws/package.json b/packages/opentelemetry-resource-detector-aws/package.json index 8cc341f78b..b4d4a279c6 100644 --- a/packages/opentelemetry-resource-detector-aws/package.json +++ b/packages/opentelemetry-resource-detector-aws/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/resource-detector-aws", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry SDK resource detector for AWS", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -56,8 +56,8 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/resources": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/resources": "^0.11.0" } } diff --git a/packages/opentelemetry-resource-detector-aws/src/version.ts b/packages/opentelemetry-resource-detector-aws/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-resource-detector-aws/src/version.ts +++ b/packages/opentelemetry-resource-detector-aws/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-resource-detector-gcp/README.md b/packages/opentelemetry-resource-detector-gcp/README.md index ccb2af7b09..8f493387b0 100644 --- a/packages/opentelemetry-resource-detector-gcp/README.md +++ b/packages/opentelemetry-resource-detector-gcp/README.md @@ -12,6 +12,8 @@ The OpenTelemetry Resource is an immutable representation of the entity producin ## Installation +The GCP resource detector requires Node.JS 10+ due to a dependency on [`gcp-metadata`](https://www.npmjs.com/package/gcp-metadata) which uses features only available in Node.JS 10+. + ```bash npm install --save @opentelemetry/resource-detector-gcp ``` diff --git a/packages/opentelemetry-resource-detector-gcp/package.json b/packages/opentelemetry-resource-detector-gcp/package.json index c059e00f93..93b8c4337a 100644 --- a/packages/opentelemetry-resource-detector-gcp/package.json +++ b/packages/opentelemetry-resource-detector-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/resource-detector-gcp", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry SDK resource detector for GCP", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -27,7 +27,7 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" }, "files": [ "build/src/**/*.js", @@ -41,24 +41,23 @@ "access": "public" }, "devDependencies": { + "@opentelemetry/core": "^0.11.0", "@types/mocha": "8.0.1", "@types/node": "14.0.27", - "@types/sinon": "9.0.4", + "@types/semver": "7.3.3", "codecov": "3.7.2", "gts": "2.0.2", "mocha": "7.2.0", "nock": "12.0.3", "nyc": "15.1.0", "rimraf": "3.0.2", - "sinon": "9.0.2", "ts-mocha": "7.0.0", "ts-node": "8.10.2", "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", - "gcp-metadata": "^3.5.0" + "@opentelemetry/resources": "^0.11.0", + "gcp-metadata": "^4.1.4", + "semver": "7.3.2" } } diff --git a/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts b/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts index 2cc793bc16..ed01accf62 100644 --- a/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts +++ b/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts @@ -15,6 +15,7 @@ */ import * as os from 'os'; +import * as semver from 'semver'; import * as gcpMetadata from 'gcp-metadata'; import { Detector, @@ -42,7 +43,10 @@ class GcpDetector implements Detector { * @param config The resource detection config with a required logger */ async detect(config: ResourceDetectionConfigWithLogger): Promise { - if (!(await gcpMetadata.isAvailable())) { + if ( + !semver.satisfies(process.version, '>=10') || + !(await gcpMetadata.isAvailable()) + ) { config.logger.debug('GcpDetector failed: GCP Metadata unavailable.'); return Resource.empty(); } diff --git a/packages/opentelemetry-resource-detector-gcp/src/version.ts b/packages/opentelemetry-resource-detector-gcp/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-resource-detector-gcp/src/version.ts +++ b/packages/opentelemetry-resource-detector-gcp/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts b/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts index f2ae581553..4d227919f2 100644 --- a/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts +++ b/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts @@ -23,6 +23,7 @@ import { resetIsAvailableCache, } from 'gcp-metadata'; import * as nock from 'nock'; +import * as semver from 'semver'; import { gcpDetector } from '../../src'; import { assertCloudResource, @@ -43,123 +44,126 @@ 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'; -describe('gcpDetector', () => { - describe('.detect', () => { - before(() => { - nock.disableNetConnect(); - }); - - after(() => { - nock.enableNetConnect(); - delete process.env.KUBERNETES_SERVICE_HOST; - delete process.env.NAMESPACE; - delete process.env.CONTAINER_NAME; - delete process.env.HOSTNAME; - }); - - beforeEach(() => { - resetIsAvailableCache(); - nock.cleanAll(); - delete process.env.KUBERNETES_SERVICE_HOST; - delete process.env.NAMESPACE; - delete process.env.CONTAINER_NAME; - delete process.env.HOSTNAME; - }); +(semver.satisfies(process.version, '>=10') ? describe : describe.skip)( + 'gcpDetector', + () => { + describe('.detect', () => { + before(() => { + nock.disableNetConnect(); + }); - it('should return resource with GCP metadata', async () => { - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .reply(200, () => 4520031799277581759, 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 secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const resource: Resource = await gcpDetector.detect({ - logger: new NoopLogger(), + after(() => { + nock.enableNetConnect(); + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.NAMESPACE; + delete process.env.CONTAINER_NAME; + delete process.env.HOSTNAME; }); - secondaryScope.done(); - scope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', + beforeEach(() => { + resetIsAvailableCache(); + nock.cleanAll(); + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.NAMESPACE; + delete process.env.CONTAINER_NAME; + delete process.env.HOSTNAME; }); - assertHostResource(resource, { id: '4520031799277582000' }); - }); - it('should populate K8s attributes when KUBERNETES_SERVICE_HOST is set', async () => { - process.env.KUBERNETES_SERVICE_HOST = 'my-host'; - process.env.NAMESPACE = 'my-namespace'; - process.env.HOSTNAME = 'my-hostname'; - process.env.CONTAINER_NAME = 'my-container-name'; - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .reply(200, () => 4520031799277581759, HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(200, () => 'my-cluster', HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(200, () => 'project/zone/my-zone', HEADERS); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const resource = await gcpDetector.detect({ logger: new NoopLogger() }); - secondaryScope.done(); - scope.done(); + it('should return resource with GCP metadata', async () => { + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .reply(200, () => 4520031799277581759, 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 secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource: Resource = await gcpDetector.detect({ + logger: new NoopLogger(), + }); + secondaryScope.done(); + scope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertHostResource(resource, { id: '4520031799277582000' }); }); - assertK8sResource(resource, { - clusterName: 'my-cluster', - podName: 'my-hostname', - namespaceName: 'my-namespace', + + it('should populate K8s attributes when KUBERNETES_SERVICE_HOST is set', async () => { + process.env.KUBERNETES_SERVICE_HOST = 'my-host'; + process.env.NAMESPACE = 'my-namespace'; + process.env.HOSTNAME = 'my-hostname'; + process.env.CONTAINER_NAME = 'my-container-name'; + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .reply(200, () => 4520031799277581759, HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(200, () => 'my-cluster', HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(200, () => 'project/zone/my-zone', HEADERS); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource = await gcpDetector.detect({ logger: new NoopLogger() }); + secondaryScope.done(); + scope.done(); + + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertK8sResource(resource, { + clusterName: 'my-cluster', + podName: 'my-hostname', + namespaceName: 'my-namespace', + }); + assertContainerResource(resource, { name: 'my-container-name' }); }); - assertContainerResource(resource, { name: 'my-container-name' }); - }); - it('should return resource and empty data for non-available metadata attributes', async () => { - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(413) - .get(INSTANCE_ID_PATH) - .reply(400, undefined, HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(413); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const resource = await gcpDetector.detect({ logger: new NoopLogger() }); - secondaryScope.done(); - scope.done(); + it('should return resource and empty data for non-available metadata attributes', async () => { + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(413) + .get(INSTANCE_ID_PATH) + .reply(400, undefined, HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(413); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource = await gcpDetector.detect({ logger: new NoopLogger() }); + secondaryScope.done(); + scope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: '', + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: '', + }); }); - }); - it('returns empty resource if not detected', async () => { - const resource = await gcpDetector.detect({ logger: new NoopLogger() }); - assertEmptyResource(resource); + it('returns empty resource if not detected', async () => { + const resource = await gcpDetector.detect({ logger: new NoopLogger() }); + assertEmptyResource(resource); + }); }); - }); -}); + } +); diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index 51f6ea5efc..6f81c3ad1b 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/resources", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry SDK resources", "main": "build/src/index.js", "browser": { @@ -60,7 +60,7 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0" } } diff --git a/packages/opentelemetry-resources/src/version.ts b/packages/opentelemetry-resources/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-resources/src/version.ts +++ b/packages/opentelemetry-resources/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-sdk-node/README.md b/packages/opentelemetry-sdk-node/README.md index 2db342d0d7..a2cf4318a4 100644 --- a/packages/opentelemetry-sdk-node/README.md +++ b/packages/opentelemetry-sdk-node/README.md @@ -76,7 +76,7 @@ Detect resources automatically from the environment using the default resource d Use a custom context manager. Default: [AsyncHooksContextManager](../opentelemetry-context-async-hooks/README.md) -### httpTextPropagator +### textMapPropagator Use a custom propagator. Default: [CompositePropagator](../opentelemetry-core/src/context/propagation/composite.ts) using [W3C Trace Context](../opentelemetry-core/README.md#httptracecontext-propagator) and [Correlation Context](../opentelemetry-core/README.md#correlation-context-propagator) diff --git a/packages/opentelemetry-sdk-node/package.json b/packages/opentelemetry-sdk-node/package.json index e87a38d71a..ad4bc3e3f3 100644 --- a/packages/opentelemetry-sdk-node/package.json +++ b/packages/opentelemetry-sdk-node/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-node", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry SDK for Node.js", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -42,28 +42,30 @@ "access": "public" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/metrics": "^0.10.2", - "@opentelemetry/node": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2", - "@opentelemetry/resource-detector-aws": "^0.10.2", - "@opentelemetry/resource-detector-gcp": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/metrics": "^0.11.0", + "@opentelemetry/node": "^0.11.0", + "@opentelemetry/resource-detector-aws": "^0.11.0", + "@opentelemetry/resource-detector-gcp": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", "nock": "12.0.3" }, "devDependencies": { - "@opentelemetry/context-async-hooks": "^0.10.2", + "@opentelemetry/context-async-hooks": "^0.11.0", "@types/mocha": "7.0.2", "@types/node": "14.0.27", + "@types/semver": "7.3.3", "@types/sinon": "9.0.4", "codecov": "3.7.2", - "gcp-metadata": "^3.5.0", + "gcp-metadata": "^4.1.4", "gts": "2.0.2", "istanbul-instrumenter-loader": "3.0.1", "mocha": "7.2.0", "nyc": "15.1.0", + "semver": "7.3.2", "sinon": "9.0.3", "ts-loader": "7.0.5", "ts-mocha": "7.0.0", diff --git a/packages/opentelemetry-sdk-node/src/sdk.ts b/packages/opentelemetry-sdk-node/src/sdk.ts index 3ab7d614e2..a411ab85df 100644 --- a/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/packages/opentelemetry-sdk-node/src/sdk.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HttpTextPropagator, metrics } from '@opentelemetry/api'; +import { TextMapPropagator, metrics } from '@opentelemetry/api'; import { ContextManager } from '@opentelemetry/context-base'; import { MeterConfig, MeterProvider } from '@opentelemetry/metrics'; import { NodeTracerConfig, NodeTracerProvider } from '@opentelemetry/node'; @@ -35,7 +35,7 @@ export class NodeSDK { tracerConfig: NodeTracerConfig; spanProcessor: SpanProcessor; contextManager?: ContextManager; - httpTextPropagator?: HttpTextPropagator; + textMapPropagator?: TextMapPropagator; }; private _meterProviderConfig?: MeterConfig; @@ -78,7 +78,7 @@ export class NodeSDK { tracerProviderConfig, spanProcessor, configuration.contextManager, - configuration.httpTextPropagator + configuration.textMapPropagator ); } @@ -110,13 +110,13 @@ export class NodeSDK { tracerConfig: NodeTracerConfig, spanProcessor: SpanProcessor, contextManager?: ContextManager, - httpTextPropagator?: HttpTextPropagator + textMapPropagator?: TextMapPropagator ) { this._tracerProviderConfig = { tracerConfig, spanProcessor, contextManager, - httpTextPropagator, + textMapPropagator, }; } @@ -157,7 +157,7 @@ export class NodeSDK { tracerProvider.addSpanProcessor(this._tracerProviderConfig.spanProcessor); tracerProvider.register({ contextManager: this._tracerProviderConfig.contextManager, - propagator: this._tracerProviderConfig.httpTextPropagator, + propagator: this._tracerProviderConfig.textMapPropagator, }); } diff --git a/packages/opentelemetry-sdk-node/src/types.ts b/packages/opentelemetry-sdk-node/src/types.ts index a093cbcfa4..4675c88b3d 100644 --- a/packages/opentelemetry-sdk-node/src/types.ts +++ b/packages/opentelemetry-sdk-node/src/types.ts @@ -21,7 +21,7 @@ export interface NodeSDKConfiguration { autoDetectResources: boolean; contextManager: ContextManager; defaultAttributes: api.Attributes; - httpTextPropagator: api.HttpTextPropagator; + textMapPropagator: api.TextMapPropagator; logger: api.Logger; logLevel: core.LogLevel; metricBatcher: metrics.Batcher; diff --git a/packages/opentelemetry-sdk-node/src/version.ts b/packages/opentelemetry-sdk-node/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-sdk-node/src/version.ts +++ b/packages/opentelemetry-sdk-node/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-sdk-node/test/sdk.test.ts b/packages/opentelemetry-sdk-node/test/sdk.test.ts index 0cb51a898e..34598a2cba 100644 --- a/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -15,14 +15,16 @@ */ import * as nock from 'nock'; +import * as semver from 'semver'; import { context, metrics, - NoopHttpTextPropagator, + NoopTextMapPropagator, NoopMeterProvider, NoopTracerProvider, propagation, trace, + ProxyTracerProvider, } from '@opentelemetry/api'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; import { NoopContextManager } from '@opentelemetry/context-base'; @@ -83,6 +85,7 @@ describe('Node SDK', () => { before(() => { // Disable attempted load of default plugins Sinon.replace(NodeConfig, 'DEFAULT_INSTRUMENTATION_PLUGINS', {}); + nock.disableNetConnect(); }); beforeEach(() => { @@ -102,10 +105,14 @@ describe('Node SDK', () => { assert.ok(context['_getContextManager']() instanceof NoopContextManager); assert.ok( - propagation['_getGlobalPropagator']() instanceof NoopHttpTextPropagator + propagation['_getGlobalPropagator']() instanceof NoopTextMapPropagator ); - assert.ok(trace.getTracerProvider() instanceof NoopTracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + console.log(apiTracerProvider); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() instanceof NoopTracerProvider); + assert.ok(metrics.getMeterProvider() instanceof NoopMeterProvider); }); @@ -125,7 +132,9 @@ describe('Node SDK', () => { assert.ok( propagation['_getGlobalPropagator']() instanceof CompositePropagator ); - assert.ok(trace.getTracerProvider() instanceof NodeTracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() instanceof NodeTracerProvider); }); it('should register a tracer provider if a span processor is provided', async () => { @@ -147,7 +156,9 @@ describe('Node SDK', () => { assert.ok( propagation['_getGlobalPropagator']() instanceof CompositePropagator ); - assert.ok(trace.getTracerProvider() instanceof NodeTracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() instanceof NodeTracerProvider); }); it('should register a meter provider if an exporter is provided', async () => { @@ -162,10 +173,12 @@ describe('Node SDK', () => { assert.ok(context['_getContextManager']() instanceof NoopContextManager); assert.ok( - propagation['_getGlobalPropagator']() instanceof NoopHttpTextPropagator + propagation['_getGlobalPropagator']() instanceof NoopTextMapPropagator ); - assert.ok(trace.getTracerProvider() instanceof NoopTracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() instanceof NoopTracerProvider); assert.ok(metrics.getMeterProvider() instanceof MeterProvider); }); @@ -184,69 +197,65 @@ describe('Node SDK', () => { delete process.env.OTEL_RESOURCE_ATTRIBUTES; }); - describe('in GCP environment', () => { - after(() => { - resetIsAvailableCache(); - }); - - it('returns a merged resource', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, + // GCP detector only works in 10+ + (semver.satisfies(process.version, '>=10') ? describe : describe.skip)( + 'in GCP environment', + () => { + after(() => { + resetIsAvailableCache(); }); - 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', + 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 gcpScope = nock(HOST_ADDRESS).get(INSTANCE_PATH).replyWithError({ - code: 'ENOTFOUND', - }); - const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .replyWithError({ - code: 'ENOTFOUND', - }); const awsScope = nock(AWS_HOST) .persist() .put(AWS_TOKEN_PATH) @@ -260,8 +269,6 @@ describe('Node SDK', () => { .reply(200, () => mockedHostResponse); await sdk.detectResources(); const resource: Resource = sdk['_resource']; - gcpSecondaryScope.done(); - gcpScope.done(); awsScope.done(); assertCloudResource(resource, { diff --git a/packages/opentelemetry-semantic-conventions/package.json b/packages/opentelemetry-semantic-conventions/package.json index 9dfa26c29b..2352b967a9 100644 --- a/packages/opentelemetry-semantic-conventions/package.json +++ b/packages/opentelemetry-semantic-conventions/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/semantic-conventions", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry semantic conventions", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -14,7 +14,8 @@ "precompile": "tsc --version", "version:update": "node ../../scripts/version-update.js", "compile": "npm run version:update && tsc -p .", - "prepare": "npm run compile" + "prepare": "npm run compile", + "watch": "tsc -w" }, "keywords": [ "opentelemetry", diff --git a/packages/opentelemetry-semantic-conventions/src/trace/exception.ts b/packages/opentelemetry-semantic-conventions/src/trace/exception.ts new file mode 100644 index 0000000000..cf7dc596be --- /dev/null +++ b/packages/opentelemetry-semantic-conventions/src/trace/exception.ts @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const ExceptionAttribute = { + MESSAGE: 'exception.message', + STACKTRACE: 'exception.stacktrace', + TYPE: 'exception.type', +}; + +export const ExceptionEventName = 'exception'; diff --git a/packages/opentelemetry-semantic-conventions/src/trace/index.ts b/packages/opentelemetry-semantic-conventions/src/trace/index.ts index ca18acaea6..9852f831bb 100644 --- a/packages/opentelemetry-semantic-conventions/src/trace/index.ts +++ b/packages/opentelemetry-semantic-conventions/src/trace/index.ts @@ -14,8 +14,9 @@ * limitations under the License. */ +export * from './database'; +export * from './exception'; export * from './general'; -export * from './rpc'; export * from './http'; -export * from './database'; export * from './os'; +export * from './rpc'; diff --git a/packages/opentelemetry-semantic-conventions/src/version.ts b/packages/opentelemetry-semantic-conventions/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-semantic-conventions/src/version.ts +++ b/packages/opentelemetry-semantic-conventions/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-shim-opentracing/package.json b/packages/opentelemetry-shim-opentracing/package.json index 70129eda4d..18385a5cf6 100644 --- a/packages/opentelemetry-shim-opentracing/package.json +++ b/packages/opentelemetry-shim-opentracing/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/shim-opentracing", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTracing to OpenTelemetry shim", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -40,7 +40,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/tracing": "^0.10.2", + "@opentelemetry/tracing": "^0.11.0", "@types/mocha": "8.0.2", "@types/node": "14.0.27", "codecov": "3.7.2", @@ -55,8 +55,8 @@ "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0", "opentracing": "^0.14.4" } } diff --git a/packages/opentelemetry-shim-opentracing/src/version.ts b/packages/opentelemetry-shim-opentracing/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-shim-opentracing/src/version.ts +++ b/packages/opentelemetry-shim-opentracing/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-shim-opentracing/test/Shim.test.ts b/packages/opentelemetry-shim-opentracing/test/Shim.test.ts index 51b0bdd890..10f2e8b2f7 100644 --- a/packages/opentelemetry-shim-opentracing/test/Shim.test.ts +++ b/packages/opentelemetry-shim-opentracing/test/Shim.test.ts @@ -19,13 +19,12 @@ import * as opentracing from 'opentracing'; import { BasicTracerProvider, Span } from '@opentelemetry/tracing'; import { TracerShim, SpanShim, SpanContextShim } from '../src/shim'; import { - INVALID_SPAN_CONTEXT, timeInputToHrTime, HttpTraceContext, CompositePropagator, HttpCorrelationContext, } from '@opentelemetry/core'; -import { propagation } from '@opentelemetry/api'; +import { INVALID_SPAN_CONTEXT, propagation } from '@opentelemetry/api'; import { performance } from 'perf_hooks'; describe('OpenTracing Shim', () => { diff --git a/packages/opentelemetry-tracing/package.json b/packages/opentelemetry-tracing/package.json index 52b5563782..15dc5a2018 100644 --- a/packages/opentelemetry-tracing/package.json +++ b/packages/opentelemetry-tracing/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/tracing", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Tracing", "main": "build/src/index.js", "browser": { @@ -74,9 +74,10 @@ "webpack": "4.44.1" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/resources": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0" } } diff --git a/packages/opentelemetry-tracing/src/Span.ts b/packages/opentelemetry-tracing/src/Span.ts index 6272729d2d..b8117b0195 100644 --- a/packages/opentelemetry-tracing/src/Span.ts +++ b/packages/opentelemetry-tracing/src/Span.ts @@ -23,6 +23,10 @@ import { timeInputToHrTime, } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; +import { + ExceptionAttribute, + ExceptionEventName, +} from '@opentelemetry/semantic-conventions'; import { ReadableSpan } from './export/ReadableSpan'; import { Tracer } from './Tracer'; import { SpanProcessor } from './SpanProcessor'; @@ -178,6 +182,35 @@ export class Span implements api.Span, ReadableSpan { return true; } + recordException(exception: api.Exception, time: api.TimeInput = hrTime()) { + const attributes: api.Attributes = {}; + if (typeof exception === 'string') { + attributes[ExceptionAttribute.MESSAGE] = exception; + } else if (exception) { + if (exception.code) { + attributes[ExceptionAttribute.TYPE] = exception.code; + } else if (exception.name) { + attributes[ExceptionAttribute.TYPE] = exception.name; + } + if (exception.message) { + attributes[ExceptionAttribute.MESSAGE] = exception.message; + } + if (exception.stack) { + attributes[ExceptionAttribute.STACKTRACE] = exception.stack; + } + } + + // these are minimum requirements from spec + if ( + attributes[ExceptionAttribute.TYPE] || + attributes[ExceptionAttribute.MESSAGE] + ) { + this.addEvent(ExceptionEventName, attributes as api.Attributes, time); + } else { + this._logger.warn(`Failed to record an exception ${exception}`); + } + } + get duration(): api.HrTime { return this._duration; } diff --git a/packages/opentelemetry-tracing/src/Tracer.ts b/packages/opentelemetry-tracing/src/Tracer.ts index 6f8fcc6f18..4a44d73599 100644 --- a/packages/opentelemetry-tracing/src/Tracer.ts +++ b/packages/opentelemetry-tracing/src/Tracer.ts @@ -20,11 +20,11 @@ import { getActiveSpan, getParentSpanContext, InstrumentationLibrary, - isValid, NoRecordingSpan, IdGenerator, RandomIdGenerator, setActiveSpan, + isInstrumentationSuppressed, } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; import { BasicTracerProvider } from './BasicTracerProvider'; @@ -69,11 +69,16 @@ export class Tracer implements api.Tracer { options: api.SpanOptions = {}, context = api.context.active() ): api.Span { + if (isInstrumentationSuppressed(context)) { + this.logger.debug('Instrumentation suppressed, returning Noop Span'); + return api.NOOP_SPAN; + } + const parentContext = getParent(options, context); const spanId = this._idGenerator.generateSpanId(); let traceId; let traceState; - if (!parentContext || !isValid(parentContext)) { + if (!parentContext || !api.trace.isSpanContextValid(parentContext)) { // New root span. traceId = this._idGenerator.generateTraceId(); } else { @@ -81,6 +86,7 @@ export class Tracer implements api.Tracer { traceId = parentContext.traceId; traceState = parentContext.traceState; } + const spanKind = options.kind ?? api.SpanKind.INTERNAL; const links = options.links ?? []; const attributes = options.attributes ?? {}; diff --git a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts index b6e6c4d33e..9768c2fdf6 100644 --- a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { ExportResult, unrefTimer } from '@opentelemetry/core'; +import { context } from '@opentelemetry/api'; +import { ExportResult, unrefTimer, suppressInstrumentation } from '@opentelemetry/core'; import { SpanProcessor } from '../SpanProcessor'; import { BufferConfig } from '../types'; import { ReadableSpan } from './ReadableSpan'; @@ -99,13 +100,16 @@ export class BatchSpanProcessor implements SpanProcessor { return Promise.resolve(); } return new Promise((resolve, reject) => { - this._exporter.export(this._finishedSpans, result => { - this._finishedSpans = []; - if (result === ExportResult.SUCCESS) { - resolve(); - } else { - reject(result); - } + // prevent downstream exporter calls from generating spans + context.with(suppressInstrumentation(context.active()), () => { + this._exporter.export(this._finishedSpans, result => { + this._finishedSpans = []; + if (result === ExportResult.SUCCESS) { + resolve(); + } else { + reject(result); + } + }); }); }); } diff --git a/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts b/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts index a814748a13..501ae11f70 100644 --- a/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts +++ b/packages/opentelemetry-tracing/src/export/InMemorySpanExporter.ts @@ -20,12 +20,16 @@ import { ExportResult } from '@opentelemetry/core'; /** * This class can be used for testing purposes. It stores the exported spans - * in a list in memory that can be retrieve using the `getFinishedSpans()` + * in a list in memory that can be retrieved using the `getFinishedSpans()` * method. */ export class InMemorySpanExporter implements SpanExporter { private _finishedSpans: ReadableSpan[] = []; - private _stopped = false; + /** + * Indicates if the exporter has been "shutdown." + * When false, exported spans will not be stored in-memory. + */ + protected _stopped = false; export( spans: ReadableSpan[], @@ -33,7 +37,8 @@ export class InMemorySpanExporter implements SpanExporter { ): void { if (this._stopped) return resultCallback(ExportResult.FAILED_NOT_RETRYABLE); this._finishedSpans.push(...spans); - return resultCallback(ExportResult.SUCCESS); + + setTimeout(() => resultCallback(ExportResult.SUCCESS), 0); } shutdown(): Promise { diff --git a/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts index bf6b61fe83..286d965d37 100644 --- a/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts @@ -17,6 +17,8 @@ import { SpanProcessor } from '../SpanProcessor'; import { SpanExporter } from './SpanExporter'; import { ReadableSpan } from './ReadableSpan'; +import { context } from '@opentelemetry/api'; +import { suppressInstrumentation } from '@opentelemetry/core'; /** * An implementation of the {@link SpanProcessor} that converts the {@link Span} @@ -42,7 +44,11 @@ export class SimpleSpanProcessor implements SpanProcessor { if (this._isShutdown) { return; } - this._exporter.export([span], () => {}); + + // prevent downstream exporter calls from generating spans + context.with(suppressInstrumentation(context.active()), () => { + this._exporter.export([span], () => {}); + }); } shutdown(): Promise { diff --git a/packages/opentelemetry-tracing/src/types.ts b/packages/opentelemetry-tracing/src/types.ts index 4e5059cea3..c90502006e 100644 --- a/packages/opentelemetry-tracing/src/types.ts +++ b/packages/opentelemetry-tracing/src/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HttpTextPropagator, Logger, Sampler } from '@opentelemetry/api'; +import { TextMapPropagator, Logger, Sampler } from '@opentelemetry/api'; import { LogLevel, IdGenerator } from '@opentelemetry/core'; import { ContextManager } from '@opentelemetry/context-base'; @@ -60,7 +60,7 @@ export interface TracerConfig { */ export interface SDKRegistrationConfig { /** Propagator to register as the global propagator */ - propagator?: HttpTextPropagator | null; + propagator?: TextMapPropagator | null; /** Context manager to register as the global context manager */ contextManager?: ContextManager | null; diff --git a/packages/opentelemetry-tracing/src/version.ts b/packages/opentelemetry-tracing/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-tracing/src/version.ts +++ b/packages/opentelemetry-tracing/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-tracing/test/Span.test.ts b/packages/opentelemetry-tracing/test/Span.test.ts index 6f1494518f..a86222e63b 100644 --- a/packages/opentelemetry-tracing/test/Span.test.ts +++ b/packages/opentelemetry-tracing/test/Span.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ExceptionAttribute } from '@opentelemetry/semantic-conventions'; import * as assert from 'assert'; import { SpanKind, @@ -21,6 +22,7 @@ import { TraceFlags, SpanContext, LinkContext, + Exception, } from '@opentelemetry/api'; import { BasicTracerProvider, Span } from '../src'; import { @@ -357,4 +359,91 @@ describe('Span', () => { span.end(); assert.strictEqual(span.ended, true); }); + + describe('recordException', () => { + const invalidExceptions: any[] = [ + 1, + null, + undefined, + { foo: 'bar' }, + { stack: 'bar' }, + ['a', 'b', 'c'], + ]; + + invalidExceptions.forEach(key => { + describe(`when exception is (${JSON.stringify(key)})`, () => { + it('should NOT record an exception', () => { + const span = new Span(tracer, name, spanContext, SpanKind.CLIENT); + assert.strictEqual(span.events.length, 0); + span.recordException(key); + assert.strictEqual(span.events.length, 0); + }); + }); + }); + + describe('when exception type is "string"', () => { + let error: Exception; + beforeEach(() => { + error = 'boom'; + }); + it('should record an exception', () => { + const span = new Span(tracer, name, spanContext, SpanKind.CLIENT); + assert.strictEqual(span.events.length, 0); + span.recordException(error); + + const event = span.events[0]; + assert.strictEqual(event.name, 'exception'); + assert.deepStrictEqual(event.attributes, { + 'exception.message': 'boom', + }); + assert.ok(event.time[0] > 0); + }); + }); + + const errorsObj = [ + { + description: 'code', + obj: { code: 'Error', message: 'boom', stack: 'bar' }, + }, + { + description: 'name', + obj: { name: 'Error', message: 'boom', stack: 'bar' }, + }, + ]; + errorsObj.forEach(errorObj => { + describe(`when exception type is an object with ${errorObj.description}`, () => { + const error: Exception = errorObj.obj; + it('should record an exception', () => { + const span = new Span(tracer, name, spanContext, SpanKind.CLIENT); + assert.strictEqual(span.events.length, 0); + span.recordException(error); + + const event = span.events[0]; + assert.ok(event.time[0] > 0); + assert.strictEqual(event.name, 'exception'); + + assert.ok(event.attributes); + + const type = event.attributes[ExceptionAttribute.TYPE]; + const message = event.attributes[ExceptionAttribute.MESSAGE]; + const stacktrace = String( + event.attributes[ExceptionAttribute.STACKTRACE] + ); + assert.strictEqual(type, 'Error'); + assert.strictEqual(message, 'boom'); + assert.strictEqual(stacktrace, 'bar'); + }); + }); + }); + + describe('when time is provided', () => { + it('should record an exception with provided time', () => { + const span = new Span(tracer, name, spanContext, SpanKind.CLIENT); + assert.strictEqual(span.events.length, 0); + span.recordException('boom', [0, 123]); + const event = span.events[0]; + assert.deepStrictEqual(event.time, [0, 123]); + }); + }); + }); }); diff --git a/packages/opentelemetry-tracing/test/Tracer.test.ts b/packages/opentelemetry-tracing/test/Tracer.test.ts index 8bf0481c85..9851aee624 100644 --- a/packages/opentelemetry-tracing/test/Tracer.test.ts +++ b/packages/opentelemetry-tracing/test/Tracer.test.ts @@ -19,6 +19,8 @@ import { NoopSpan, Sampler, SamplingDecision, + Context, + NOOP_SPAN, TraceFlags, } from '@opentelemetry/api'; import { BasicTracerProvider, Tracer, Span } from '../src'; @@ -27,6 +29,7 @@ import { NoopLogger, AlwaysOnSampler, AlwaysOffSampler, + suppressInstrumentation, } from '@opentelemetry/core'; describe('Tracer', () => { @@ -115,6 +118,25 @@ describe('Tracer', () => { assert.strictEqual(lib.version, '0.0.1'); }); + describe('when suppressInstrumentation true', () => { + const context = suppressInstrumentation(Context.ROOT_CONTEXT); + + it('should return cached no-op span ', done => { + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + { sampler: new TestSampler() }, + tracerProvider + ); + + const span = tracer.startSpan('span3', undefined, context); + + assert.equal(span, NOOP_SPAN); + span.end(); + + done(); + }); + }); + if (typeof process !== 'undefined' && process.release.name === 'node') { it('should sample a trace when OTEL_SAMPLING_PROBABILITY is invalid', () => { process.env.OTEL_SAMPLING_PROBABILITY = 'invalid value'; diff --git a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts index 41552ac649..0fa40be507 100644 --- a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts @@ -23,6 +23,9 @@ import { InMemorySpanExporter, Span, } from '../../src'; +import { context } from '@opentelemetry/api'; +import { TestTracingSpanExporter } from './TestTracingSpanExporter'; +import { TestStackContextManager } from './TestStackContextManager'; function createSampledSpan(spanName: string): Span { const tracer = new BasicTracerProvider({ @@ -226,5 +229,32 @@ describe('BatchSpanProcessor', () => { }); }); }); + + describe('flushing spans with exporter triggering instrumentation', () => { + beforeEach(() => { + const contextManager = new TestStackContextManager().enable(); + context.setGlobalContextManager(contextManager); + }); + + afterEach(() => { + context.disable(); + }); + + it('should prevent instrumentation prior to export', done => { + const testTracingExporter = new TestTracingSpanExporter(); + const processor = new BatchSpanProcessor(testTracingExporter); + + const span = createSampledSpan('test'); + processor.onStart(span); + processor.onEnd(span); + + processor.forceFlush(() => { + const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); + assert.equal(exporterCreatedSpans.length, 0); + + done(); + }); + }); + }); }); }); diff --git a/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts index 05c9fcc3b5..c62799428a 100644 --- a/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts @@ -21,7 +21,9 @@ import { InMemorySpanExporter, SimpleSpanProcessor, } from '../../src'; -import { SpanContext, SpanKind, TraceFlags } from '@opentelemetry/api'; +import { SpanContext, SpanKind, TraceFlags, context } from '@opentelemetry/api'; +import { TestTracingSpanExporter } from './TestTracingSpanExporter'; +import { TestStackContextManager } from './TestStackContextManager'; describe('SimpleSpanProcessor', () => { const provider = new BasicTracerProvider(); @@ -80,16 +82,20 @@ describe('SimpleSpanProcessor', () => { await processor.shutdown(); assert.strictEqual(exporter.getFinishedSpans().length, 0); }); + }); - describe('force flush', () => { - it('should call an async callback when flushing is complete', done => { + describe('force flush', () => { + describe('when flushing complete', () => { + it('should call an async callback', done => { const processor = new SimpleSpanProcessor(exporter); processor.forceFlush().then(() => { done(); }); }); + }); - it('should call an async callback when shutdown is complete', done => { + describe('when shutdown is complete', () => { + it('should call an async callback', done => { const processor = new SimpleSpanProcessor(exporter); processor.shutdown().then(() => { done(); @@ -97,4 +103,37 @@ describe('SimpleSpanProcessor', () => { }); }); }); + + describe('onEnd', () => { + beforeEach(() => { + const contextManager = new TestStackContextManager().enable(); + context.setGlobalContextManager(contextManager); + }); + + afterEach(() => { + context.disable(); + }); + + it('should prevent instrumentation prior to export', () => { + const testTracingExporter = new TestTracingSpanExporter(); + const processor = new SimpleSpanProcessor(testTracingExporter); + + const spanContext: SpanContext = { + traceId: 'a3cda95b652f4a1592b449d5929fda1b', + spanId: '5e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const span = new Span( + provider.getTracer('default'), + 'span-name', + spanContext, + SpanKind.CLIENT + ); + + processor.onEnd(span); + + const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); + assert.equal(exporterCreatedSpans.length, 0); + }); + }); }); diff --git a/packages/opentelemetry-tracing/test/export/TestStackContextManager.ts b/packages/opentelemetry-tracing/test/export/TestStackContextManager.ts new file mode 100644 index 0000000000..3062ea1069 --- /dev/null +++ b/packages/opentelemetry-tracing/test/export/TestStackContextManager.ts @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ContextManager, Context } from '@opentelemetry/context-base'; + +/** + * A test-only ContextManager that uses an in-memory stack to keep track of + * the active context. + * + * This is not intended for advanced or asynchronous use cases. + */ +export class TestStackContextManager implements ContextManager { + private _contextStack: Context[] = []; + + active(): Context { + return ( + this._contextStack[this._contextStack.length - 1] ?? Context.ROOT_CONTEXT + ); + } + + with ReturnType>( + context: Context, + fn: T + ): ReturnType { + this._contextStack.push(context); + try { + return fn(); + } finally { + this._contextStack.pop(); + } + } + + bind(target: T, context?: Context): T { + throw new Error('Method not implemented.'); + } + + enable(): this { + return this; + } + + disable(): this { + return this; + } +} diff --git a/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts b/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts new file mode 100644 index 0000000000..0aba00b054 --- /dev/null +++ b/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BasicTracerProvider, + InMemorySpanExporter, + ReadableSpan, + Tracer, + SpanProcessor, +} from '../../src'; +import { ExportResult, NoopLogger, AlwaysOnSampler } from '@opentelemetry/core'; + +/** + * A test-only span exporter that naively simulates triggering instrumentation + * (creating new spans) during export. + */ +export class TestTracingSpanExporter extends InMemorySpanExporter { + private _exporterCreatedSpans: ReadableSpan[] = []; + private _tracer: Tracer; + + constructor() { + super(); + + const tracerProvider = new BasicTracerProvider({ + logger: new NoopLogger(), + }); + + const spanProcessor: SpanProcessor = { + forceFlush: () => {}, + onStart: () => {}, + shutdown: () => {}, + onEnd: span => { + this._exporterCreatedSpans.push(span); + }, + }; + + tracerProvider.addSpanProcessor(spanProcessor); + + this._tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + { sampler: new AlwaysOnSampler() }, + tracerProvider + ); + } + + export( + spans: ReadableSpan[], + resultCallback: (result: ExportResult) => void + ): void { + if (!this._stopped) { + // Simulates an instrumented exporter by creating a span on the tracer. + const createdSpan = this._tracer.startSpan('exporter-created-span'); + createdSpan.end(); + } + + super.export(spans, resultCallback); + } + + shutdown(): void { + super.shutdown(); + this._exporterCreatedSpans = []; + } + + reset() { + super.reset(); + this._exporterCreatedSpans = []; + } + + getExporterCreatedSpans(): ReadableSpan[] { + return this._exporterCreatedSpans; + } +} diff --git a/packages/opentelemetry-web/package.json b/packages/opentelemetry-web/package.json index a83c421cd5..0149ef8c09 100644 --- a/packages/opentelemetry-web/package.json +++ b/packages/opentelemetry-web/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/web", - "version": "0.10.2", + "version": "0.11.0", "description": "OpenTelemetry Web Tracer", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -44,8 +44,8 @@ }, "devDependencies": { "@babel/core": "7.11.1", - "@opentelemetry/context-zone": "^0.10.2", - "@opentelemetry/resources": "^0.10.2", + "@opentelemetry/context-zone": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", "@types/jquery": "3.5.1", "@types/mocha": "8.0.2", "@types/node": "14.0.27", @@ -75,10 +75,10 @@ "webpack-merge": "5.1.1" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/context-base": "^0.10.2", - "@opentelemetry/core": "^0.10.2", - "@opentelemetry/semantic-conventions": "^0.10.2", - "@opentelemetry/tracing": "^0.10.2" + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0" } } diff --git a/packages/opentelemetry-web/src/utils.ts b/packages/opentelemetry-web/src/utils.ts index a1d539987a..385cb1d05f 100644 --- a/packages/opentelemetry-web/src/utils.ts +++ b/packages/opentelemetry-web/src/utils.ts @@ -28,6 +28,9 @@ import { } from '@opentelemetry/core'; import { HttpAttribute } from '@opentelemetry/semantic-conventions'; +// Used to normalize relative URLs +const urlNormalizingA = document.createElement('a'); + /** * Helper function to be able to use enum as typed key in type and in interface when using forEach * @param obj @@ -127,6 +130,10 @@ export function getResource( >(), initiatorType?: string ): PerformanceResourceTimingInfo { + // de-relativize the URL before usage (does no harm to absolute URLs) + urlNormalizingA.href = spanUrl; + spanUrl = urlNormalizingA.href; + const filteredResources = filterResourcesForSpan( spanUrl, startTimeHR, diff --git a/packages/opentelemetry-web/src/version.ts b/packages/opentelemetry-web/src/version.ts index ea45ee2fc4..714520138d 100644 --- a/packages/opentelemetry-web/src/version.ts +++ b/packages/opentelemetry-web/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.10.2'; +export const VERSION = '0.11.0'; diff --git a/packages/opentelemetry-web/test/registration.test.ts b/packages/opentelemetry-web/test/registration.test.ts index 2639072bf4..123b9667d1 100644 --- a/packages/opentelemetry-web/test/registration.test.ts +++ b/packages/opentelemetry-web/test/registration.test.ts @@ -16,9 +16,10 @@ import { context, - NoopHttpTextPropagator, + NoopTextMapPropagator, propagation, trace, + ProxyTracerProvider, } from '@opentelemetry/api'; import { NoopContextManager } from '@opentelemetry/context-base'; import { CompositePropagator } from '@opentelemetry/core'; @@ -40,14 +41,16 @@ describe('API registration', () => { assert.ok( propagation['_getGlobalPropagator']() instanceof CompositePropagator ); - assert.ok(trace.getTracerProvider() === tracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); it('should register configured implementations', () => { const tracerProvider = new WebTracerProvider(); const contextManager = new NoopContextManager(); - const propagator = new NoopHttpTextPropagator(); + const propagator = new NoopTextMapPropagator(); tracerProvider.register({ contextManager, @@ -57,7 +60,9 @@ describe('API registration', () => { assert.ok(context['_getContextManager']() === contextManager); assert.ok(propagation['_getGlobalPropagator']() === propagator); - assert.ok(trace.getTracerProvider() === tracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); it('should skip null context manager', () => { @@ -71,7 +76,9 @@ describe('API registration', () => { assert.ok( propagation['_getGlobalPropagator']() instanceof CompositePropagator ); - assert.ok(trace.getTracerProvider() === tracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); it('should skip null propagator', () => { @@ -81,10 +88,12 @@ describe('API registration', () => { }); assert.ok( - propagation['_getGlobalPropagator']() instanceof NoopHttpTextPropagator + propagation['_getGlobalPropagator']() instanceof NoopTextMapPropagator ); assert.ok(context['_getContextManager']() instanceof StackContextManager); - assert.ok(trace.getTracerProvider() === tracerProvider); + const apiTracerProvider = trace.getTracerProvider(); + assert.ok(apiTracerProvider instanceof ProxyTracerProvider); + assert.ok(apiTracerProvider.getDelegate() === tracerProvider); }); }); diff --git a/renovate.json b/renovate.json index d2b949af99..d95f24142e 100644 --- a/renovate.json +++ b/renovate.json @@ -12,8 +12,7 @@ "ignoreDeps": [ "gcp-metadata", "got", - "mocha", - "prom-client" + "mocha" ], "assignees": [ "@dyladan", From 1e73b408971cfadab565ecabc9ea6c64fe87b8b7 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 3 Sep 2020 03:44:41 +0200 Subject: [PATCH 6/7] chore: fixes after merge --- .../src/PrometheusExporter.ts | 53 ++++++++++--------- .../test/PrometheusExporter.test.ts | 12 ++--- .../test/unmocked.test.ts | 8 ++- .../src/export/BatchSpanProcessor.ts | 6 ++- .../test/export/BatchSpanProcessor.test.ts | 2 +- .../test/export/TestTracingSpanExporter.ts | 15 ++++-- 6 files changed, 57 insertions(+), 39 deletions(-) diff --git a/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts b/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts index 3d6035cc4f..9a987a0a60 100644 --- a/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts +++ b/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts @@ -60,7 +60,7 @@ export class PrometheusExporter implements MetricExporter { ).replace(/^([^/])/, '/$1'); if (config.startServer || PrometheusExporter.DEFAULT_OPTIONS.startServer) { - this.startServer(callback); + this.startServer().then(callback); } else if (callback) { callback(); } @@ -98,48 +98,53 @@ export class PrometheusExporter implements MetricExporter { /** * Shuts down the export server and clears the registry - * - * @param cb called when server is stopped */ - shutdown(cb?: () => void) { - this.stopServer(cb); + shutdown(): Promise { + return this.stopServer(); } /** * Stops the Prometheus export server * @param callback A callback that will be executed once the server is stopped */ - stopServer(callback?: () => void) { + stopServer(): Promise { if (!this._server) { this._logger.debug( 'Prometheus stopServer() was called but server was never started.' ); - if (callback) { - callback(); - } + return Promise.resolve(); } else { - this._server.close(() => { - this._logger.debug('Prometheus exporter was stopped'); - if (callback) { - callback(); - } + return new Promise(resolve => { + this._server.close(err => { + if (!err) { + this._logger.debug('Prometheus exporter was stopped'); + } else { + if ( + ((err as unknown) as { code: string }).code !== + 'ERR_SERVER_NOT_RUNNING' + ) { + this._logger.error( + `Error during stopping the Prometheus Exporter "${err.message}"` + ); + } + } + resolve(); + }); }); } } /** * Starts the Prometheus export server - * - * @param callback called once the server is ready */ - startServer(callback?: () => void) { - this._server.listen(this._port, () => { - this._logger.debug( - `Prometheus exporter started on port ${this._port} at endpoint ${this._endpoint}` - ); - if (callback) { - callback(); - } + startServer(): Promise { + return new Promise(resolve => { + this._server.listen(this._port, () => { + this._logger.debug( + `Prometheus exporter started on port ${this._port} at endpoint ${this._endpoint}` + ); + resolve(); + }); }); } diff --git a/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts b/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts index 309b47df11..4153ebf8c3 100644 --- a/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts +++ b/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts @@ -371,9 +371,9 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter a test description', '# TYPE counter counter', - `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, - `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, - `counter{counterKey1="labelValue3"} 30 ${mockedTimeMS}`, + `counter{counterKey1="labelValue1"} 10 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue2"} 20 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue3"} 30 ${mockedHrTimeMs}`, '', ]); @@ -403,9 +403,9 @@ describe('PrometheusExporter', () => { assert.deepStrictEqual(lines, [ '# HELP counter a test description', '# TYPE counter counter', - `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`, - `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`, - `counter{counterKey1="labelValue3"} 30 ${mockedTimeMS}`, + `counter{counterKey1="labelValue1"} 10 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue2"} 20 ${mockedHrTimeMs}`, + `counter{counterKey1="labelValue3"} 30 ${mockedHrTimeMs}`, '', ]); diff --git a/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts b/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts index 2423eda95a..3f3214e4b5 100644 --- a/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts +++ b/packages/opentelemetry-plugin-xml-http-request/test/unmocked.test.ts @@ -22,9 +22,13 @@ import { HttpAttribute } from '@opentelemetry/semantic-conventions'; class TestSpanProcessor implements SpanProcessor { spans: ReadableSpan[] = []; - forceFlush(callback: () => void): void {} + forceFlush(): Promise { + return Promise.resolve(); + } onStart(span: ReadableSpan): void {} - shutdown(callback: () => void): void {} + shutdown(): Promise { + return Promise.resolve(); + } onEnd(span: ReadableSpan): void { this.spans.push(span); diff --git a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts index 9768c2fdf6..948119966d 100644 --- a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts @@ -15,7 +15,11 @@ */ import { context } from '@opentelemetry/api'; -import { ExportResult, unrefTimer, suppressInstrumentation } from '@opentelemetry/core'; +import { + ExportResult, + unrefTimer, + suppressInstrumentation, +} from '@opentelemetry/core'; import { SpanProcessor } from '../SpanProcessor'; import { BufferConfig } from '../types'; import { ReadableSpan } from './ReadableSpan'; diff --git a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts index 0fa40be507..4b0f659093 100644 --- a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts @@ -248,7 +248,7 @@ describe('BatchSpanProcessor', () => { processor.onStart(span); processor.onEnd(span); - processor.forceFlush(() => { + processor.forceFlush().then(() => { const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); assert.equal(exporterCreatedSpans.length, 0); diff --git a/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts b/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts index 0aba00b054..a5804e8af5 100644 --- a/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts +++ b/packages/opentelemetry-tracing/test/export/TestTracingSpanExporter.ts @@ -39,9 +39,13 @@ export class TestTracingSpanExporter extends InMemorySpanExporter { }); const spanProcessor: SpanProcessor = { - forceFlush: () => {}, + forceFlush: () => { + return Promise.resolve(); + }, onStart: () => {}, - shutdown: () => {}, + shutdown: () => { + return Promise.resolve(); + }, onEnd: span => { this._exporterCreatedSpans.push(span); }, @@ -69,9 +73,10 @@ export class TestTracingSpanExporter extends InMemorySpanExporter { super.export(spans, resultCallback); } - shutdown(): void { - super.shutdown(); - this._exporterCreatedSpans = []; + shutdown(): Promise { + return super.shutdown().then(() => { + this._exporterCreatedSpans = []; + }); } reset() { From 3a0a72ac2efae8f7f2a2c96368fa8f2ce8e950d5 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 3 Sep 2020 19:58:29 +0200 Subject: [PATCH 7/7] chore: reviews --- packages/opentelemetry-metrics/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-metrics/package.json b/packages/opentelemetry-metrics/package.json index 2cb2d4fa16..93e44f92d0 100644 --- a/packages/opentelemetry-metrics/package.json +++ b/packages/opentelemetry-metrics/package.json @@ -1,4 +1,4 @@ - { +{ "name": "@opentelemetry/metrics", "version": "0.11.0", "description": "OpenTelemetry metrics SDK",