Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 28fabc4

Browse files
NaseemFlarnadyladan
authored
feat: add tracer.startActiveSpan() (#54)
Co-authored-by: Gerhard Stöbich <deb2001-github@yahoo.de> Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
1 parent 8435e0a commit 28fabc4

File tree

5 files changed

+177
-10
lines changed

5 files changed

+177
-10
lines changed

src/trace/NoopTracer.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getSpanContext } from '../trace/context-utils';
17+
import { context } from '../';
1818
import { Context } from '../context/types';
19+
import { getSpanContext, setSpan } from '../trace/context-utils';
1920
import { NonRecordingSpan } from './NonRecordingSpan';
2021
import { Span } from './span';
2122
import { isSpanContextValid } from './spancontext-utils';
@@ -45,6 +46,45 @@ export class NoopTracer implements Tracer {
4546
return new NonRecordingSpan();
4647
}
4748
}
49+
50+
startActiveSpan<F extends (span: Span) => ReturnType<F>>(
51+
name: string,
52+
arg2: F | SpanOptions,
53+
arg3?: F | Context,
54+
arg4?: F
55+
): ReturnType<F> | undefined {
56+
let fn: F | undefined,
57+
options: SpanOptions | undefined,
58+
activeContext: Context | undefined;
59+
if (arguments.length === 2 && typeof arg2 === 'function') {
60+
fn = arg2;
61+
} else if (
62+
arguments.length === 3 &&
63+
typeof arg2 === 'object' &&
64+
typeof arg3 === 'function'
65+
) {
66+
options = arg2;
67+
fn = arg3;
68+
} else if (
69+
arguments.length === 4 &&
70+
typeof arg2 === 'object' &&
71+
typeof arg3 === 'object' &&
72+
typeof arg4 === 'function'
73+
) {
74+
options = arg2;
75+
activeContext = arg3;
76+
fn = arg4;
77+
}
78+
79+
const parentContext = activeContext ?? context.active();
80+
const span = this.startSpan(name, options, parentContext);
81+
const contextWithSpanSet = setSpan(parentContext, span);
82+
83+
if (fn) {
84+
return context.with(contextWithSpanSet, fn, undefined, span);
85+
}
86+
return;
87+
}
4888
}
4989

5090
function isSpanContext(spanContext: any): spanContext is SpanContext {

src/trace/ProxyTracer.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ export class ProxyTracer implements Tracer {
3838
return this._getTracer().startSpan(name, options, context);
3939
}
4040

41+
startActiveSpan<F extends (span: Span) => unknown>(
42+
_name: string,
43+
_options: F | SpanOptions,
44+
_context?: F | Context,
45+
_fn?: F
46+
): ReturnType<F> {
47+
const tracer = this._getTracer();
48+
return Reflect.apply(tracer.startActiveSpan, tracer, arguments);
49+
}
50+
4151
/**
4252
* Try to get a tracer from the proxy tracer provider.
4353
* If the proxy tracer provider has no delegate, return a noop tracer.

src/trace/tracer.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,63 @@ export interface Tracer {
3737
* span.end();
3838
*/
3939
startSpan(name: string, options?: SpanOptions, context?: Context): Span;
40+
41+
/**
42+
* Starts a new {@link Span} and calls the given function passing it the
43+
* created span as first argument.
44+
* Additionally the new span gets set in context and this context is activated
45+
* for the duration of the function call.
46+
*
47+
* @param name The name of the span
48+
* @param [options] SpanOptions used for span creation
49+
* @param [context] Context to use to extract parent
50+
* @param fn function called in the context of the span and receives the newly created span as an argument
51+
* @returns return value of fn
52+
* @example
53+
* const something = tracer.startActiveSpan('op', span => {
54+
* try {
55+
* do some work
56+
* span.setStatus({code: SpanStatusCode.OK});
57+
* return something;
58+
* } catch (err) {
59+
* span.setStatus({
60+
* code: SpanStatusCode.ERROR,
61+
* message: err.message,
62+
* });
63+
* throw err;
64+
* } finally {
65+
* span.end();
66+
* }
67+
* });
68+
* @example
69+
* const span = tracer.startActiveSpan('op', span => {
70+
* try {
71+
* do some work
72+
* return span;
73+
* } catch (err) {
74+
* span.setStatus({
75+
* code: SpanStatusCode.ERROR,
76+
* message: err.message,
77+
* });
78+
* throw err;
79+
* }
80+
* });
81+
* do some more work
82+
* span.end();
83+
*/
84+
startActiveSpan<F extends (span: Span) => unknown>(
85+
name: string,
86+
fn: F
87+
): ReturnType<F>;
88+
startActiveSpan<F extends (span: Span) => unknown>(
89+
name: string,
90+
options: SpanOptions,
91+
fn: F
92+
): ReturnType<F>;
93+
startActiveSpan<F extends (span: Span) => unknown>(
94+
name: string,
95+
options: SpanOptions,
96+
context: Context,
97+
fn: F
98+
): ReturnType<F>;
4099
}

test/noop-implementations/noop-tracer.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616

1717
import * as assert from 'assert';
1818
import {
19+
context,
1920
NoopTracer,
21+
Span,
2022
SpanContext,
2123
SpanKind,
22-
TraceFlags,
23-
context,
2424
trace,
25+
TraceFlags,
2526
} from '../../src';
2627
import { NonRecordingSpan } from '../../src/trace/NonRecordingSpan';
2728

@@ -56,4 +57,28 @@ describe('NoopTracer', () => {
5657
assert(span.spanContext().spanId === parent.spanId);
5758
assert(span.spanContext().traceFlags === parent.traceFlags);
5859
});
60+
61+
it('should accept 2 to 4 args and start an active span', () => {
62+
const tracer = new NoopTracer();
63+
const name = 'span-name';
64+
const fn = (span: Span) => {
65+
try {
66+
return 1;
67+
} finally {
68+
span.end();
69+
}
70+
};
71+
const opts = { attributes: { foo: 'bar' } };
72+
const ctx = context.active();
73+
74+
const a = tracer.startActiveSpan(name, fn);
75+
assert.strictEqual(a, 1);
76+
77+
const b = tracer.startActiveSpan(name, opts, fn);
78+
79+
assert.strictEqual(b, 1);
80+
81+
const c = tracer.startActiveSpan(name, opts, ctx, fn);
82+
assert.strictEqual(c, 1);
83+
});
5984
});

test/proxy-implementations/proxy-tracer.test.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@
1717
import * as assert from 'assert';
1818
import * as sinon from 'sinon';
1919
import {
20-
ProxyTracerProvider,
21-
SpanKind,
22-
TracerProvider,
23-
ProxyTracer,
24-
Tracer,
25-
Span,
20+
context,
2621
NoopTracer,
22+
ProxyTracer,
23+
ProxyTracerProvider,
2724
ROOT_CONTEXT,
25+
Span,
26+
SpanKind,
2827
SpanOptions,
28+
Tracer,
29+
TracerProvider,
2930
} from '../../src';
3031
import { NonRecordingSpan } from '../../src/trace/NonRecordingSpan';
31-
3232
describe('ProxyTracer', () => {
3333
let provider: ProxyTracerProvider;
3434
const sandbox = sinon.createSandbox();
@@ -96,6 +96,10 @@ describe('ProxyTracer', () => {
9696
startSpan() {
9797
return delegateSpan;
9898
},
99+
100+
startActiveSpan() {
101+
// stubbed
102+
},
99103
};
100104

101105
tracer = provider.getTracer('test');
@@ -114,6 +118,34 @@ describe('ProxyTracer', () => {
114118
assert.strictEqual(span, delegateSpan);
115119
});
116120

121+
it('should create active spans using the delegate tracer', () => {
122+
// sinon types are broken with overloads, hence the any
123+
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/36436
124+
const startActiveSpanStub = sinon.stub<Tracer, any>(
125+
delegateTracer,
126+
'startActiveSpan'
127+
);
128+
129+
const name = 'span-name';
130+
const fn = (span: Span) => {
131+
try {
132+
return 1;
133+
} finally {
134+
span.end();
135+
}
136+
};
137+
const opts = { attributes: { foo: 'bar' } };
138+
const ctx = context.active();
139+
140+
startActiveSpanStub.withArgs(name, fn).returns(1);
141+
startActiveSpanStub.withArgs(name, opts, fn).returns(2);
142+
startActiveSpanStub.withArgs(name, opts, ctx, fn).returns(3);
143+
144+
assert.strictEqual(tracer.startActiveSpan(name, fn), 1);
145+
assert.strictEqual(tracer.startActiveSpan(name, opts, fn), 2);
146+
assert.strictEqual(tracer.startActiveSpan(name, opts, ctx, fn), 3);
147+
});
148+
117149
it('should pass original arguments to DelegateTracer#startSpan', () => {
118150
const startSpanStub = sandbox.stub(delegateTracer, 'startSpan');
119151

@@ -130,6 +162,7 @@ describe('ProxyTracer', () => {
130162
assert.deepStrictEqual(Object.getOwnPropertyNames(NoopTracer.prototype), [
131163
'constructor',
132164
'startSpan',
165+
'startActiveSpan',
133166
]);
134167
sandbox.assert.calledOnceWithExactly(startSpanStub, name, options, ctx);
135168
});

0 commit comments

Comments
 (0)