Skip to content

Commit e7f07db

Browse files
committed
feat: expose withSpanAsync on NodeTracer open-telemetry#752
1 parent 52f583d commit e7f07db

File tree

4 files changed

+158
-8
lines changed

4 files changed

+158
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*!
2+
* Copyright 2020, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*!
18+
* Copyright 2019, OpenTelemetry Authors
19+
*
20+
* Licensed under the Apache License, Version 2.0 (the "License");
21+
* you may not use this file except in compliance with the License.
22+
* You may obtain a copy of the License at
23+
*
24+
* https://www.apache.org/licenses/LICENSE-2.0
25+
*
26+
* Unless required by applicable law or agreed to in writing, software
27+
* distributed under the License is distributed on an "AS IS" BASIS,
28+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29+
* See the License for the specific language governing permissions and
30+
* limitations under the License.
31+
*/
32+
33+
import * as api from '@opentelemetry/api';
34+
import { setActiveSpan } from '@opentelemetry/core';
35+
import {
36+
BasicTracerProvider,
37+
Tracer,
38+
TracerConfig,
39+
} from '@opentelemetry/tracing';
40+
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
41+
42+
/**
43+
* This class represents a nodejs-specific tracer.
44+
*/
45+
export class NodeTracer extends Tracer {
46+
/**
47+
* Constructs a new NodeTracer instance.
48+
*/
49+
constructor(config: TracerConfig, _tracerProvider: BasicTracerProvider) {
50+
super(config, _tracerProvider);
51+
}
52+
53+
async withSpanAsync<
54+
T extends Promise<any>,
55+
U extends (...args: unknown[]) => T
56+
>(span: api.Span, fn: U): Promise<T> {
57+
// @ts-ignore
58+
const contextManager = api.context._getContextManager();
59+
if (contextManager instanceof AsyncHooksContextManager) {
60+
return contextManager.withAsync(
61+
setActiveSpan(api.context.active(), span),
62+
fn
63+
);
64+
} else {
65+
this.logger.warn(
66+
`Using withAsync without AsyncHookContextManager doesn't work, please refer to`
67+
);
68+
return fn();
69+
}
70+
}
71+
}

packages/opentelemetry-node/src/NodeTracerProvider.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* Copyright 2019, OpenTelemetry Authors
2+
* Copyright 2020, OpenTelemetry Authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,9 +18,11 @@ import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
1818
import {
1919
BasicTracerProvider,
2020
SDKRegistrationConfig,
21+
TracerConfig,
2122
} from '@opentelemetry/tracing';
2223
import { DEFAULT_INSTRUMENTATION_PLUGINS, NodeTracerConfig } from './config';
2324
import { PluginLoader } from './instrumentation/PluginLoader';
25+
import { NodeTracer } from './NodeTracer';
2426

2527
/**
2628
* Register this TracerProvider for use with the OpenTelemetry API.
@@ -31,15 +33,16 @@ import { PluginLoader } from './instrumentation/PluginLoader';
3133
*/
3234
export class NodeTracerProvider extends BasicTracerProvider {
3335
private readonly _pluginLoader: PluginLoader;
36+
private readonly _nodeTracers: Map<string, NodeTracer> = new Map();
3437

3538
/**
3639
* Constructs a new Tracer instance.
3740
*/
38-
constructor(config: NodeTracerConfig = {}) {
41+
constructor(config?: NodeTracerConfig) {
3942
super(config);
4043

4144
this._pluginLoader = new PluginLoader(this, this.logger);
42-
this._pluginLoader.load(config.plugins || DEFAULT_INSTRUMENTATION_PLUGINS);
45+
this._pluginLoader.load(config?.plugins ?? DEFAULT_INSTRUMENTATION_PLUGINS);
4346
}
4447

4548
stop() {
@@ -51,7 +54,15 @@ export class NodeTracerProvider extends BasicTracerProvider {
5154
config.contextManager = new AsyncHooksContextManager();
5255
config.contextManager.enable();
5356
}
54-
5557
super.register(config);
5658
}
59+
60+
getTracer(name: string, version = '*', config?: TracerConfig): NodeTracer {
61+
const key = `${name}@${version}`;
62+
if (!this._nodeTracers.has(key)) {
63+
this._nodeTracers.set(key, new NodeTracer(config ?? this._config, this));
64+
}
65+
66+
return this._nodeTracers.get(key)!;
67+
}
5768
}

packages/opentelemetry-node/test/NodeTracerProvider.test.ts

+71-3
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ describe('NodeTracerProvider', () => {
186186
});
187187

188188
describe('.withSpan()', () => {
189-
it('should run context with AsyncHooksContextManager context manager', done => {
189+
it('should run context with AsyncHooksContextManager', done => {
190190
provider = new NodeTracerProvider({});
191191
const span = provider.getTracer('default').startSpan('my-span');
192192
provider.getTracer('default').withSpan(span, () => {
@@ -202,7 +202,7 @@ describe('NodeTracerProvider', () => {
202202
);
203203
});
204204

205-
it('should run context with AsyncHooksContextManager context manager with multiple spans', done => {
205+
it('should run context with AsyncHooksContextManager with multiple spans', done => {
206206
provider = new NodeTracerProvider({});
207207
const span = provider.getTracer('default').startSpan('my-span');
208208
provider.getTracer('default').withSpan(span, () => {
@@ -254,7 +254,7 @@ describe('NodeTracerProvider', () => {
254254
});
255255

256256
describe('.bind()', () => {
257-
it('should bind context with AsyncHooksContextManager context manager', done => {
257+
it('should bind context with AsyncHooksContextManager', done => {
258258
const provider = new NodeTracerProvider({});
259259
const span = provider.getTracer('default').startSpan('my-span');
260260
const fn = () => {
@@ -268,4 +268,72 @@ describe('NodeTracerProvider', () => {
268268
return patchedFn();
269269
});
270270
});
271+
272+
describe('.withSpanAsync()', () => {
273+
it('should run async context with AsyncHooksContextManager', done => {
274+
provider = new NodeTracerProvider({});
275+
const span = provider.getTracer('default').startSpan('my-span');
276+
void provider.getTracer('default').withSpanAsync(span, async () => {
277+
assert.deepStrictEqual(
278+
provider.getTracer('default').getCurrentSpan(),
279+
span
280+
);
281+
return done();
282+
});
283+
assert.deepStrictEqual(
284+
provider.getTracer('default').getCurrentSpan(),
285+
undefined
286+
);
287+
});
288+
289+
it('should run context with AsyncHooksContextManager with multiple spans', async () => {
290+
provider = new NodeTracerProvider({});
291+
let nestedHasBeenRun: boolean = false;
292+
const span = provider.getTracer('default').startSpan('my-span');
293+
await provider.getTracer('default').withSpanAsync(span, async () => {
294+
assert.deepStrictEqual(
295+
provider.getTracer('default').getCurrentSpan(),
296+
span
297+
);
298+
299+
const span1 = provider.getTracer('default').startSpan('my-span1');
300+
301+
await provider.getTracer('default').withSpanAsync(span1, async () => {
302+
assert.deepStrictEqual(
303+
provider.getTracer('default').getCurrentSpan(),
304+
span1
305+
);
306+
assert.deepStrictEqual(
307+
span1.context().traceId,
308+
span.context().traceId
309+
);
310+
nestedHasBeenRun = true;
311+
});
312+
});
313+
assert.deepStrictEqual(
314+
provider.getTracer('default').getCurrentSpan(),
315+
undefined
316+
);
317+
assert(nestedHasBeenRun);
318+
});
319+
320+
it('should find correct context with promises', async () => {
321+
provider = new NodeTracerProvider();
322+
const span = provider.getTracer('default').startSpan('my-span');
323+
await provider.getTracer('default').withSpanAsync(span, async () => {
324+
for (let i = 0; i < 3; i++) {
325+
await sleep(5).then(() => {
326+
assert.deepStrictEqual(
327+
provider.getTracer('default').getCurrentSpan(),
328+
span
329+
);
330+
});
331+
}
332+
});
333+
assert.deepStrictEqual(
334+
provider.getTracer('default').getCurrentSpan(),
335+
undefined
336+
);
337+
});
338+
});
271339
});

packages/opentelemetry-tracing/src/BasicTracerProvider.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { Resource } from '@opentelemetry/resources';
2727
* This class represents a basic tracer provider which platform libraries can extend
2828
*/
2929
export class BasicTracerProvider implements api.TracerProvider {
30-
private readonly _config: TracerConfig;
30+
protected readonly _config: TracerConfig;
3131
private readonly _registeredSpanProcessors: SpanProcessor[] = [];
3232
private readonly _tracers: Map<string, Tracer> = new Map();
3333

0 commit comments

Comments
 (0)