diff --git a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts index 5813c302faf..b2e308b4427 100644 --- a/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts @@ -109,8 +109,10 @@ export class BatchSpanProcessor implements SpanProcessor { return new Promise((resolve, reject) => { // prevent downstream exporter calls from generating spans context.with(suppressInstrumentation(context.active()), () => { - this._exporter.export(this._finishedSpans, result => { - this._finishedSpans = []; + // Reset the finished spans buffer here because the next invocations of the _flush method + // could pass the same finished spans to the exporter if the buffer is cleared + // outside of the execution of this callback. + this._exporter.export(this._finishedSpans.splice(0), result => { if (result.code === ExportResultCode.SUCCESS) { resolve(); } else { diff --git a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts index 014f5edfa27..10ca9e0800c 100644 --- a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts @@ -181,6 +181,23 @@ describe('BatchSpanProcessor', () => { clock.restore(); }); + + it('should export each sampled span exactly once with buffer size reached multiple times', async () => { + const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); + const totalSpans = defaultBufferConfig.bufferSize * 2; + for (let i = 0; i <= totalSpans; i++) { + const span = createSampledSpan(`${name}_${i}`); + + processor.onEnd(span); + } + // Now we should start seeing the spans in exporter + const span = createSampledSpan(`${name}_last`); + processor.onEnd(span); + assert.strictEqual(exporter.getFinishedSpans().length, totalSpans + 2); + + await processor.shutdown(); + assert.strictEqual(exporter.getFinishedSpans().length, 0); + }); }); describe('force flush', () => {