diff --git a/packages/opentelemetry-api/src/api/context.ts b/packages/opentelemetry-api/src/api/context.ts index 1044311b3fe..741677c3e04 100644 --- a/packages/opentelemetry-api/src/api/context.ts +++ b/packages/opentelemetry-api/src/api/context.ts @@ -15,16 +15,17 @@ */ import { + Context, ContextManager, NoopContextManager, - Context, } from '@opentelemetry/context-base'; +import { + GLOBAL_CONTEXT_MANAGER_API_KEY, + makeGetter, + _global, +} from './global-utils'; const NOOP_CONTEXT_MANAGER = new NoopContextManager(); - -const GLOBAL_CONTEXT_MANAGER_API_KEY = Symbol.for( - 'io.opentelemetry.js.api.context' -); const API_VERSION = 0; /** @@ -51,20 +52,16 @@ export class ContextAPI { public setGlobalContextManager( contextManager: ContextManager ): ContextManager { - if ((global as any)[GLOBAL_CONTEXT_MANAGER_API_KEY]) { + if (_global[GLOBAL_CONTEXT_MANAGER_API_KEY]) { // global context manager has already been set return NOOP_CONTEXT_MANAGER; } - (global as any)[GLOBAL_CONTEXT_MANAGER_API_KEY] = function getTraceApi( - version: number - ) { - if (version !== API_VERSION) { - return NOOP_CONTEXT_MANAGER; - } - - return contextManager; - }; + _global[GLOBAL_CONTEXT_MANAGER_API_KEY] = makeGetter( + API_VERSION, + contextManager, + NOOP_CONTEXT_MANAGER + ); return contextManager; } @@ -100,16 +97,16 @@ export class ContextAPI { } private _getContextManager(): ContextManager { - if (!(global as any)[GLOBAL_CONTEXT_MANAGER_API_KEY]) { + if (!_global[GLOBAL_CONTEXT_MANAGER_API_KEY]) { return NOOP_CONTEXT_MANAGER; } - return (global as any)[GLOBAL_CONTEXT_MANAGER_API_KEY](API_VERSION); + return _global[GLOBAL_CONTEXT_MANAGER_API_KEY]!(API_VERSION); } /** Disable and remove the global context manager */ public disable() { this._getContextManager().disable(); - delete (global as any)[GLOBAL_CONTEXT_MANAGER_API_KEY]; + delete _global[GLOBAL_CONTEXT_MANAGER_API_KEY]; } } diff --git a/packages/opentelemetry-api/src/api/global-utils.ts b/packages/opentelemetry-api/src/api/global-utils.ts new file mode 100644 index 00000000000..79a77fd2222 --- /dev/null +++ b/packages/opentelemetry-api/src/api/global-utils.ts @@ -0,0 +1,50 @@ +/*! + * Copyright 2020, 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 { ContextManager } from '@opentelemetry/context-base'; +import { HttpTextPropagator } from '../context/propagation/HttpTextPropagator'; +import { MeterProvider } from '../metrics/MeterProvider'; +import { TracerProvider } from '../trace/tracer_provider'; + +export const GLOBAL_CONTEXT_MANAGER_API_KEY = Symbol.for( + 'io.opentelemetry.js.api.context' +); +export const GLOBAL_METRICS_API_KEY = Symbol.for( + 'io.opentelemetry.js.api.metrics' +); +export const GLOBAL_PROPAGATION_API_KEY = Symbol.for( + 'io.opentelemetry.js.api.propagation' +); +export const GLOBAL_TRACE_API_KEY = Symbol.for('io.opentelemetry.js.api.trace'); + +type Get = (version: number) => T; +type MyGlobals = Partial<{ + [GLOBAL_CONTEXT_MANAGER_API_KEY]: Get; + [GLOBAL_METRICS_API_KEY]: Get; + [GLOBAL_PROPAGATION_API_KEY]: Get; + [GLOBAL_TRACE_API_KEY]: Get; +}>; + +export const _global = global as typeof global & MyGlobals; + +export function makeGetter( + requiredVersion: number, + instance: T, + fallback: T +): Get { + return (version: number): T => + version === requiredVersion ? instance : fallback; +} diff --git a/packages/opentelemetry-api/src/api/metrics.ts b/packages/opentelemetry-api/src/api/metrics.ts index 0659d90e282..6677b9f9e4e 100644 --- a/packages/opentelemetry-api/src/api/metrics.ts +++ b/packages/opentelemetry-api/src/api/metrics.ts @@ -17,8 +17,8 @@ import { Meter } from '../metrics/Meter'; import { MeterProvider } from '../metrics/MeterProvider'; import { NOOP_METER_PROVIDER } from '../metrics/NoopMeterProvider'; +import { GLOBAL_METRICS_API_KEY, makeGetter, _global } from './global-utils'; -const GLOBAL_METRICS_API_KEY = Symbol.for('io.opentelemetry.js.api.metrics'); const API_VERSION = 0; /** @@ -43,20 +43,16 @@ export class MetricsAPI { * Set the current global meter. Returns the initialized global meter provider. */ public setGlobalMeterProvider(provider: MeterProvider): MeterProvider { - if ((global as any)[GLOBAL_METRICS_API_KEY]) { + if (_global[GLOBAL_METRICS_API_KEY]) { // global meter provider has already been set return NOOP_METER_PROVIDER; } - (global as any)[GLOBAL_METRICS_API_KEY] = function getTraceApi( - version: number - ) { - if (version !== API_VERSION) { - return NOOP_METER_PROVIDER; - } - - return provider; - }; + _global[GLOBAL_METRICS_API_KEY] = makeGetter( + API_VERSION, + provider, + NOOP_METER_PROVIDER + ); return provider; } @@ -65,11 +61,11 @@ export class MetricsAPI { * Returns the global meter provider. */ public getMeterProvider(): MeterProvider { - if (!(global as any)[GLOBAL_METRICS_API_KEY]) { + if (!_global[GLOBAL_METRICS_API_KEY]) { return NOOP_METER_PROVIDER; } - return (global as any)[GLOBAL_METRICS_API_KEY](API_VERSION); + return _global[GLOBAL_METRICS_API_KEY]!(API_VERSION); } /** @@ -81,6 +77,6 @@ export class MetricsAPI { /** Remove the global meter provider */ public disable() { - delete (global as any)[GLOBAL_METRICS_API_KEY]; + delete _global[GLOBAL_METRICS_API_KEY]; } } diff --git a/packages/opentelemetry-api/src/api/propagation.ts b/packages/opentelemetry-api/src/api/propagation.ts index a07a66ea15b..efef42441f8 100644 --- a/packages/opentelemetry-api/src/api/propagation.ts +++ b/packages/opentelemetry-api/src/api/propagation.ts @@ -20,12 +20,14 @@ import { HttpTextPropagator } from '../context/propagation/HttpTextPropagator'; import { NOOP_HTTP_TEXT_PROPAGATOR } from '../context/propagation/NoopHttpTextPropagator'; import { defaultSetter, SetterFunction } from '../context/propagation/setter'; import { ContextAPI } from './context'; +import { + GLOBAL_PROPAGATION_API_KEY, + makeGetter, + _global, +} from './global-utils'; const contextApi = ContextAPI.getInstance(); -const GLOBAL_PROPAGATION_API_KEY = Symbol.for( - 'io.opentelemetry.js.api.propagation' -); const API_VERSION = 0; /** @@ -52,20 +54,16 @@ export class PropagationAPI { public setGlobalPropagator( propagator: HttpTextPropagator ): HttpTextPropagator { - if ((global as any)[GLOBAL_PROPAGATION_API_KEY]) { + if (_global[GLOBAL_PROPAGATION_API_KEY]) { // global propagator has already been set return NOOP_HTTP_TEXT_PROPAGATOR; } - (global as any)[GLOBAL_PROPAGATION_API_KEY] = function getTraceApi( - version: number - ) { - if (version !== API_VERSION) { - return NOOP_HTTP_TEXT_PROPAGATOR; - } - - return propagator; - }; + _global[GLOBAL_PROPAGATION_API_KEY] = makeGetter( + API_VERSION, + propagator, + NOOP_HTTP_TEXT_PROPAGATOR + ); return propagator; } @@ -102,14 +100,14 @@ export class PropagationAPI { /** Remove the global propagator */ public disable() { - delete (global as any)[GLOBAL_PROPAGATION_API_KEY]; + delete _global[GLOBAL_PROPAGATION_API_KEY]; } private _getGlobalPropagator(): HttpTextPropagator { - if (!(global as any)[GLOBAL_PROPAGATION_API_KEY]) { + if (!_global[GLOBAL_PROPAGATION_API_KEY]) { return NOOP_HTTP_TEXT_PROPAGATOR; } - return (global as any)[GLOBAL_PROPAGATION_API_KEY](API_VERSION); + return _global[GLOBAL_PROPAGATION_API_KEY]!(API_VERSION); } } diff --git a/packages/opentelemetry-api/src/api/trace.ts b/packages/opentelemetry-api/src/api/trace.ts index 827c1100fcf..4353cfd1f27 100644 --- a/packages/opentelemetry-api/src/api/trace.ts +++ b/packages/opentelemetry-api/src/api/trace.ts @@ -15,10 +15,10 @@ */ import { NOOP_TRACER_PROVIDER } from '../trace/NoopTracerProvider'; -import { TracerProvider } from '../trace/tracer_provider'; import { Tracer } from '../trace/tracer'; +import { TracerProvider } from '../trace/tracer_provider'; +import { GLOBAL_TRACE_API_KEY, makeGetter, _global } from './global-utils'; -const GLOBAL_TRACE_API_KEY = Symbol.for('io.opentelemetry.js.api.trace'); const API_VERSION = 0; /** @@ -43,20 +43,16 @@ export class TraceAPI { * Set the current global tracer. Returns the initialized global tracer provider */ public setGlobalTracerProvider(provider: TracerProvider): TracerProvider { - if ((global as any)[GLOBAL_TRACE_API_KEY]) { + if (_global[GLOBAL_TRACE_API_KEY]) { // global tracer provider has already been set return NOOP_TRACER_PROVIDER; } - (global as any)[GLOBAL_TRACE_API_KEY] = function getTraceApi( - version: number - ) { - if (version !== API_VERSION) { - return NOOP_TRACER_PROVIDER; - } - - return provider; - }; + _global[GLOBAL_TRACE_API_KEY] = makeGetter( + API_VERSION, + provider, + NOOP_TRACER_PROVIDER + ); return this.getTracerProvider(); } @@ -65,12 +61,12 @@ export class TraceAPI { * Returns the global tracer provider. */ public getTracerProvider(): TracerProvider { - if (!(global as any)[GLOBAL_TRACE_API_KEY]) { + if (!_global[GLOBAL_TRACE_API_KEY]) { // global tracer provider has already been set return NOOP_TRACER_PROVIDER; } - return (global as any)[GLOBAL_TRACE_API_KEY](API_VERSION); + return _global[GLOBAL_TRACE_API_KEY]!(API_VERSION); } /** @@ -82,6 +78,6 @@ export class TraceAPI { /** Remove the global tracer provider */ public disable() { - delete (global as any)[GLOBAL_TRACE_API_KEY]; + delete _global[GLOBAL_TRACE_API_KEY]; } } diff --git a/packages/opentelemetry-api/test/api/global.test.ts b/packages/opentelemetry-api/test/api/global.test.ts new file mode 100644 index 00000000000..5ff4e25a2bf --- /dev/null +++ b/packages/opentelemetry-api/test/api/global.test.ts @@ -0,0 +1,81 @@ +/*! + * Copyright 2020, 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 { NoopContextManager } from '@opentelemetry/context-base'; +import { + _global, + GLOBAL_CONTEXT_MANAGER_API_KEY, +} from '../../src/api/global-utils'; + +const api1 = require('../../src') as typeof import('../../src'); + +// clear cache and load a second instance of the api +for (const key of Object.keys(require.cache)) { + delete require.cache[key]; +} +const api2 = require('../../src') as typeof import('../../src'); + +describe('Global Utils', () => { + // prove they are separate instances + assert.notEqual(api1, api2); + // that return separate noop instances to start + assert.notStrictEqual( + api1.context['_getContextManager'](), + api2.context['_getContextManager']() + ); + + beforeEach(() => { + api1.context.disable(); + api1.propagation.disable(); + api1.trace.disable(); + api1.metrics.disable(); + }); + + it('should change the global context manager', () => { + const original = api1.context['_getContextManager'](); + const newContextManager = new NoopContextManager(); + api1.context.setGlobalContextManager(newContextManager); + assert.notStrictEqual(api1.context['_getContextManager'](), original); + assert.strictEqual(api1.context['_getContextManager'](), newContextManager); + }); + + it('should load an instance from one which was set in the other', () => { + api1.context.setGlobalContextManager(new NoopContextManager()); + assert.strictEqual( + api1.context['_getContextManager'](), + api2.context['_getContextManager']() + ); + }); + + it('should disable both if one is disabled', () => { + const original = api1.context['_getContextManager'](); + + api1.context.setGlobalContextManager(new NoopContextManager()); + + assert.notStrictEqual(original, api1.context['_getContextManager']()); + api2.context.disable(); + assert.strictEqual(original, api1.context['_getContextManager']()); + }); + + it('should return the module NoOp implementation if the version is a mismatch', () => { + const original = api1.context['_getContextManager'](); + api1.context.setGlobalContextManager(new NoopContextManager()); + const afterSet = _global[GLOBAL_CONTEXT_MANAGER_API_KEY]!(-1); + + assert.strictEqual(original, afterSet); + }); +});