Skip to content

Commit a05e581

Browse files
feat: implement on ending in span processor (#6024)
1 parent 2c0bca4 commit a05e581

File tree

6 files changed

+101
-28
lines changed

6 files changed

+101
-28
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
1414

1515
### :rocket: Features
1616

17+
* feat(sdk-trace-base): implement on ending in span processor [#6024](https://github.com/open-telemetry/opentelemetry-js/pull/6024) @majanjua-amzn
18+
* note: this feature is experimental and subject to change
19+
1720
### :bug: Bug Fixes
1821

1922
### :books: Documentation

packages/opentelemetry-sdk-trace-base/src/MultiSpanProcessor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ export class MultiSpanProcessor implements SpanProcessor {
5353
}
5454
}
5555

56+
onEnding(span: Span): void {
57+
for (const spanProcessor of this._spanProcessors) {
58+
if (spanProcessor.onEnding) {
59+
spanProcessor.onEnding(span);
60+
}
61+
}
62+
}
63+
5664
onEnd(span: ReadableSpan): void {
5765
for (const spanProcessor of this._spanProcessors) {
5866
spanProcessor.onEnd(span);

packages/opentelemetry-sdk-trace-base/src/Span.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,6 @@ export class SpanImpl implements Span {
270270
);
271271
return;
272272
}
273-
this._ended = true;
274-
275273
this.endTime = this._getTime(endTime);
276274
this._duration = hrTimeDuration(this.startTime, this.endTime);
277275

@@ -290,7 +288,11 @@ export class SpanImpl implements Span {
290288
`Dropped ${this._droppedEventsCount} events because eventCountLimit reached`
291289
);
292290
}
291+
if (this._spanProcessor.onEnding) {
292+
this._spanProcessor.onEnding(this);
293+
}
293294

295+
this._ended = true;
294296
this._spanProcessor.onEnd(this);
295297
}
296298

packages/opentelemetry-sdk-trace-base/src/SpanProcessor.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ export interface SpanProcessor {
3535
*/
3636
onStart(span: Span, parentContext: Context): void;
3737

38+
/**
39+
* Called when a {@link Span} is ending, if the `span.isRecording()`
40+
* returns true.
41+
* @param span the Span that is ending.
42+
*
43+
* @experimental This method is experimental and may break in minor versions of this package
44+
*/
45+
onEnding?(span: Span): void;
46+
3847
/**
3948
* Called when a {@link ReadableSpan} is ended, if the `span.isRecording()`
4049
* returns true.

packages/opentelemetry-sdk-trace-base/test/common/MultiSpanProcessor.test.ts

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,39 @@ import {
3030
import { MultiSpanProcessor } from '../../src/MultiSpanProcessor';
3131

3232
class TestProcessor implements SpanProcessor {
33+
static events: string[] = [];
34+
name: string;
3335
spans: Span[] = [];
34-
onStart(span: Span): void {}
36+
37+
constructor(name: string) {
38+
this.name = name;
39+
}
40+
41+
onStart(span: Span): void {
42+
TestProcessor.events.push(this.name + ':start');
43+
}
44+
3545
onEnd(span: Span): void {
46+
TestProcessor.events.push(this.name + ':end');
3647
this.spans.push(span);
3748
}
49+
3850
shutdown(): Promise<void> {
39-
this.spans = [];
51+
TestProcessor.events = [];
4052
return Promise.resolve();
4153
}
54+
4255
forceFlush(): Promise<void> {
4356
return Promise.resolve();
4457
}
4558
}
4659

60+
class ExtendedTestProcessor extends TestProcessor {
61+
onEnding(span: Span): void {
62+
TestProcessor.events.push(this.name + ':ending');
63+
}
64+
}
65+
4766
describe('MultiSpanProcessor', () => {
4867
let removeEvent: (() => void) | undefined;
4968
afterEach(() => {
@@ -54,63 +73,71 @@ describe('MultiSpanProcessor', () => {
5473
});
5574

5675
it('should handle one span processor', () => {
57-
const processor1 = new TestProcessor();
76+
const processor1 = new TestProcessor('sp1');
5877
const tracerProvider = new BasicTracerProvider({
5978
spanProcessors: [processor1],
6079
});
6180
const tracer = tracerProvider.getTracer('default');
6281
const span = tracer.startSpan('one');
63-
assert.strictEqual(processor1.spans.length, 0);
82+
assert.deepStrictEqual(TestProcessor.events, ['sp1:start']);
6483
span.end();
65-
assert.strictEqual(processor1.spans.length, 1);
84+
assert.deepStrictEqual(TestProcessor.events, ['sp1:start', 'sp1:end']);
6685
tracerProvider['_activeSpanProcessor'].shutdown();
6786
});
6887

69-
it('should handle two span processor', async () => {
70-
const processor1 = new TestProcessor();
71-
const processor2 = new TestProcessor();
88+
it('should handle one span processor with on ending', () => {
89+
TestProcessor.events = [];
90+
const processor1 = new ExtendedTestProcessor('sp1');
7291
const tracerProvider = new BasicTracerProvider({
73-
spanProcessors: [processor1, processor2],
92+
spanProcessors: [processor1],
7493
});
7594
const tracer = tracerProvider.getTracer('default');
7695
const span = tracer.startSpan('one');
77-
assert.strictEqual(processor1.spans.length, 0);
78-
assert.strictEqual(processor1.spans.length, processor2.spans.length);
79-
96+
assert.deepStrictEqual(TestProcessor.events, ['sp1:start']);
8097
span.end();
81-
assert.strictEqual(processor1.spans.length, 1);
82-
assert.strictEqual(processor1.spans.length, processor2.spans.length);
83-
84-
tracerProvider.shutdown().then(() => {
85-
assert.strictEqual(processor1.spans.length, 0);
86-
assert.strictEqual(processor1.spans.length, processor2.spans.length);
87-
});
98+
assert.deepStrictEqual(TestProcessor.events, [
99+
'sp1:start',
100+
'sp1:ending',
101+
'sp1:end',
102+
]);
103+
tracerProvider['_activeSpanProcessor'].shutdown();
88104
});
89105

90-
it('should export spans on manual shutdown from two span processor', () => {
91-
const processor1 = new TestProcessor();
92-
const processor2 = new TestProcessor();
106+
it('should handle two span processor', async () => {
107+
TestProcessor.events = [];
108+
const processor1 = new TestProcessor('p1');
109+
const processor2 = new ExtendedTestProcessor('p2');
93110
const tracerProvider = new BasicTracerProvider({
94111
spanProcessors: [processor1, processor2],
95112
});
96113
const tracer = tracerProvider.getTracer('default');
97114
const span = tracer.startSpan('one');
98115
assert.strictEqual(processor1.spans.length, 0);
99-
assert.strictEqual(processor1.spans.length, processor2.spans.length);
116+
assert.strictEqual(processor2.spans.length, processor2.spans.length);
117+
assert.deepStrictEqual(TestProcessor.events, ['p1:start', 'p2:start']);
100118

101119
span.end();
102120
assert.strictEqual(processor1.spans.length, 1);
103121
assert.strictEqual(processor1.spans.length, processor2.spans.length);
122+
assert.deepStrictEqual(TestProcessor.events, [
123+
'p1:start',
124+
'p2:start',
125+
'p2:ending',
126+
'p1:end',
127+
'p2:end',
128+
]);
104129

105130
tracerProvider.shutdown().then(() => {
106131
assert.strictEqual(processor1.spans.length, 0);
107132
assert.strictEqual(processor1.spans.length, processor2.spans.length);
133+
assert.deepStrictEqual(TestProcessor.events.length, 0);
108134
});
109135
});
110136

111137
it('should export spans on manual shutdown from two span processor', () => {
112-
const processor1 = new TestProcessor();
113-
const processor2 = new TestProcessor();
138+
TestProcessor.events = [];
139+
const processor1 = new TestProcessor('p1');
140+
const processor2 = new ExtendedTestProcessor('p2');
114141
const tracerProvider = new BasicTracerProvider({
115142
spanProcessors: [processor1, processor2],
116143
});
@@ -172,7 +199,8 @@ describe('MultiSpanProcessor', () => {
172199

173200
it('should call globalErrorHandler in forceFlush', async () => {
174201
const expectedError = new Error('whoops');
175-
const testProcessor = new TestProcessor();
202+
TestProcessor.events = [];
203+
const testProcessor = new TestProcessor('p1');
176204
const forceFlush = Sinon.stub(testProcessor, 'forceFlush');
177205
forceFlush.rejects(expectedError);
178206

packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,29 @@ describe('Span', () => {
13461346
const s = provider.getTracer('default').startSpan('test') as Span;
13471347
assert.ok(s.attributes.attr);
13481348
});
1349+
1350+
it('should call onEnding with a writeable span', () => {
1351+
const processor: SpanProcessor = {
1352+
onStart: span => {
1353+
span.setAttribute('attr', true);
1354+
},
1355+
forceFlush: () => Promise.resolve(),
1356+
onEnding: span => {
1357+
span.setAttribute('attr2', true);
1358+
},
1359+
onEnd() {},
1360+
shutdown: () => Promise.resolve(),
1361+
};
1362+
1363+
const provider = new BasicTracerProvider({
1364+
spanProcessors: [processor],
1365+
});
1366+
1367+
const s = provider.getTracer('default').startSpan('test') as Span;
1368+
s.end();
1369+
assert.ok(s.attributes.attr);
1370+
assert.ok(s.attributes.attr2);
1371+
});
13491372
});
13501373

13511374
describe('recordException', () => {

0 commit comments

Comments
 (0)