Skip to content

Commit b523dab

Browse files
fix(tracing): use globalErrorHandler when flushing fails (#1622)
Co-authored-by: Bartlomiej Obecny <[email protected]>
1 parent 435d608 commit b523dab

File tree

4 files changed

+122
-8
lines changed

4 files changed

+122
-8
lines changed

packages/opentelemetry-tracing/src/export/BatchSpanProcessor.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
*/
1616

1717
import { context, suppressInstrumentation } from '@opentelemetry/api';
18-
import { ExportResult, unrefTimer } from '@opentelemetry/core';
18+
import {
19+
ExportResult,
20+
globalErrorHandler,
21+
unrefTimer,
22+
} from '@opentelemetry/core';
1923
import { Span } from '../Span';
2024
import { SpanProcessor } from '../SpanProcessor';
2125
import { BufferConfig } from '../types';
@@ -90,7 +94,9 @@ export class BatchSpanProcessor implements SpanProcessor {
9094
this._finishedSpans.push(span);
9195
this._maybeStartTimer();
9296
if (this._finishedSpans.length > this._bufferSize) {
93-
this._flush();
97+
this._flush().catch(e => {
98+
globalErrorHandler(e);
99+
});
94100
}
95101
}
96102

@@ -108,7 +114,11 @@ export class BatchSpanProcessor implements SpanProcessor {
108114
if (result === ExportResult.SUCCESS) {
109115
resolve();
110116
} else {
111-
reject(result);
117+
reject(
118+
new Error(
119+
`BatchSpanProcessor: span export failed (status ${result})`
120+
)
121+
);
112122
}
113123
});
114124
});
@@ -119,7 +129,9 @@ export class BatchSpanProcessor implements SpanProcessor {
119129
if (this._timer !== undefined) return;
120130

121131
this._timer = setTimeout(() => {
122-
this._flush().catch();
132+
this._flush().catch(e => {
133+
globalErrorHandler(e);
134+
});
123135
}, this._bufferTimeout);
124136
unrefTimer(this._timer);
125137
}

packages/opentelemetry-tracing/src/export/SimpleSpanProcessor.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
*/
1616

1717
import { context, suppressInstrumentation } from '@opentelemetry/api';
18+
import { ExportResult, globalErrorHandler } from '@opentelemetry/core';
1819
import { Span } from '../Span';
20+
import { SpanExporter } from './SpanExporter';
1921
import { SpanProcessor } from '../SpanProcessor';
2022
import { ReadableSpan } from './ReadableSpan';
21-
import { SpanExporter } from './SpanExporter';
2223

2324
/**
2425
* An implementation of the {@link SpanProcessor} that converts the {@link Span}
@@ -47,7 +48,15 @@ export class SimpleSpanProcessor implements SpanProcessor {
4748

4849
// prevent downstream exporter calls from generating spans
4950
context.with(suppressInstrumentation(context.active()), () => {
50-
this._exporter.export([span], () => {});
51+
this._exporter.export([span], result => {
52+
if (result !== ExportResult.SUCCESS) {
53+
globalErrorHandler(
54+
new Error(
55+
`SimpleSpanProcessor: span export failed (status ${result})`
56+
)
57+
);
58+
}
59+
});
5160
});
5261
}
5362

packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts

+44-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { AlwaysOnSampler, ExportResult } from '@opentelemetry/core';
17+
import {
18+
AlwaysOnSampler,
19+
ExportResult,
20+
loggingErrorHandler,
21+
setGlobalErrorHandler,
22+
} from '@opentelemetry/core';
1823
import * as assert from 'assert';
1924
import * as sinon from 'sinon';
2025
import {
@@ -199,7 +204,7 @@ describe('BatchSpanProcessor', () => {
199204
let processor: BatchSpanProcessor;
200205

201206
beforeEach(() => {
202-
processor = new BatchSpanProcessor(exporter);
207+
processor = new BatchSpanProcessor(exporter, defaultBufferConfig);
203208
const span = createSampledSpan('test');
204209
processor.onStart(span);
205210
processor.onEnd(span);
@@ -228,6 +233,43 @@ describe('BatchSpanProcessor', () => {
228233
done();
229234
});
230235
});
236+
237+
it('should call globalErrorHandler when exporting fails', async () => {
238+
const expectedError = new Error(
239+
'BatchSpanProcessor: span export failed (status 1)'
240+
);
241+
sinon.stub(exporter, 'export').callsFake((_, callback) => {
242+
setTimeout(() => {
243+
callback(ExportResult.FAILED_NOT_RETRYABLE);
244+
}, 0);
245+
});
246+
247+
const errorHandlerSpy = sinon.spy();
248+
249+
setGlobalErrorHandler(errorHandlerSpy);
250+
251+
// Cause a flush by emitting more spans then the default buffer size
252+
for (let i = 0; i < defaultBufferConfig.bufferSize; i++) {
253+
const span = createSampledSpan('test');
254+
processor.onStart(span);
255+
processor.onEnd(span);
256+
}
257+
258+
await new Promise(resolve => {
259+
setTimeout(() => {
260+
resolve();
261+
}, 0);
262+
});
263+
264+
assert.strictEqual(errorHandlerSpy.callCount, 1);
265+
266+
const [[error]] = errorHandlerSpy.args;
267+
268+
assert.deepStrictEqual(error, expectedError);
269+
270+
//reset global error handler
271+
setGlobalErrorHandler(loggingErrorHandler());
272+
});
231273
});
232274

233275
describe('flushing spans with exporter triggering instrumentation', () => {

packages/opentelemetry-tracing/test/export/SimpleSpanProcessor.test.ts

+51
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@
1515
*/
1616

1717
import * as assert from 'assert';
18+
import * as sinon from 'sinon';
1819
import {
1920
Span,
2021
BasicTracerProvider,
2122
InMemorySpanExporter,
2223
SimpleSpanProcessor,
2324
} from '../../src';
25+
import {
26+
ExportResult,
27+
setGlobalErrorHandler,
28+
loggingErrorHandler,
29+
} from '@opentelemetry/core';
2430
import { SpanContext, SpanKind, TraceFlags, context } from '@opentelemetry/api';
2531
import { TestTracingSpanExporter } from './TestTracingSpanExporter';
2632
import { TestStackContextManager } from './TestStackContextManager';
@@ -82,6 +88,51 @@ describe('SimpleSpanProcessor', () => {
8288
await processor.shutdown();
8389
assert.strictEqual(exporter.getFinishedSpans().length, 0);
8490
});
91+
92+
it('should call globalErrorHandler when exporting fails', async () => {
93+
const expectedError = new Error(
94+
'SimpleSpanProcessor: span export failed (status 1)'
95+
);
96+
const processor = new SimpleSpanProcessor(exporter);
97+
const spanContext: SpanContext = {
98+
traceId: 'a3cda95b652f4a1592b449d5929fda1b',
99+
spanId: '5e0c63257de34c92',
100+
traceFlags: TraceFlags.NONE,
101+
};
102+
const span = new Span(
103+
provider.getTracer('default'),
104+
'span-name',
105+
spanContext,
106+
SpanKind.CLIENT
107+
);
108+
109+
sinon.stub(exporter, 'export').callsFake((_, callback) => {
110+
setTimeout(() => {
111+
callback(ExportResult.FAILED_NOT_RETRYABLE);
112+
}, 0);
113+
});
114+
115+
const errorHandlerSpy = sinon.spy();
116+
117+
setGlobalErrorHandler(errorHandlerSpy);
118+
119+
processor.onEnd(span);
120+
121+
await new Promise(resolve => {
122+
setTimeout(() => {
123+
resolve();
124+
}, 0);
125+
});
126+
127+
assert.strictEqual(errorHandlerSpy.callCount, 1);
128+
129+
const [[error]] = errorHandlerSpy.args;
130+
131+
assert.deepStrictEqual(error, expectedError);
132+
133+
//reset global error handler
134+
setGlobalErrorHandler(loggingErrorHandler());
135+
});
85136
});
86137

87138
describe('force flush', () => {

0 commit comments

Comments
 (0)