Skip to content

Commit

Permalink
feat: add startActiveSpan method to Tracer (#2221)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
  • Loading branch information
Naseem and dyladan committed Jun 1, 2021
1 parent d551781 commit aea89cf
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 44 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -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": [
Expand Down
128 changes: 88 additions & 40 deletions packages/opentelemetry-tracing/src/Tracer.ts
Expand Up @@ -54,46 +54,6 @@ export class Tracer implements api.Tracer {
this.instrumentationLibrary = instrumentationLibrary;
}

startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
name: string,
arg2: F | api.SpanOptions,
arg3?: F | api.Context,
arg4?: F
): ReturnType<F> | 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.
Expand Down Expand Up @@ -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<F extends (span: api.Span) => ReturnType<F>>(
name: string,
fn: F
): ReturnType<F>;
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
name: string,
opts: api.SpanOptions,
fn: F
): ReturnType<F>;
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
name: string,
opts: api.SpanOptions,
ctx: api.Context,
fn: F
): ReturnType<F>;
startActiveSpan<F extends (span: api.Span) => ReturnType<F>>(
name: string,
arg2?: F | api.SpanOptions,
arg3?: F | api.Context,
arg4?: F
): ReturnType<F> | 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;
Expand Down
82 changes: 79 additions & 3 deletions packages/opentelemetry-tracing/test/Tracer.test.ts
Expand Up @@ -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();
Expand All @@ -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;
});
Expand Down Expand Up @@ -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);
});
});

0 comments on commit aea89cf

Please sign in to comment.