diff --git a/examples/basic-tracer-node/index.js b/examples/basic-tracer-node/index.js index 19b2a7b881..3329e7f2c6 100644 --- a/examples/basic-tracer-node/index.js +++ b/examples/basic-tracer-node/index.js @@ -1,5 +1,5 @@ const opentelemetry = require('@opentelemetry/core'); -const { BasicTracer, SimpleSpanProcessor } = require('@opentelemetry/tracing'); +const { BasicTracerFactory, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); @@ -17,14 +17,14 @@ if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new JaegerExporter(options); } -const tracer = new BasicTracer(); +// Initialize the OpenTelemetry APIs to use the BasicTracer bindings +opentelemetry.initGlobalTracerFactory(new BasicTracerFactory()); + +const tracer = opentelemetry.getTracer(); // Configure span processor to send spans to the provided exporter tracer.addSpanProcessor(new SimpleSpanProcessor(exporter)); -// Initialize the OpenTelemetry APIs to use the BasicTracer bindings -opentelemetry.initGlobalTracer(tracer); - // Create a span. A span must be closed. const span = opentelemetry.getTracer().startSpan('main'); for (let i = 0; i < 10; i++) { diff --git a/examples/grpc/setup.js b/examples/grpc/setup.js index 4b01ef130a..f68f9da920 100644 --- a/examples/grpc/setup.js +++ b/examples/grpc/setup.js @@ -1,14 +1,14 @@ 'use strict'; const opentelemetry = require('@opentelemetry/core'); -const { NodeTracer } = require('@opentelemetry/node'); +const { NodeTracerFactory } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); const EXPORTER = process.env.EXPORTER || ''; function setupTracerAndExporters(service) { - const tracer = new NodeTracer({ + const factory = new NodeTracerFactory({ plugins: { grpc: { enabled: true, @@ -17,6 +17,7 @@ function setupTracerAndExporters(service) { } } }); + const tracer = factory.getTracer(); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { @@ -33,8 +34,8 @@ function setupTracerAndExporters(service) { tracer.addSpanProcessor(new SimpleSpanProcessor(exporter)); - // Initialize the OpenTelemetry APIs to use the BasicTracer bindings - opentelemetry.initGlobalTracer(tracer); + // Initialize the OpenTelemetry APIs to use the BasicTracerFactory bindings + opentelemetry.initGlobalTracerFactory(factory); } exports.setupTracerAndExporters = setupTracerAndExporters; diff --git a/examples/http/setup.js b/examples/http/setup.js index 3e729201cd..dae462db99 100644 --- a/examples/http/setup.js +++ b/examples/http/setup.js @@ -1,14 +1,15 @@ 'use strict'; const opentelemetry = require('@opentelemetry/core'); -const { NodeTracer } = require('@opentelemetry/node'); +const { NodeTracerFactory } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); const EXPORTER = process.env.EXPORTER || ''; function setupTracerAndExporters(service) { - const tracer = new NodeTracer(); + const factory = new NodeTracerFactory(); + const tracer = factory.getTracer(); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { @@ -26,7 +27,7 @@ function setupTracerAndExporters(service) { tracer.addSpanProcessor(new SimpleSpanProcessor(exporter)); // Initialize the OpenTelemetry APIs to use the BasicTracer bindings - opentelemetry.initGlobalTracer(tracer); + opentelemetry.initGlobalTracerFactory(factory); } exports.setupTracerAndExporters = setupTracerAndExporters; diff --git a/examples/https/setup.js b/examples/https/setup.js index d09961eec9..5df2b12a56 100644 --- a/examples/https/setup.js +++ b/examples/https/setup.js @@ -1,15 +1,17 @@ 'use strict'; const opentelemetry = require('@opentelemetry/core'); -const { NodeTracer } = require('@opentelemetry/node'); +const { NodeTracerFactory } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); const EXPORTER = process.env.EXPORTER || ''; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + function setupTracerAndExporters(service) { let exporter; - const tracer = new NodeTracer(); + const factory = new NodeTracerFactory(); + const tracer = factory.getTracer(); if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ @@ -26,7 +28,7 @@ function setupTracerAndExporters(service) { tracer.addSpanProcessor(new SimpleSpanProcessor(exporter)); // Initialize the OpenTelemetry APIs to use the BasicTracer bindings - opentelemetry.initGlobalTracer(tracer); + opentelemetry.initGlobalTracerFactory(factory); } exports.setupTracerAndExporters = setupTracerAndExporters; diff --git a/examples/tracer-web/index.js b/examples/tracer-web/index.js index a8e3e34b41..a43182da51 100644 --- a/examples/tracer-web/index.js +++ b/examples/tracer-web/index.js @@ -1,11 +1,13 @@ import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; -import { WebTracer } from '@opentelemetry/web'; +import { WebTracerFactory } from '@opentelemetry/web'; import { DocumentLoad } from '@opentelemetry/plugin-document-load'; -const webTracer = new WebTracer({ +const webTracerFactory = new WebTracerFactory({ plugins: [ new DocumentLoad() ] }); -webTracer.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); +webTracerFactory.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + +const webTracer = webTracerFactory.getTracer('example'); diff --git a/packages/opentelemetry-core/src/index.ts b/packages/opentelemetry-core/src/index.ts index 581d9ab055..9a4d848f50 100644 --- a/packages/opentelemetry-core/src/index.ts +++ b/packages/opentelemetry-core/src/index.ts @@ -26,9 +26,11 @@ export * from './trace/globaltracer-utils'; export * from './trace/instrumentation/BasePlugin'; export * from './trace/NoopSpan'; export * from './trace/NoopTracer'; +export * from './trace/AbstractTracerFactory'; +export * from './trace/NoopTracerFactory'; export * from './trace/NoRecordingSpan'; export * from './trace/sampler/ProbabilitySampler'; export * from './trace/spancontext-utils'; -export * from './trace/TracerDelegate'; +export * from './trace/TracerFactoryDelegate'; export * from './trace/TraceState'; export * from './metrics/NoopMeter'; diff --git a/packages/opentelemetry-core/src/trace/AbstractTracerFactory.ts b/packages/opentelemetry-core/src/trace/AbstractTracerFactory.ts new file mode 100644 index 0000000000..4af59524e1 --- /dev/null +++ b/packages/opentelemetry-core/src/trace/AbstractTracerFactory.ts @@ -0,0 +1,45 @@ +/*! + * 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'; + +/** + * AbstractTracerFactory provides a base class for other tracer factories to extend from. + */ +export abstract class AbstractTracerFactory + implements types.TracerFactory { + protected readonly _tracers: Map = new Map(); + + /** + * _newTracer creates a new concrete tracer for factories that extend this. + */ + protected abstract _newTracer(): T; + + /** + * getTracer finds or creates a new tracer. + * @param name identifies the instrumentation library + * @param [version] is the semantic version of the library. + */ + getTracer(name: string, version?: string): types.Tracer { + const key = name + (version != undefined ? version : ''); + if (this._tracers.has(key)) return this._tracers.get(key)!; + + const tracer = this._newTracer(); + this._tracers.set(key, tracer); + + return tracer; + } +} diff --git a/packages/opentelemetry-core/src/trace/NoopTracerFactory.ts b/packages/opentelemetry-core/src/trace/NoopTracerFactory.ts new file mode 100644 index 0000000000..2e73ebbfe4 --- /dev/null +++ b/packages/opentelemetry-core/src/trace/NoopTracerFactory.ts @@ -0,0 +1,30 @@ +/*! + * 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'; + +export class NoopTracerFactory implements types.TracerFactory { + private readonly _tracer: types.Tracer; + + constructor() { + this._tracer = new NoopTracer(); + } + + getTracer(name: string, version?: string): types.Tracer { + return this._tracer; + } +} diff --git a/packages/opentelemetry-core/src/trace/TracerDelegate.ts b/packages/opentelemetry-core/src/trace/TracerDelegate.ts deleted file mode 100644 index cb90722d72..0000000000 --- a/packages/opentelemetry-core/src/trace/TracerDelegate.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * 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 intractable 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; - private readonly _tracer: types.Tracer | null; - private readonly _fallbackTracer: 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(tracer?: types.Tracer, fallbackTracer?: types.Tracer) { - this._tracer = tracer || null; - this._fallbackTracer = fallbackTracer || new NoopTracer(); - this._currentTracer = this._tracer || this._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 | null { - return this._currentTracer.getCurrentSpan.apply( - this._currentTracer, - // tslint:disable-next-line:no-any - arguments as any - ); - } - - bind(target: T, span?: types.Span): T { - return (this._currentTracer.bind.apply( - this._currentTracer, - // tslint:disable-next-line:no-any - arguments as any - ) as unknown) as T; - } - - startSpan(name: string, options?: types.SpanOptions): types.Span { - return this._currentTracer.startSpan.apply( - this._currentTracer, - // tslint:disable-next-line:no-any - arguments as any - ); - } - - withSpan ReturnType>( - span: types.Span, - fn: T - ): ReturnType { - return this._currentTracer.withSpan.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 - ); - } -} diff --git a/packages/opentelemetry-core/src/trace/TracerFactoryDelegate.ts b/packages/opentelemetry-core/src/trace/TracerFactoryDelegate.ts new file mode 100644 index 0000000000..07e290dd93 --- /dev/null +++ b/packages/opentelemetry-core/src/trace/TracerFactoryDelegate.ts @@ -0,0 +1,60 @@ +/*! + * 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 { NoopTracerFactory } from './NoopTracerFactory'; + +// Acts a bridge to the global tracer factory that can be safely called before the +// global tracer factory is initialized. The purpose of the delegation is to avoid the +// sometimes nearly intractable initialization order problems that can arise in +// applications with a complex set of dependencies. Also allows for the factory +// to be changed/disabled during runtime without needing to change reference +// to the global factory. +export class TracerFactoryDelegate implements types.TracerFactory { + private _currentTracerFactory: types.TracerFactory; + private readonly _tracerFactory: types.TracerFactory | null; + private readonly _fallbackTracerFactory: types.TracerFactory; + + // Wrap a TracerFactory with a TracerDelegateFactory. Provided factory becomes the default + // fallback factory for when a global factory has not been initialized + constructor( + tracerFactory?: types.TracerFactory, + fallbackTracerFactory?: types.TracerFactory + ) { + this._tracerFactory = tracerFactory || null; + this._fallbackTracerFactory = + fallbackTracerFactory || new NoopTracerFactory(); + this._currentTracerFactory = + this._tracerFactory || this._fallbackTracerFactory; // equivalent to this.start() + } + + // Begin using the user provided tracer factory. Stop always falling back to fallback tracer + // factory. + start(): void { + this._currentTracerFactory = + this._tracerFactory || this._fallbackTracerFactory; + } + + // Stop the delegate from using the provided tracer factory. Begin to use the fallback factory + stop(): void { + this._currentTracerFactory = this._fallbackTracerFactory; + } + + // -- TracerFactory interface implementation below -- // + getTracer(name: string, version?: string): types.Tracer { + return this._currentTracerFactory.getTracer(name, version); + } +} diff --git a/packages/opentelemetry-core/src/trace/globaltracer-utils.ts b/packages/opentelemetry-core/src/trace/globaltracer-utils.ts index 9f16402e4d..d44ac9748f 100644 --- a/packages/opentelemetry-core/src/trace/globaltracer-utils.ts +++ b/packages/opentelemetry-core/src/trace/globaltracer-utils.ts @@ -15,21 +15,28 @@ */ import * as types from '@opentelemetry/types'; -import { TracerDelegate } from './TracerDelegate'; +import { TracerFactoryDelegate } from './TracerFactoryDelegate'; -let globalTracerDelegate = new TracerDelegate(); +let globalTracerFactoryDelegate = new TracerFactoryDelegate(); /** * Set the current global tracer. Returns the initialized global tracer */ -export function initGlobalTracer(tracer: types.Tracer): types.Tracer { - return (globalTracerDelegate = new TracerDelegate(tracer)); +export function initGlobalTracerFactory( + tracerFactory: types.TracerFactory +): types.TracerFactory { + return (globalTracerFactoryDelegate = new TracerFactoryDelegate( + tracerFactory + )); +} + +export function getTracerFactory(): types.TracerFactory { + return globalTracerFactoryDelegate; } /** - * Returns the global tracer + * Finds or creates tracer from the global TracerFactory */ -export function getTracer(): types.Tracer { - // Return the global tracer delegate - return globalTracerDelegate; +export function getTracer(name: string = '', version?: string): types.Tracer { + return globalTracerFactoryDelegate.getTracer(name, version); } diff --git a/packages/opentelemetry-core/test/trace/NoopTracerFactory.test.ts b/packages/opentelemetry-core/test/trace/NoopTracerFactory.test.ts new file mode 100644 index 0000000000..429d223625 --- /dev/null +++ b/packages/opentelemetry-core/test/trace/NoopTracerFactory.test.ts @@ -0,0 +1,27 @@ +/*! + * 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 { NoopTracerFactory } from '../../src/trace/NoopTracerFactory'; +import { NOOP_SPAN } from '../../src/trace/NoopSpan'; + +describe('NoopTracerFactory', () => { + it('should return a NOOP_SPAN', () => { + const factory = new NoopTracerFactory(); + const span = factory.getTracer('fake').startSpan('my-span'); + assert.deepStrictEqual(span, NOOP_SPAN); + }); +}); diff --git a/packages/opentelemetry-core/test/trace/TracerDelegate.test.ts b/packages/opentelemetry-core/test/trace/TracerDelegate.test.ts deleted file mode 100644 index cc8772f263..0000000000 --- a/packages/opentelemetry-core/test/trace/TracerDelegate.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*! - * 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 { TraceFlags } from '@opentelemetry/types'; - -describe('TracerDelegate', () => { - const functions = [ - 'getCurrentSpan', - 'startSpan', - 'withSpan', - 'bind', - 'getBinaryFormat', - 'getHttpTextFormat', - ]; - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.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(true, 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); - } - } -}); diff --git a/packages/opentelemetry-core/test/trace/TracerFactoryDelegate.test.ts b/packages/opentelemetry-core/test/trace/TracerFactoryDelegate.test.ts new file mode 100644 index 0000000000..97457f8449 --- /dev/null +++ b/packages/opentelemetry-core/test/trace/TracerFactoryDelegate.test.ts @@ -0,0 +1,108 @@ +/*! + * 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 { TracerFactoryDelegate } from '../../src/trace/TracerFactoryDelegate'; +import { NoopTracerFactory, NoopTracer } from '../../src'; + +describe('TracerFactoryDelegate', () => { + const functions = ['getTracer']; + + describe('constructor', () => { + it('should not crash with default constructor', () => { + functions.forEach(fn => { + const tracerFactory = new TracerFactoryDelegate(); + try { + ((tracerFactory 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(true, fn); + } + } + }); + }); + + it('should allow fallback tracer factory to be set', () => { + const dummyTracerFactory = new DummyTracerFactory(); + const tracerFactoryDelegate = new TracerFactoryDelegate( + dummyTracerFactory + ); + + tracerFactoryDelegate.getTracer('dummy'); + assert.deepStrictEqual(dummyTracerFactory.spyCounter, 1); + }); + + it('should use user provided tracer factory if provided', () => { + const dummyTracerFactory = new DummyTracerFactory(); + const tracerFactoryDelegate = new TracerFactoryDelegate( + dummyTracerFactory + ); + + tracerFactoryDelegate.getTracer('foo', '1.0.0'); + assert.deepStrictEqual(dummyTracerFactory.spyCounter, 1); + }); + }); + + describe('.start/.stop()', () => { + it('should use the fallback tracer factory when stop is called', () => { + const dummyTracerFactoryUser = new DummyTracerFactory(); + const dummyTracerFactoryFallback = new DummyTracerFactory(); + const tracerFactoryDelegate = new TracerFactoryDelegate( + dummyTracerFactoryUser, + dummyTracerFactoryFallback + ); + + tracerFactoryDelegate.stop(); + tracerFactoryDelegate.getTracer('fallback'); + assert.deepStrictEqual(dummyTracerFactoryUser.spyCounter, 0); + assert.deepStrictEqual(dummyTracerFactoryFallback.spyCounter, 1); + }); + + it('should use the user tracer factory when start is called', () => { + const dummyTracerFactoryUser = new DummyTracerFactory(); + const dummyTracerFactoryFallback = new DummyTracerFactory(); + const tracerFactoryDelegate = new TracerFactoryDelegate( + dummyTracerFactoryUser, + dummyTracerFactoryFallback + ); + + tracerFactoryDelegate.stop(); + tracerFactoryDelegate.getTracer('fallback'); + assert.deepStrictEqual(dummyTracerFactoryUser.spyCounter, 0); + assert.deepStrictEqual(dummyTracerFactoryFallback.spyCounter, 1); + + tracerFactoryDelegate.start(); + tracerFactoryDelegate.getTracer('user'); + assert.deepStrictEqual(dummyTracerFactoryUser.spyCounter, 1); + assert.deepStrictEqual( + dummyTracerFactoryFallback.spyCounter, + 1, + 'Only user tracer factory counter is incremented' + ); + }); + }); + + class DummyTracerFactory extends NoopTracerFactory { + spyCounter = 0; + + getTracer(name?: string, version?: string): types.Tracer { + this.spyCounter = this.spyCounter + 1; + return new NoopTracer(); + } + } +}); diff --git a/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts b/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts index 9d15b0d12f..72a79e1dc8 100644 --- a/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts +++ b/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts @@ -18,19 +18,14 @@ import * as assert from 'assert'; import * as types from '@opentelemetry/types'; import { getTracer, - initGlobalTracer, + getTracerFactory, + initGlobalTracerFactory, } from '../../src/trace/globaltracer-utils'; -import { NoopTracer, NoopSpan } from '../../src'; +import { NoopTracer, NoopTracerFactory, NoopSpan } from '../../src'; import { TraceFlags } from '@opentelemetry/types'; describe('globaltracer-utils', () => { - const functions = [ - 'getCurrentSpan', - 'startSpan', - 'withSpan', - 'getBinaryFormat', - 'getHttpTextFormat', - ]; + const functions = ['getTracer']; it('should expose a tracer via getTracer', () => { const tracer = getTracer(); @@ -47,12 +42,12 @@ describe('globaltracer-utils', () => { const dummySpan = new NoopSpan(spanContext); afterEach(() => { - initGlobalTracer(new NoopTracer()); + initGlobalTracerFactory(new NoopTracerFactory()); }); - it('should not crash', () => { + it('getTracer should not crash', () => { functions.forEach(fn => { - const tracer = getTracer(); + const tracer = getTracerFactory(); try { ((tracer as unknown) as { [fn: string]: Function })[fn](); // Try to run the function assert.ok(true, fn); @@ -65,11 +60,19 @@ describe('globaltracer-utils', () => { }); it('should use the global tracer', () => { - const tracer = initGlobalTracer(new TestTracer()); - const span = tracer.startSpan('test'); + const tracerFactory = initGlobalTracerFactory(new TestTracerFactory()); + const span = tracerFactory.getTracer('fake').startSpan('test'); assert.deepStrictEqual(span, dummySpan); }); + class TestTracerFactory extends NoopTracerFactory { + private readonly _testTracer: types.Tracer = new TestTracer(); + + getTracer(name?: string, version?: string) { + return this._testTracer; + } + } + class TestTracer extends NoopTracer { startSpan( name: string, diff --git a/packages/opentelemetry-node/README.md b/packages/opentelemetry-node/README.md index 8a24c5b0d7..e848de9fb0 100644 --- a/packages/opentelemetry-node/README.md +++ b/packages/opentelemetry-node/README.md @@ -57,7 +57,7 @@ const opentelemetry = require('@opentelemetry/core'); const { NodeTracer } = require('@opentelemetry/node'); // Create and configure NodeTracer -const tracer = new NodeTracer({ +const tracerFactory = new NodeTracerFactory({ plugins: { http: { enabled: true, @@ -69,7 +69,7 @@ const tracer = new NodeTracer({ }); // Initialize the tracer -opentelemetry.initGlobalTracer(tracer); +opentelemetry.initGlobalTracerFactory(tracerFactory); // Your application code - http will automatically be instrumented if // @opentelemetry/plugin-http is present @@ -80,13 +80,13 @@ To enable instrumentation for all [supported modules](https://github.com/open-te ```js const opentelemetry = require('@opentelemetry/core'); -const { NodeTracer } = require('@opentelemetry/node'); +const { NodeTracerFactory} = require('@opentelemetry/node'); -// Create and initialize NodeTracer -const tracer = new NodeTracer(); +// Create and initialize NodeTracerFactory +const tracerFactory = new NodeTracerFactory(); // Initialize the tracer -opentelemetry.initGlobalTracer(tracer); +opentelemetry.initGlobalTracerFactory(tracerFactory); // Your application code // ... diff --git a/packages/opentelemetry-node/src/NodeTracerFactory.ts b/packages/opentelemetry-node/src/NodeTracerFactory.ts new file mode 100644 index 0000000000..56425a3e92 --- /dev/null +++ b/packages/opentelemetry-node/src/NodeTracerFactory.ts @@ -0,0 +1,32 @@ +/*! + * 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 { NodeTracer } from './NodeTracer'; +import { NodeTracerConfig } from './config'; +import { BasicTracerFactory } from '@opentelemetry/tracing'; + +export class NodeTracerFactory extends BasicTracerFactory { + private readonly _nodeConfig?: NodeTracerConfig; + + constructor(config?: NodeTracerConfig) { + super(); + this._nodeConfig = config; + } + + protected _newTracer(): NodeTracer { + return new NodeTracer(this._nodeConfig); + } +} diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts index 6517aafec9..90d41bf8af 100644 --- a/packages/opentelemetry-node/src/index.ts +++ b/packages/opentelemetry-node/src/index.ts @@ -15,3 +15,4 @@ */ export * from './NodeTracer'; +export * from './NodeTracerFactory'; diff --git a/packages/opentelemetry-tracing/README.md b/packages/opentelemetry-tracing/README.md index 8b29052a43..b1c9e6c639 100644 --- a/packages/opentelemetry-tracing/README.md +++ b/packages/opentelemetry-tracing/README.md @@ -25,16 +25,16 @@ npm install --save @opentelemetry/tracing ```js const opentelemetry = require('@opentelemetry/core'); -const { BasicTracer } = require('@opentelemetry/tracing'); +const { BasicTracerFactory } = require('@opentelemetry/tracing'); -// To start a trace, you first need to initialize the Tracer. +// To start a trace, you first need to initialize the Tracer factory. // NOTE: the default OpenTelemetry tracer does not record any tracing information. -const tracer = new BasicTracer(); +const factory = new BasicTracerFactory(); -// Initialize the OpenTelemetry APIs to use the BasicTracer bindings -opentelemetry.initGlobalTracer(tracer); +// Initialize the OpenTelemetry APIs to use the BasicTracerFactory bindings +opentelemetry.initGlobalTracerFactory(factory); -// To create a span in a trace, we used the global singleton tracer to start a new span. +// To create a span in a trace, we use the global tracer factory to start a new span. const span = opentelemetry.getTracer().startSpan('foo'); // Create an Attributes diff --git a/packages/opentelemetry-tracing/src/BasicTracerFactory.ts b/packages/opentelemetry-tracing/src/BasicTracerFactory.ts new file mode 100644 index 0000000000..71d01cd991 --- /dev/null +++ b/packages/opentelemetry-tracing/src/BasicTracerFactory.ts @@ -0,0 +1,54 @@ +/*! + * 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 { AbstractTracerFactory } from '@opentelemetry/core'; +import { BasicTracer } from './BasicTracer'; +import { BasicTracerConfig } from './types'; +import { SpanProcessor } from './SpanProcessor'; + +export class BasicTracerFactory extends AbstractTracerFactory { + private _config?: BasicTracerConfig; + private _spanProcessors: SpanProcessor[] = []; + + constructor(config?: BasicTracerConfig) { + super(); + this._config = config; + } + + /** + * addSpanProcessor adds a {@link SpanProcessor} to the factory, applying it + * to all new and existing tracers. + */ + addSpanProcessor(spanProcessor: SpanProcessor): void { + this._spanProcessors.push(spanProcessor); + for (const tracer of this._tracers.values()) { + tracer.addSpanProcessor(spanProcessor); + } + } + + getTracer(name: string = '', version?: string): BasicTracer { + const tracer = super.getTracer(name, version) as BasicTracer; + for (const processor of this._spanProcessors) { + tracer.addSpanProcessor(processor); + } + + return tracer; + } + + protected _newTracer(): BasicTracer { + return new BasicTracer(this._config); + } +} diff --git a/packages/opentelemetry-tracing/src/index.ts b/packages/opentelemetry-tracing/src/index.ts index 55a68cbde6..daf88bc81e 100644 --- a/packages/opentelemetry-tracing/src/index.ts +++ b/packages/opentelemetry-tracing/src/index.ts @@ -15,6 +15,7 @@ */ export * from './BasicTracer'; +export * from './BasicTracerFactory'; export * from './export/ConsoleSpanExporter'; export * from './export/BatchSpanProcessor'; export * from './export/InMemorySpanExporter'; diff --git a/packages/opentelemetry-types/src/index.ts b/packages/opentelemetry-types/src/index.ts index 6e90dd1690..4286aaf7b4 100644 --- a/packages/opentelemetry-types/src/index.ts +++ b/packages/opentelemetry-types/src/index.ts @@ -35,5 +35,6 @@ export * from './trace/span_kind'; export * from './trace/status'; export * from './trace/TimedEvent'; export * from './trace/tracer'; +export * from './trace/TracerFactory'; export * from './trace/trace_flags'; export * from './trace/trace_state'; diff --git a/packages/opentelemetry-types/src/trace/TracerFactory.ts b/packages/opentelemetry-types/src/trace/TracerFactory.ts new file mode 100644 index 0000000000..b5ab8f14ab --- /dev/null +++ b/packages/opentelemetry-types/src/trace/TracerFactory.ts @@ -0,0 +1,30 @@ +/*! + * 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 { Tracer } from './tracer'; + +/** + * TracerFactory is an object that creates a new {@link Tracer}. + */ +export interface TracerFactory { + /** + * Finds or creates a tracer for the named instrumentation library. If the name + * is empty or null, a default tracer is returned. + * @param name must identify the instrumentation library. + * @param [version] is the semantic version of the instrumentation library. + */ + getTracer(name: string, version?: string): Tracer; +} diff --git a/packages/opentelemetry-web/src/WebTracerFactory.ts b/packages/opentelemetry-web/src/WebTracerFactory.ts new file mode 100644 index 0000000000..a70531e05a --- /dev/null +++ b/packages/opentelemetry-web/src/WebTracerFactory.ts @@ -0,0 +1,34 @@ +/*! + * 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 { WebTracer, WebTracerConfig } from './WebTracer'; +import { BasicTracerFactory } from '@opentelemetry/tracing'; + +/** + * WebTracerFactory produces named tracers. + */ +export class WebTracerFactory extends BasicTracerFactory { + private readonly _webConfig?: WebTracerConfig; + + constructor(config?: WebTracerConfig) { + super(); + this._webConfig = config; + } + + protected _newTracer(): WebTracer { + return new WebTracer(this._webConfig); + } +}