From aea89cf7a48395f3d403e1158122bc559b09e899 Mon Sep 17 00:00:00 2001 From: Naseem Date: Tue, 1 Jun 2021 16:14:20 -0400 Subject: [PATCH] feat: add startActiveSpan method to Tracer (#2221) Co-authored-by: Daniel Dyla --- package.json | 3 +- packages/opentelemetry-tracing/src/Tracer.ts | 128 ++++++++++++------ .../opentelemetry-tracing/test/Tracer.test.ts | 82 ++++++++++- 3 files changed, 169 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 1f11b0db1e..338d235fc7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "lint:examples": "eslint ./examples/**/*.js", "lint:examples:fix": "eslint ./examples/**/*.js --fix", "lint:markdown": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md", - "lint:markdown:fix": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md --fix" + "lint:markdown:fix": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md --fix", + "reset": "lerna clean -y && rm -rf node_modules && npm i && npm run compile && npm run lint:fix" }, "repository": "open-telemetry/opentelemetry-js", "keywords": [ diff --git a/packages/opentelemetry-tracing/src/Tracer.ts b/packages/opentelemetry-tracing/src/Tracer.ts index ff05bade16..446e944189 100644 --- a/packages/opentelemetry-tracing/src/Tracer.ts +++ b/packages/opentelemetry-tracing/src/Tracer.ts @@ -54,46 +54,6 @@ export class Tracer implements api.Tracer { this.instrumentationLibrary = instrumentationLibrary; } - startActiveSpan ReturnType>( - name: string, - arg2: F | api.SpanOptions, - arg3?: F | api.Context, - arg4?: F - ): ReturnType | undefined { - let fn: F | undefined, - options: api.SpanOptions | undefined, - activeContext: api.Context | undefined; - - if (arguments.length === 2 && typeof arg2 === 'function') { - fn = arg2; - } else if ( - arguments.length === 3 && - typeof arg2 === 'object' && - typeof arg3 === 'function' - ) { - options = arg2; - fn = arg3; - } else if ( - arguments.length === 4 && - typeof arg2 === 'object' && - typeof arg3 === 'object' && - typeof arg4 === 'function' - ) { - options = arg2; - activeContext = arg3; - fn = arg4; - } - - const parentContext = activeContext ?? api.context.active(); - const span = this.startSpan(name, options, parentContext); - const contextWithSpanSet = api.trace.setSpan(parentContext, span); - - if (fn) { - return api.context.with(contextWithSpanSet, fn, undefined, span); - } - return; - } - /** * Starts a new Span or returns the default NoopSpan based on the sampling * decision. @@ -163,6 +123,94 @@ export class Tracer implements api.Tracer { return span; } + /** + * Starts a new {@link Span} and calls the given function passing it the + * created span as first argument. + * Additionally the new span gets set in context and this context is activated + * for the duration of the function call. + * + * @param name The name of the span + * @param [options] SpanOptions used for span creation + * @param [context] Context to use to extract parent + * @param fn function called in the context of the span and receives the newly created span as an argument + * @returns return value of fn + * @example + * const something = tracer.startActiveSpan('op', span => { + * try { + * do some work + * span.setStatus({code: SpanStatusCode.OK}); + * return something; + * } catch (err) { + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: err.message, + * }); + * throw err; + * } finally { + * span.end(); + * } + * }); + * @example + * const span = tracer.startActiveSpan('op', span => { + * try { + * do some work + * return span; + * } catch (err) { + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: err.message, + * }); + * throw err; + * } + * }); + * do some more work + * span.end(); + */ + startActiveSpan ReturnType>( + name: string, + fn: F + ): ReturnType; + startActiveSpan ReturnType>( + name: string, + opts: api.SpanOptions, + fn: F + ): ReturnType; + startActiveSpan ReturnType>( + name: string, + opts: api.SpanOptions, + ctx: api.Context, + fn: F + ): ReturnType; + startActiveSpan ReturnType>( + name: string, + arg2?: F | api.SpanOptions, + arg3?: F | api.Context, + arg4?: F + ): ReturnType | undefined { + let opts: api.SpanOptions | undefined; + let ctx: api.Context | undefined; + let fn: F; + + if (arguments.length < 2) { + return; + } else if (arguments.length === 2) { + fn = arg2 as F; + } else if (arguments.length === 3) { + opts = arg2 as api.SpanOptions | undefined; + fn = arg3 as F; + } else { + opts = arg2 as api.SpanOptions | undefined; + ctx = arg3 as api.Context | undefined; + fn = arg4 as F; + } + + const parentContext = ctx ?? api.context.active(); + const span = this.startSpan(name, opts, parentContext); + const contextWithSpanSet = api.trace.setSpan(parentContext, span); + + return api.context.with(contextWithSpanSet, fn, undefined, span); + } + /** Returns the active {@link SpanLimits}. */ getSpanLimits(): SpanLimits { return this._spanLimits; diff --git a/packages/opentelemetry-tracing/test/Tracer.test.ts b/packages/opentelemetry-tracing/test/Tracer.test.ts index 08b2b66c79..aa42daf0e8 100644 --- a/packages/opentelemetry-tracing/test/Tracer.test.ts +++ b/packages/opentelemetry-tracing/test/Tracer.test.ts @@ -13,24 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { + context, + createContextKey, INVALID_TRACEID, ROOT_CONTEXT, Sampler, SamplingDecision, - TraceFlags, SpanContext, trace, + TraceFlags } from '@opentelemetry/api'; +import { getSpan } from '@opentelemetry/api/build/src/trace/context-utils'; import { AlwaysOffSampler, AlwaysOnSampler, InstrumentationLibrary, - suppressTracing, + suppressTracing } from '@opentelemetry/core'; import * as assert from 'assert'; import { BasicTracerProvider, Span, Tracer } from '../src'; +import { TestStackContextManager } from './export/TestStackContextManager'; +import * as sinon from 'sinon'; describe('Tracer', () => { const tracerProvider = new BasicTracerProvider(); @@ -49,7 +53,13 @@ describe('Tracer', () => { } } + beforeEach(() => { + const contextManager = new TestStackContextManager().enable(); + context.setGlobalContextManager(contextManager); + }); + afterEach(() => { + context.disable(); delete envSource.OTEL_TRACES_SAMPLER; delete envSource.OTEL_TRACES_SAMPLER_ARG; }); @@ -220,4 +230,70 @@ describe('Tracer', () => { assert.strictEqual(context.traceFlags, TraceFlags.NONE); span.end(); }); + + it('should start an active span with name and function args', () => { + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + { sampler: new TestSampler() }, + tracerProvider + ); + + const spy = sinon.spy(tracer, "startSpan"); + + assert.strictEqual(tracer.startActiveSpan('my-span', span => { + try { + assert(spy.calledWith('my-span')) + assert.strictEqual(getSpan(context.active()), span) + return 1 + } finally { + span.end(); + } + }), 1); + }); + + it('should start an active span with name, options and function args', () => { + + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + { sampler: new TestSampler() }, + tracerProvider + ); + + const spy = sinon.spy(tracer, "startSpan"); + + assert.strictEqual(tracer.startActiveSpan('my-span', {attributes: {foo: 'bar'}}, span => { + try { + assert(spy.calledWith('my-span', {attributes: {foo: 'bar'}})) + assert.strictEqual(getSpan(context.active()), span) + return 1 + } finally { + span.end(); + } + }), 1); + }); + + it('should start an active span with name, options, context and function args', () => { + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + { sampler: new TestSampler() }, + tracerProvider + ); + + const ctxKey = createContextKey('foo'); + + const ctx = context.active().setValue(ctxKey, 'bar') + + const spy = sinon.spy(tracer, "startSpan"); + + assert.strictEqual(tracer.startActiveSpan('my-span', {attributes: {foo: 'bar'}}, ctx, span => { + try { + assert(spy.calledWith('my-span', {attributes: {foo: 'bar'}}, ctx)) + assert.strictEqual(getSpan(context.active()), span) + assert.strictEqual(ctx.getValue(ctxKey), 'bar') + return 1 + } finally { + span.end(); + } + }), 1); + }); });