diff --git a/CHANGELOG.md b/CHANGELOG.md index f3ebf020f9..f827e31585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :rocket: (Enhancement) +* feat(SpanExpoter): Add optional forceFlush to SpanExporter interface [#3753](https://github.com/open-telemetry/opentelemetry-js/pull/3753/) @sgracias1 @JacksonWeber + ### :bug: (Bug Fix) ### :books: (Refine Doc) diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts index 40f3381dbb..318e0f98c3 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts @@ -49,6 +49,10 @@ class DummySpanExporter implements tracing.SpanExporter { shutdown() { return Promise.resolve(); } + + forceFlush(): Promise { + return Promise.resolve(); + } } const getData = (url: string, method?: string) => { diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts b/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts index 6176dd005c..b304bb9c03 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts @@ -46,6 +46,10 @@ class DummySpanExporter implements tracing.SpanExporter { shutdown() { return Promise.resolve(); } + + forceFlush(): Promise { + return Promise.resolve(); + } } const XHR_TIMEOUT = 2000; diff --git a/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts b/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts index c78adb7e27..c960305783 100644 --- a/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts +++ b/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts @@ -117,15 +117,22 @@ export abstract class OTLPExporterBase< return this._shutdownOnce.call(); } + /** + * Exports any pending spans in the exporter + */ + forceFlush(): Promise { + return Promise.all(this._sendingPromises).then(() => { + /** ignore resolved values */ + }); + } + /** * Called by _shutdownOnce with BindOnceFuture */ private _shutdown(): Promise { diag.debug('shutdown started'); this.onShutdown(); - return Promise.all(this._sendingPromises).then(() => { - /** ignore resolved values */ - }); + return this.forceFlush(); } abstract onShutdown(): void; diff --git a/experimental/packages/otlp-exporter-base/test/node/util.test.ts b/experimental/packages/otlp-exporter-base/test/node/util.test.ts index 86c8df40ab..b279e57b9a 100644 --- a/experimental/packages/otlp-exporter-base/test/node/util.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/util.test.ts @@ -60,6 +60,13 @@ class Exporter extends OTLPExporterNodeBase { } } +describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + const exporter = new Exporter({}); + await exporter.forceFlush(); + }); +}); + describe('configureExporterTimeout', () => { const envSource = process.env; it('should use timeoutMillis parameter as export timeout value', () => { diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index 28d3ccb337..aafc884cd0 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -97,6 +97,13 @@ export class JaegerExporter implements SpanExporter { return this._shutdownOnce.call(); } + /** + * Exports any pending spans in exporter + */ + forceFlush(): Promise { + return this._flush(); + } + private _shutdown(): Promise { return Promise.race([ new Promise((_resolve, reject) => { diff --git a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts index bcfdbf051f..d667579637 100644 --- a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts +++ b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts @@ -178,6 +178,14 @@ describe('JaegerExporter', () => { }); }); + describe('force flush', () => { + let exporter: JaegerExporter; + it('forceFlush should flush spans and return', async () => { + exporter = new JaegerExporter(); + await exporter.forceFlush(); + }); + }); + describe('export', () => { let exporter: JaegerExporter; diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts index 2cb3e44436..ca52806d39 100644 --- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts +++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts @@ -102,6 +102,13 @@ export class ZipkinExporter implements SpanExporter { shutdown(): Promise { diag.debug('Zipkin exporter shutdown'); this._isShutdown = true; + return this.forceFlush(); + } + + /** + * Exports any pending spans in exporter + */ + forceFlush(): Promise { return new Promise((resolve, reject) => { Promise.all(this._sendingPromises).then(() => { resolve(); diff --git a/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts index eeab75348e..3001662498 100644 --- a/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts @@ -526,6 +526,13 @@ describe('Zipkin Exporter - node', () => { }); }); + describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + const exporter = new ZipkinExporter({}); + await exporter.forceFlush(); + }); + }); + describe('when env.OTEL_EXPORTER_ZIPKIN_ENDPOINT is set', () => { before(() => { process.env.OTEL_EXPORTER_ZIPKIN_ENDPOINT = 'http://localhost:9412'; diff --git a/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts b/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts index 77b0965598..596b3cefd3 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts @@ -46,6 +46,13 @@ export class ConsoleSpanExporter implements SpanExporter { */ shutdown(): Promise { this._sendSpans([]); + return this.forceFlush(); + } + + /** + * Exports any pending spans in exporter + */ + forceFlush(): Promise { return Promise.resolve(); } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts b/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts index c7c17d80c1..4a755ea8cd 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts @@ -48,6 +48,13 @@ export class InMemorySpanExporter implements SpanExporter { shutdown(): Promise { this._stopped = true; this._finishedSpans = []; + return this.forceFlush(); + } + + /** + * Exports any pending spans in the exporter + */ + forceFlush(): Promise { return Promise.resolve(); } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 8351b4b6ed..463e8376bd 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -46,6 +46,9 @@ export class SimpleSpanProcessor implements SpanProcessor { async forceFlush(): Promise { // await unresolved resources before resolving await Promise.all(Array.from(this._unresolvedExports)); + if (this._exporter.forceFlush) { + await this._exporter.forceFlush(); + } } onStart(_span: Span, _parentContext: Context): void {} diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts b/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts index b3b89d4aa6..c9ca9c0c89 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts @@ -36,4 +36,7 @@ export interface SpanExporter { /** Stops the exporter. */ shutdown(): Promise; + + /** Immediately export all spans */ + forceFlush?(): Promise; } diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts index 72642927a3..d005a2f803 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts @@ -99,4 +99,11 @@ describe('ConsoleSpanExporter', () => { }); }); }); + + describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + consoleExporter = new ConsoleSpanExporter(); + await consoleExporter.forceFlush(); + }); + }); }); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts index df9105d97b..585610514e 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts @@ -86,6 +86,13 @@ describe('InMemorySpanExporter', () => { assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); }); + describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + memoryExporter = new InMemorySpanExporter(); + await memoryExporter.forceFlush(); + }); + }); + it('should return the success result', () => { const exorter = new InMemorySpanExporter(); exorter.export([], (result: ExportResult) => { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts index 88490c527b..94e86eeed7 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts @@ -152,6 +152,14 @@ describe('SimpleSpanProcessor', () => { }); describe('force flush', () => { + it('should call forceflush on exporter', () => { + const spyflush = sinon.spy(exporter, 'forceFlush'); + const processor = new SimpleSpanProcessor(exporter); + processor.forceFlush().then(() => { + assert.ok(spyflush.calledOnce); + }); + }); + it('should await unresolved resources', async () => { const processor = new SimpleSpanProcessor(exporter); const providerWithAsyncResource = new BasicTracerProvider({