Skip to content

Commit aea89cf

Browse files
Naseemdyladan
Naseem
andauthored
feat: add startActiveSpan method to Tracer (#2221)
Co-authored-by: Daniel Dyla <[email protected]>
1 parent d551781 commit aea89cf

File tree

3 files changed

+169
-44
lines changed

3 files changed

+169
-44
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"lint:examples": "eslint ./examples/**/*.js",
3333
"lint:examples:fix": "eslint ./examples/**/*.js --fix",
3434
"lint:markdown": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md",
35-
"lint:markdown:fix": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md --fix"
35+
"lint:markdown:fix": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md --fix",
36+
"reset": "lerna clean -y && rm -rf node_modules && npm i && npm run compile && npm run lint:fix"
3637
},
3738
"repository": "open-telemetry/opentelemetry-js",
3839
"keywords": [

packages/opentelemetry-tracing/src/Tracer.ts

+88-40
Original file line numberDiff line numberDiff line change
@@ -54,46 +54,6 @@ export class Tracer implements api.Tracer {
5454
this.instrumentationLibrary = instrumentationLibrary;
5555
}
5656

57-
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
58-
name: string,
59-
arg2: F | api.SpanOptions,
60-
arg3?: F | api.Context,
61-
arg4?: F
62-
): ReturnType<F> | undefined {
63-
let fn: F | undefined,
64-
options: api.SpanOptions | undefined,
65-
activeContext: api.Context | undefined;
66-
67-
if (arguments.length === 2 && typeof arg2 === 'function') {
68-
fn = arg2;
69-
} else if (
70-
arguments.length === 3 &&
71-
typeof arg2 === 'object' &&
72-
typeof arg3 === 'function'
73-
) {
74-
options = arg2;
75-
fn = arg3;
76-
} else if (
77-
arguments.length === 4 &&
78-
typeof arg2 === 'object' &&
79-
typeof arg3 === 'object' &&
80-
typeof arg4 === 'function'
81-
) {
82-
options = arg2;
83-
activeContext = arg3;
84-
fn = arg4;
85-
}
86-
87-
const parentContext = activeContext ?? api.context.active();
88-
const span = this.startSpan(name, options, parentContext);
89-
const contextWithSpanSet = api.trace.setSpan(parentContext, span);
90-
91-
if (fn) {
92-
return api.context.with(contextWithSpanSet, fn, undefined, span);
93-
}
94-
return;
95-
}
96-
9757
/**
9858
* Starts a new Span or returns the default NoopSpan based on the sampling
9959
* decision.
@@ -163,6 +123,94 @@ export class Tracer implements api.Tracer {
163123
return span;
164124
}
165125

126+
/**
127+
* Starts a new {@link Span} and calls the given function passing it the
128+
* created span as first argument.
129+
* Additionally the new span gets set in context and this context is activated
130+
* for the duration of the function call.
131+
*
132+
* @param name The name of the span
133+
* @param [options] SpanOptions used for span creation
134+
* @param [context] Context to use to extract parent
135+
* @param fn function called in the context of the span and receives the newly created span as an argument
136+
* @returns return value of fn
137+
* @example
138+
* const something = tracer.startActiveSpan('op', span => {
139+
* try {
140+
* do some work
141+
* span.setStatus({code: SpanStatusCode.OK});
142+
* return something;
143+
* } catch (err) {
144+
* span.setStatus({
145+
* code: SpanStatusCode.ERROR,
146+
* message: err.message,
147+
* });
148+
* throw err;
149+
* } finally {
150+
* span.end();
151+
* }
152+
* });
153+
* @example
154+
* const span = tracer.startActiveSpan('op', span => {
155+
* try {
156+
* do some work
157+
* return span;
158+
* } catch (err) {
159+
* span.setStatus({
160+
* code: SpanStatusCode.ERROR,
161+
* message: err.message,
162+
* });
163+
* throw err;
164+
* }
165+
* });
166+
* do some more work
167+
* span.end();
168+
*/
169+
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
170+
name: string,
171+
fn: F
172+
): ReturnType<F>;
173+
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
174+
name: string,
175+
opts: api.SpanOptions,
176+
fn: F
177+
): ReturnType<F>;
178+
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
179+
name: string,
180+
opts: api.SpanOptions,
181+
ctx: api.Context,
182+
fn: F
183+
): ReturnType<F>;
184+
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
185+
name: string,
186+
arg2?: F | api.SpanOptions,
187+
arg3?: F | api.Context,
188+
arg4?: F
189+
): ReturnType<F> | undefined {
190+
let opts: api.SpanOptions | undefined;
191+
let ctx: api.Context | undefined;
192+
let fn: F;
193+
194+
if (arguments.length < 2) {
195+
return;
196+
} else if (arguments.length === 2) {
197+
fn = arg2 as F;
198+
} else if (arguments.length === 3) {
199+
opts = arg2 as api.SpanOptions | undefined;
200+
fn = arg3 as F;
201+
} else {
202+
opts = arg2 as api.SpanOptions | undefined;
203+
ctx = arg3 as api.Context | undefined;
204+
fn = arg4 as F;
205+
}
206+
207+
const parentContext = ctx ?? api.context.active();
208+
const span = this.startSpan(name, opts, parentContext);
209+
const contextWithSpanSet = api.trace.setSpan(parentContext, span);
210+
211+
return api.context.with(contextWithSpanSet, fn, undefined, span);
212+
}
213+
166214
/** Returns the active {@link SpanLimits}. */
167215
getSpanLimits(): SpanLimits {
168216
return this._spanLimits;

packages/opentelemetry-tracing/test/Tracer.test.ts

+79-3
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,28 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
import {
17+
context,
18+
createContextKey,
1819
INVALID_TRACEID,
1920
ROOT_CONTEXT,
2021
Sampler,
2122
SamplingDecision,
22-
TraceFlags,
2323
SpanContext,
2424
trace,
25+
TraceFlags
2526
} from '@opentelemetry/api';
27+
import { getSpan } from '@opentelemetry/api/build/src/trace/context-utils';
2628
import {
2729
AlwaysOffSampler,
2830
AlwaysOnSampler,
2931
InstrumentationLibrary,
30-
suppressTracing,
32+
suppressTracing
3133
} from '@opentelemetry/core';
3234
import * as assert from 'assert';
3335
import { BasicTracerProvider, Span, Tracer } from '../src';
36+
import { TestStackContextManager } from './export/TestStackContextManager';
37+
import * as sinon from 'sinon';
3438

3539
describe('Tracer', () => {
3640
const tracerProvider = new BasicTracerProvider();
@@ -49,7 +53,13 @@ describe('Tracer', () => {
4953
}
5054
}
5155

56+
beforeEach(() => {
57+
const contextManager = new TestStackContextManager().enable();
58+
context.setGlobalContextManager(contextManager);
59+
});
60+
5261
afterEach(() => {
62+
context.disable();
5363
delete envSource.OTEL_TRACES_SAMPLER;
5464
delete envSource.OTEL_TRACES_SAMPLER_ARG;
5565
});
@@ -220,4 +230,70 @@ describe('Tracer', () => {
220230
assert.strictEqual(context.traceFlags, TraceFlags.NONE);
221231
span.end();
222232
});
233+
234+
it('should start an active span with name and function args', () => {
235+
const tracer = new Tracer(
236+
{ name: 'default', version: '0.0.1' },
237+
{ sampler: new TestSampler() },
238+
tracerProvider
239+
);
240+
241+
const spy = sinon.spy(tracer, "startSpan");
242+
243+
assert.strictEqual(tracer.startActiveSpan('my-span', span => {
244+
try {
245+
assert(spy.calledWith('my-span'))
246+
assert.strictEqual(getSpan(context.active()), span)
247+
return 1
248+
} finally {
249+
span.end();
250+
}
251+
}), 1);
252+
});
253+
254+
it('should start an active span with name, options and function args', () => {
255+
256+
const tracer = new Tracer(
257+
{ name: 'default', version: '0.0.1' },
258+
{ sampler: new TestSampler() },
259+
tracerProvider
260+
);
261+
262+
const spy = sinon.spy(tracer, "startSpan");
263+
264+
assert.strictEqual(tracer.startActiveSpan('my-span', {attributes: {foo: 'bar'}}, span => {
265+
try {
266+
assert(spy.calledWith('my-span', {attributes: {foo: 'bar'}}))
267+
assert.strictEqual(getSpan(context.active()), span)
268+
return 1
269+
} finally {
270+
span.end();
271+
}
272+
}), 1);
273+
});
274+
275+
it('should start an active span with name, options, context and function args', () => {
276+
const tracer = new Tracer(
277+
{ name: 'default', version: '0.0.1' },
278+
{ sampler: new TestSampler() },
279+
tracerProvider
280+
);
281+
282+
const ctxKey = createContextKey('foo');
283+
284+
const ctx = context.active().setValue(ctxKey, 'bar')
285+
286+
const spy = sinon.spy(tracer, "startSpan");
287+
288+
assert.strictEqual(tracer.startActiveSpan('my-span', {attributes: {foo: 'bar'}}, ctx, span => {
289+
try {
290+
assert(spy.calledWith('my-span', {attributes: {foo: 'bar'}}, ctx))
291+
assert.strictEqual(getSpan(context.active()), span)
292+
assert.strictEqual(ctx.getValue(ctxKey), 'bar')
293+
return 1
294+
} finally {
295+
span.end();
296+
}
297+
}), 1);
298+
});
223299
});

0 commit comments

Comments
 (0)