Skip to content

Commit

Permalink
add GlobalTracer API (open-telemetry#118)
Browse files Browse the repository at this point in the history
* add GlobalTracer

* remove extra tslint rule

* update format types

* refactor to add globaltracer-utils

* rename: GlobalTracer to GlobalTracerDelegate

* remove 1st arg from Delegate constructor

* rename GlobalTracerDelegate to TracerDelegate

* refactor TracerDelegate tracer as runtime readonly

* refactor globaltracer-utils

* fix new lint issues

* add missing linter fix

* move no-any disabler

* delegate: add start/stop to use fallback

* core: reorder exports

* delegate: use constructor initializers
  • Loading branch information
markwolff authored and mayurkale22 committed Jul 31, 2019
1 parent a15ccb9 commit 081469f
Show file tree
Hide file tree
Showing 5 changed files with 337 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/opentelemetry-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ export * from './context/propagation/BinaryTraceContext';
export * from './context/propagation/HttpTraceContext';
export * from './platform';
export * from './resources/Resource';
export * from './trace/globaltracer-utils';
export * from './trace/instrumentation/BasePlugin';
export * from './trace/NoopSpan';
export * from './trace/NoopTracer';
export * from './trace/sampler/ProbabilitySampler';
export * from './trace/spancontext-utils';
export * from './trace/TracerDelegate';
100 changes: 100 additions & 0 deletions packages/opentelemetry-core/src/trace/TracerDelegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright 2019, 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 types from '@opentelemetry/types';
import { NoopTracer } from './NoopTracer';

// Acts a bridge to the global tracer that can be safely called before the
// global tracer is initialized. The purpose of the delegation is to avoid the
// sometimes nearly intractible initialization order problems that can arise in
// applications with a complex set of dependencies. Also allows for the tracer
// to be changed/disabled during runtime without needing to change reference
// to the global tracer
export class TracerDelegate implements types.Tracer {
private _currentTracer: types.Tracer;

// Wrap a tracer with a TracerDelegate. Provided tracer becomes the default
// fallback tracer for when a global tracer has not been initialized
constructor(
private readonly tracer: types.Tracer | null = null,
private readonly fallbackTracer: types.Tracer = new NoopTracer()
) {
this._currentTracer = tracer || fallbackTracer; // equivalent to this.start()
}

// Begin using the user provided tracer. Stop always falling back to fallback tracer
start(): void {
this._currentTracer = this.tracer || this.fallbackTracer;
}

// Stop the delegate from using the provided tracer. Begin to use the fallback tracer
stop(): void {
this._currentTracer = this.fallbackTracer;
}

// -- Tracer interface implementation below -- //

getCurrentSpan(): types.Span {
return this._currentTracer.getCurrentSpan.apply(
this._currentTracer,
// tslint:disable-next-line:no-any
arguments as any
);
}

startSpan(name: string, options?: types.SpanOptions | undefined): types.Span {
return this._currentTracer.startSpan.apply(
this._currentTracer,
// tslint:disable-next-line:no-any
arguments as any
);
}

withSpan<T extends (...args: unknown[]) => unknown>(
span: types.Span,
fn: T
): ReturnType<T> {
return this._currentTracer.withSpan.apply(
this._currentTracer,
// tslint:disable-next-line:no-any
arguments as any
) as ReturnType<T>;
}

recordSpanData(span: types.Span): void {
return this._currentTracer.recordSpanData.apply(
this._currentTracer,
// tslint:disable-next-line:no-any
arguments as any
);
}

getBinaryFormat(): types.BinaryFormat {
return this._currentTracer.getBinaryFormat.apply(
this._currentTracer,
// tslint:disable-next-line:no-any
arguments as any
);
}

getHttpTextFormat(): types.HttpTextFormat {
return this._currentTracer.getHttpTextFormat.apply(
this._currentTracer,
// tslint:disable-next-line:no-any
arguments as any
);
}
}
35 changes: 35 additions & 0 deletions packages/opentelemetry-core/src/trace/globaltracer-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright 2019, 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 types from '@opentelemetry/types';
import { TracerDelegate } from './TracerDelegate';

let globalTracerDelegate = new TracerDelegate();

/**
* Set the current global tracer. Returns the initialized global tracer
*/
export function initGlobalTracer(tracer: types.Tracer): types.Tracer {
return (globalTracerDelegate = new TracerDelegate(tracer));
}

/**
* Returns the global tracer
*/
export function getTracer(): types.Tracer {
// Return the global tracer delegate
return globalTracerDelegate;
}
117 changes: 117 additions & 0 deletions packages/opentelemetry-core/test/trace/TracerDelegate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Copyright 2019, 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 types from '@opentelemetry/types';
import { TracerDelegate } from '../../src/trace/TracerDelegate';
import { NoopTracer, NoopSpan } from '../../src';
import { TraceOptions } from '@opentelemetry/types';

describe('TracerDelegate', () => {
const functions = [
'getCurrentSpan',
'startSpan',
'withSpan',
'recordSpanData',
'getBinaryFormat',
'getHttpTextFormat',
];
const spanContext = {
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
spanId: '6e0c63257de34c92',
traceOptions: TraceOptions.UNSAMPLED,
};

describe('#constructor(...)', () => {
it('should not crash with default constructor', () => {
functions.forEach(fn => {
const tracer = new TracerDelegate();
try {
((tracer as unknown) as { [fn: string]: Function })[fn](); // Try to run the function
assert.ok(true, fn);
} catch (err) {
if (err.message !== 'Method not implemented.') {
assert.ok(false, fn);
}
}
});
});

it('should allow fallback tracer to be set', () => {
const dummyTracer = new DummyTracer();
const tracerDelegate = new TracerDelegate(dummyTracer);

tracerDelegate.startSpan('foo');
assert.deepStrictEqual(dummyTracer.spyCounter, 1);
});

it('should use user provided tracer if provided', () => {
const dummyTracer = new DummyTracer();
const tracerDelegate = new TracerDelegate(dummyTracer);

tracerDelegate.startSpan('foo');
assert.deepStrictEqual(dummyTracer.spyCounter, 1);
});

describe('#start/stop()', () => {
it('should use the fallback tracer when stop is called', () => {
const dummyTracerUser = new DummyTracer();
const dummyTracerFallback = new DummyTracer();
const tracerDelegate = new TracerDelegate(
dummyTracerUser,
dummyTracerFallback
);

tracerDelegate.stop();
tracerDelegate.startSpan('fallback');
assert.deepStrictEqual(dummyTracerUser.spyCounter, 0);
assert.deepStrictEqual(dummyTracerFallback.spyCounter, 1);
});

it('should use the user tracer when start is called', () => {
const dummyTracerUser = new DummyTracer();
const dummyTracerFallback = new DummyTracer();
const tracerDelegate = new TracerDelegate(
dummyTracerUser,
dummyTracerFallback
);

tracerDelegate.stop();
tracerDelegate.startSpan('fallback');
assert.deepStrictEqual(dummyTracerUser.spyCounter, 0);
assert.deepStrictEqual(dummyTracerFallback.spyCounter, 1);

tracerDelegate.start();
tracerDelegate.startSpan('user');
assert.deepStrictEqual(dummyTracerUser.spyCounter, 1);
assert.deepStrictEqual(
dummyTracerFallback.spyCounter,
1,
'Only user tracer counter is incremented'
);
});
});
});

class DummyTracer extends NoopTracer {
spyCounter = 0;

startSpan(name: string, options?: types.SpanOptions | undefined) {
this.spyCounter = this.spyCounter + 1;
return new NoopSpan(spanContext);
}
}
});
83 changes: 83 additions & 0 deletions packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright 2019, 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 types from '@opentelemetry/types';
import {
getTracer,
initGlobalTracer,
} from '../../src/trace/globaltracer-utils';
import { NoopTracer, NoopSpan } from '../../src';
import { TraceOptions } from '@opentelemetry/types';

describe('globaltracer-utils', () => {
const functions = [
'getCurrentSpan',
'startSpan',
'withSpan',
'recordSpanData',
'getBinaryFormat',
'getHttpTextFormat',
];

it('should expose a tracer via getTracer', () => {
const tracer = getTracer();
assert.ok(tracer);
assert.strictEqual(typeof tracer, 'object');
});

describe('GlobalTracer', () => {
const spanContext = {
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
spanId: '6e0c63257de34c92',
traceOptions: TraceOptions.UNSAMPLED,
};
const dummySpan = new NoopSpan(spanContext);

afterEach(() => {
initGlobalTracer(new NoopTracer());
});

it('should not crash', () => {
functions.forEach(fn => {
const tracer = getTracer();
try {
((tracer as unknown) as { [fn: string]: Function })[fn](); // Try to run the function
assert.ok(true, fn);
} catch (err) {
if (err.message !== 'Method not implemented.') {
assert.ok(false, fn);
}
}
});
});

it('should use the global tracer', () => {
const tracer = initGlobalTracer(new TestTracer());
const span = tracer.startSpan('test');
assert.deepStrictEqual(span, dummySpan);
});

class TestTracer extends NoopTracer {
startSpan(
name: string,
options?: types.SpanOptions | undefined
): types.Span {
return dummySpan;
}
}
});
});

0 comments on commit 081469f

Please sign in to comment.