Skip to content

Commit

Permalink
feat: tracers provided by the API become useable when provider regist…
Browse files Browse the repository at this point in the history
…ered (#1448)
  • Loading branch information
dyladan authored Aug 27, 2020
1 parent 2052a24 commit 5c7753f
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 14 deletions.
2 changes: 2 additions & 0 deletions packages/opentelemetry-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"devDependencies": {
"@types/mocha": "8.0.2",
"@types/node": "14.0.27",
"@types/sinon": "4.3.1",
"@types/webpack-env": "1.15.2",
"codecov": "3.7.2",
"gts": "2.0.2",
Expand All @@ -70,6 +71,7 @@
"linkinator": "2.1.1",
"mocha": "7.2.0",
"nyc": "15.1.0",
"sinon": "9.0.3",
"ts-loader": "8.0.2",
"ts-mocha": "7.0.0",
"typedoc": "0.18.0",
Expand Down
10 changes: 8 additions & 2 deletions packages/opentelemetry-api/src/api/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { NOOP_TRACER_PROVIDER } from '../trace/NoopTracerProvider';
import { ProxyTracerProvider } from '../trace/ProxyTracerProvider';
import { Tracer } from '../trace/tracer';
import { TracerProvider } from '../trace/tracer_provider';
import {
Expand All @@ -30,6 +31,8 @@ import {
export class TraceAPI {
private static _instance?: TraceAPI;

private _proxyTracerProvider = new ProxyTracerProvider();

/** Empty private constructor prevents end users from constructing a new instance of the API */
private constructor() {}

Expand All @@ -51,9 +54,11 @@ export class TraceAPI {
return this.getTracerProvider();
}

this._proxyTracerProvider.setDelegate(provider);

_global[GLOBAL_TRACE_API_KEY] = makeGetter(
API_BACKWARDS_COMPATIBILITY_VERSION,
provider,
this._proxyTracerProvider,
NOOP_TRACER_PROVIDER
);

Expand All @@ -66,7 +71,7 @@ export class TraceAPI {
public getTracerProvider(): TracerProvider {
return (
_global[GLOBAL_TRACE_API_KEY]?.(API_BACKWARDS_COMPATIBILITY_VERSION) ??
NOOP_TRACER_PROVIDER
this._proxyTracerProvider
);
}

Expand All @@ -80,5 +85,6 @@ export class TraceAPI {
/** Remove the global tracer provider */
public disable() {
delete _global[GLOBAL_TRACE_API_KEY];
this._proxyTracerProvider = new ProxyTracerProvider();
}
}
2 changes: 2 additions & 0 deletions packages/opentelemetry-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export * from './trace/link';
export * from './trace/NoopSpan';
export * from './trace/NoopTracer';
export * from './trace/NoopTracerProvider';
export * from './trace/ProxyTracer';
export * from './trace/ProxyTracerProvider';
export * from './trace/Sampler';
export * from './trace/SamplingResult';
export * from './trace/span_context';
Expand Down
71 changes: 71 additions & 0 deletions packages/opentelemetry-api/src/trace/ProxyTracer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Span, SpanOptions, Tracer } from '..';
import { NOOP_TRACER } from './NoopTracer';
import { ProxyTracerProvider } from './ProxyTracerProvider';

/**
* Proxy tracer provided by the proxy tracer provider
*/
export class ProxyTracer implements Tracer {
// When a real implementation is provided, this will be it
private _delegate?: Tracer;

constructor(
private _provider: ProxyTracerProvider,
public readonly name: string,
public readonly version?: string
) {}

getCurrentSpan(): Span | undefined {
return this._getTracer().getCurrentSpan();
}

startSpan(name: string, options?: SpanOptions): Span {
return this._getTracer().startSpan(name, options);
}

withSpan<T extends (...args: unknown[]) => ReturnType<T>>(
span: Span,
fn: T
): ReturnType<T> {
return this._getTracer().withSpan(span, fn);
}

bind<T>(target: T, span?: Span): T {
return this._getTracer().bind(target, span);
}

/**
* Try to get a tracer from the proxy tracer provider.
* If the proxy tracer provider has no delegate, return a noop tracer.
*/
private _getTracer() {
if (this._delegate) {
return this._delegate;
}

const tracer = this._provider.getDelegateTracer(this.name, this.version);

if (!tracer) {
return NOOP_TRACER;
}

this._delegate = tracer;
return this._delegate;
}
}
57 changes: 57 additions & 0 deletions packages/opentelemetry-api/src/trace/ProxyTracerProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Tracer } from './tracer';
import { TracerProvider } from './tracer_provider';
import { ProxyTracer } from './ProxyTracer';
import { NOOP_TRACER_PROVIDER } from './NoopTracerProvider';

/**
* Tracer provider which provides {@link ProxyTracer}s.
*
* Before a delegate is set, tracers provided are NoOp.
* When a delegate is set, traces are provided from the delegate.
* When a delegate is set after tracers have already been provided,
* all tracers already provided will use the provided delegate implementation.
*/
export class ProxyTracerProvider implements TracerProvider {
private _delegate?: TracerProvider;

/**
* Get a {@link ProxyTracer}
*/
getTracer(name: string, version?: string): Tracer {
return (
this.getDelegateTracer(name, version) ??
new ProxyTracer(this, name, version)
);
}

getDelegate(): TracerProvider {
return this._delegate ?? NOOP_TRACER_PROVIDER;
}

/**
* Set the delegate tracer provider
*/
setDelegate(delegate: TracerProvider) {
this._delegate = delegate;
}

getDelegateTracer(name: string, version?: string): Tracer | undefined {
return this._delegate?.getTracer(name, version);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as assert from 'assert';
import * as sinon from 'sinon';
import {
NoopSpan,
NOOP_SPAN,
ProxyTracerProvider,
SpanKind,
TracerProvider,
ProxyTracer,
Tracer,
Span,
NoopTracer,
} from '../../src';

describe('ProxyTracer', () => {
let provider: ProxyTracerProvider;

beforeEach(() => {
provider = new ProxyTracerProvider();
});

describe('when no delegate is set', () => {
it('should return proxy tracers', () => {
const tracer = provider.getTracer('test');

assert.ok(tracer instanceof ProxyTracer);
});

it('startSpan should return Noop Spans', () => {
const tracer = provider.getTracer('test');

assert.deepStrictEqual(tracer.startSpan('span-name'), NOOP_SPAN);
assert.deepStrictEqual(
tracer.startSpan('span-name1', { kind: SpanKind.CLIENT }),
NOOP_SPAN
);
assert.deepStrictEqual(
tracer.startSpan('span-name2', {
kind: SpanKind.CLIENT,
}),
NOOP_SPAN
);

assert.deepStrictEqual(tracer.getCurrentSpan(), NOOP_SPAN);
});
});

describe('when delegate is set before getTracer', () => {
let delegate: TracerProvider;
const sandbox = sinon.createSandbox();
let getTracerStub: sinon.SinonStub;

beforeEach(() => {
getTracerStub = sandbox.stub().returns(new NoopTracer());
delegate = {
getTracer: getTracerStub,
};
provider.setDelegate(delegate);
});

afterEach(() => {
sandbox.restore();
});

it('should return tracers directly from the delegate', () => {
const tracer = provider.getTracer('test', 'v0');

sandbox.assert.calledOnce(getTracerStub);
assert.strictEqual(getTracerStub.firstCall.returnValue, tracer);
assert.deepEqual(getTracerStub.firstCall.args, ['test', 'v0']);
});
});

describe('when delegate is set after getTracer', () => {
let tracer: Tracer;
let delegate: TracerProvider;
let delegateSpan: Span;
let delegateTracer: Tracer;

beforeEach(() => {
delegateSpan = new NoopSpan();
delegateTracer = {
bind(target) {
return target;
},
getCurrentSpan() {
return delegateSpan;
},
startSpan() {
return delegateSpan;
},
withSpan(span, fn) {
return fn();
},
};

tracer = provider.getTracer('test');

delegate = {
getTracer() {
return delegateTracer;
},
};
provider.setDelegate(delegate);
});

it('should create spans using the delegate tracer', () => {
const span = tracer.startSpan('test');

assert.strictEqual(span, delegateSpan);
});
});
});
20 changes: 16 additions & 4 deletions packages/opentelemetry-node/test/registration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
NoopTextMapPropagator,
propagation,
trace,
ProxyTracerProvider,
} from '@opentelemetry/api';
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
import { NoopContextManager } from '@opentelemetry/context-base';
Expand All @@ -43,7 +44,10 @@ describe('API registration', () => {
assert.ok(
propagation['_getGlobalPropagator']() instanceof CompositePropagator
);
assert.ok(trace.getTracerProvider() === tracerProvider);
const apiTracerProvider = trace.getTracerProvider();

assert.ok(apiTracerProvider instanceof ProxyTracerProvider);
assert.ok(apiTracerProvider.getDelegate() === tracerProvider);
});

it('should register configured implementations', () => {
Expand All @@ -60,7 +64,9 @@ describe('API registration', () => {
assert.ok(context['_getContextManager']() === contextManager);
assert.ok(propagation['_getGlobalPropagator']() === propagator);

assert.ok(trace.getTracerProvider() === tracerProvider);
const apiTracerProvider = trace.getTracerProvider();
assert.ok(apiTracerProvider instanceof ProxyTracerProvider);
assert.ok(apiTracerProvider.getDelegate() === tracerProvider);
});

it('should skip null context manager', () => {
Expand All @@ -74,7 +80,10 @@ describe('API registration', () => {
assert.ok(
propagation['_getGlobalPropagator']() instanceof CompositePropagator
);
assert.ok(trace.getTracerProvider() === tracerProvider);

const apiTracerProvider = trace.getTracerProvider();
assert.ok(apiTracerProvider instanceof ProxyTracerProvider);
assert.ok(apiTracerProvider.getDelegate() === tracerProvider);
});

it('should skip null propagator', () => {
Expand All @@ -90,6 +99,9 @@ describe('API registration', () => {
assert.ok(
context['_getContextManager']() instanceof AsyncHooksContextManager
);
assert.ok(trace.getTracerProvider() === tracerProvider);

const apiTracerProvider = trace.getTracerProvider();
assert.ok(apiTracerProvider instanceof ProxyTracerProvider);
assert.ok(apiTracerProvider.getDelegate() === tracerProvider);
});
});
Loading

0 comments on commit 5c7753f

Please sign in to comment.