Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: use semver to determine API compatibility #1772

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/opentelemetry-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@
"access": "public"
},
"dependencies": {
"@opentelemetry/context-base": "^0.14.0"
"@opentelemetry/context-base": "^0.14.0",
"semver": "^7.3.2"
},
"devDependencies": {
"@types/mocha": "8.2.0",
"@types/node": "14.14.12",
"@types/semver": "7.3.4",
"@types/sinon": "9.0.9",
"@types/webpack-env": "1.16.0",
"codecov": "3.8.1",
Expand Down
34 changes: 15 additions & 19 deletions packages/opentelemetry-api/src/api/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import {
NoopContextManager,
} from '@opentelemetry/context-base';
import {
API_BACKWARDS_COMPATIBILITY_VERSION,
GLOBAL_CONTEXT_MANAGER_API_KEY,
makeGetter,
_global,
getGlobal,
isCompatible,
registerGlobal,
unregisterGlobal,
} from './global-utils';

const NOOP_CONTEXT_MANAGER = new NoopContextManager();
Expand Down Expand Up @@ -52,17 +52,11 @@ export class ContextAPI {
public setGlobalContextManager(
contextManager: ContextManager
): ContextManager {
if (_global[GLOBAL_CONTEXT_MANAGER_API_KEY]) {
// global context manager has already been set
return this._getContextManager();
if (getGlobal('context')) {
throw new Error('Attempted to set global Context Manager multiple times');
}

_global[GLOBAL_CONTEXT_MANAGER_API_KEY] = makeGetter(
API_BACKWARDS_COMPATIBILITY_VERSION,
contextManager,
NOOP_CONTEXT_MANAGER
);

registerGlobal('context', contextManager);
return contextManager;
}

Expand Down Expand Up @@ -97,16 +91,18 @@ export class ContextAPI {
}

private _getContextManager(): ContextManager {
return (
_global[GLOBAL_CONTEXT_MANAGER_API_KEY]?.(
API_BACKWARDS_COMPATIBILITY_VERSION
) ?? NOOP_CONTEXT_MANAGER
);
const signal = getGlobal('context');

if (signal && isCompatible(signal.version)) {
return signal.instance;
}

return NOOP_CONTEXT_MANAGER;
}

/** Disable and remove the global context manager */
public disable() {
this._getContextManager().disable();
delete _global[GLOBAL_CONTEXT_MANAGER_API_KEY];
unregisterGlobal('context');
}
}
104 changes: 62 additions & 42 deletions packages/opentelemetry-api/src/api/global-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,74 @@
*/

import { ContextManager } from '@opentelemetry/context-base';
import * as semver from 'semver';
import { TextMapPropagator } from '../context/propagation/TextMapPropagator';
import { MeterProvider } from '../metrics/MeterProvider';
import { TracerProvider } from '../trace/tracer_provider';
import { _globalThis } from '../platform';
import { TracerProvider } from '../trace/tracer_provider';
import { VERSION } from '../version';

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');
const _global = _globalThis as OTelGlobal;
const acceptableRange = new semver.Range(`^${VERSION}`);
const GLOBAL_OPENTELEMETRY_API_KEY = Symbol.for('io.opentelemetry.js.api');

type Get<T> = (version: number) => T;
type OtelGlobal = Partial<{
[GLOBAL_CONTEXT_MANAGER_API_KEY]: Get<ContextManager>;
[GLOBAL_METRICS_API_KEY]: Get<MeterProvider>;
[GLOBAL_PROPAGATION_API_KEY]: Get<TextMapPropagator>;
[GLOBAL_TRACE_API_KEY]: Get<TracerProvider>;
}>;
export function registerGlobal(type: 'trace', instance: TracerProvider): void;
export function registerGlobal(type: 'metrics', instance: MeterProvider): void;
export function registerGlobal(type: 'context', instance: ContextManager): void;
export function registerGlobal(
type: 'propagation',
instance: TextMapPropagator
): void;
export function registerGlobal(type: keyof OTelGlobalApi, instance: any) {
_global[GLOBAL_OPENTELEMETRY_API_KEY] =
_global[GLOBAL_OPENTELEMETRY_API_KEY] ?? {};

export const _global = _globalThis as OtelGlobal;
const api = _global[GLOBAL_OPENTELEMETRY_API_KEY]!;
if (api[type]) {
// already registered an API of this type
return;
}

/**
* Make a function which accepts a version integer and returns the instance of an API if the version
* is compatible, or a fallback version (usually NOOP) if it is not.
*
* @param requiredVersion Backwards compatibility version which is required to return the instance
* @param instance Instance which should be returned if the required version is compatible
* @param fallback Fallback instance, usually NOOP, which will be returned if the required version is not compatible
*/
export function makeGetter<T>(
requiredVersion: number,
instance: T,
fallback: T
): Get<T> {
return (version: number): T =>
version === requiredVersion ? instance : fallback;
api[type] = {
instance,
version: VERSION,
};
}

/**
* A number which should be incremented each time a backwards incompatible
* change is made to the API. This number is used when an API package
* attempts to access the global API to ensure it is getting a compatible
* version. If the global API is not compatible with the API package
* attempting to get it, a NOOP API implementation will be returned.
*/
export const API_BACKWARDS_COMPATIBILITY_VERSION = 3;
export function getGlobal(type: 'trace'): Signal<TracerProvider> | undefined;
export function getGlobal(type: 'metrics'): Signal<MeterProvider> | undefined;
export function getGlobal(type: 'context'): Signal<ContextManager> | undefined;
export function getGlobal(
type: 'propagation'
): Signal<TextMapPropagator> | undefined;
export function getGlobal(type: keyof OTelGlobalApi) {
return _global[GLOBAL_OPENTELEMETRY_API_KEY]?.[type];
}

export function unregisterGlobal(type: keyof OTelGlobalApi) {
const api = _global[GLOBAL_OPENTELEMETRY_API_KEY];

if (api) {
delete api[type];
}
}

export function isCompatible(version: string) {
return semver.satisfies(version, acceptableRange);
}

type OTelGlobal = Partial<{
[GLOBAL_OPENTELEMETRY_API_KEY]: OTelGlobalApi;
}>;

type OTelGlobalApi = Partial<{
trace: Signal<TracerProvider>;
metrics: Signal<MeterProvider>;
context: Signal<ContextManager>;
propagation: Signal<TextMapPropagator>;
}>;

type Signal<T> = {
instance: T;
version: string;
};
33 changes: 15 additions & 18 deletions packages/opentelemetry-api/src/api/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import { Meter } from '../metrics/Meter';
import { MeterProvider } from '../metrics/MeterProvider';
import { NOOP_METER_PROVIDER } from '../metrics/NoopMeterProvider';
import {
API_BACKWARDS_COMPATIBILITY_VERSION,
GLOBAL_METRICS_API_KEY,
makeGetter,
_global,
getGlobal,
isCompatible,
registerGlobal,
unregisterGlobal,
} from './global-utils';

/**
Expand All @@ -46,28 +46,25 @@ export class MetricsAPI {
* Set the current global meter. Returns the initialized global meter provider.
*/
public setGlobalMeterProvider(provider: MeterProvider): MeterProvider {
if (_global[GLOBAL_METRICS_API_KEY]) {
// global meter provider has already been set
return this.getMeterProvider();
if (getGlobal('metrics')) {
throw new Error('Attempted to set global Meter Provider multiple times');
}

_global[GLOBAL_METRICS_API_KEY] = makeGetter(
API_BACKWARDS_COMPATIBILITY_VERSION,
provider,
NOOP_METER_PROVIDER
);

registerGlobal('metrics', provider);
return provider;
}

/**
* Returns the global meter provider.
*/
public getMeterProvider(): MeterProvider {
return (
_global[GLOBAL_METRICS_API_KEY]?.(API_BACKWARDS_COMPATIBILITY_VERSION) ??
NOOP_METER_PROVIDER
);
const signal = getGlobal('metrics');

if (signal && isCompatible(signal.version)) {
return signal.instance;
}

return NOOP_METER_PROVIDER;
}

/**
Expand All @@ -79,6 +76,6 @@ export class MetricsAPI {

/** Remove the global meter provider */
public disable() {
delete _global[GLOBAL_METRICS_API_KEY];
unregisterGlobal('metrics');
}
}
33 changes: 15 additions & 18 deletions packages/opentelemetry-api/src/api/propagation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import {
TextMapSetter,
} from '../context/propagation/TextMapPropagator';
import {
API_BACKWARDS_COMPATIBILITY_VERSION,
GLOBAL_PROPAGATION_API_KEY,
makeGetter,
_global,
getGlobal,
isCompatible,
registerGlobal,
unregisterGlobal,
} from './global-utils';

/**
Expand All @@ -52,16 +52,11 @@ export class PropagationAPI {
* Set the current propagator. Returns the initialized propagator
*/
public setGlobalPropagator(propagator: TextMapPropagator): TextMapPropagator {
if (_global[GLOBAL_PROPAGATION_API_KEY]) {
// global propagator has already been set
return this._getGlobalPropagator();
if (getGlobal('propagation')) {
throw new Error('Attempted to set global Propagator multiple times');
}

_global[GLOBAL_PROPAGATION_API_KEY] = makeGetter(
API_BACKWARDS_COMPATIBILITY_VERSION,
propagator,
NOOP_TEXT_MAP_PROPAGATOR
);
registerGlobal('propagation', propagator);

return propagator;
}
Expand Down Expand Up @@ -98,14 +93,16 @@ export class PropagationAPI {

/** Remove the global propagator */
public disable() {
delete _global[GLOBAL_PROPAGATION_API_KEY];
unregisterGlobal('propagation');
}

private _getGlobalPropagator(): TextMapPropagator {
return (
_global[GLOBAL_PROPAGATION_API_KEY]?.(
API_BACKWARDS_COMPATIBILITY_VERSION
) ?? NOOP_TEXT_MAP_PROPAGATOR
);
const signal = getGlobal('propagation');

if (signal && isCompatible(signal.version)) {
return signal.instance;
}

return NOOP_TEXT_MAP_PROPAGATOR;
}
}
36 changes: 16 additions & 20 deletions packages/opentelemetry-api/src/api/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
* limitations under the License.
*/

import { NOOP_TRACER_PROVIDER } from '../trace/NoopTracerProvider';
import { ProxyTracerProvider } from '../trace/ProxyTracerProvider';
import { Tracer } from '../trace/tracer';
import { TracerProvider } from '../trace/tracer_provider';
import { isSpanContextValid } from '../trace/spancontext-utils';
import {
API_BACKWARDS_COMPATIBILITY_VERSION,
GLOBAL_TRACE_API_KEY,
makeGetter,
_global,
getGlobal,
isCompatible,
registerGlobal,
unregisterGlobal,
} from './global-utils';

/**
Expand All @@ -50,30 +49,27 @@ export class TraceAPI {
* Set the current global tracer. Returns the initialized global tracer provider
*/
public setGlobalTracerProvider(provider: TracerProvider): TracerProvider {
if (_global[GLOBAL_TRACE_API_KEY]) {
// global tracer provider has already been set
return this.getTracerProvider();
if (getGlobal('trace')) {
throw new Error('Attempted to set global Tracer Provider multiple times');
}

this._proxyTracerProvider.setDelegate(provider);
registerGlobal('trace', this._proxyTracerProvider);

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

return this.getTracerProvider();
return this._proxyTracerProvider;
}

/**
* Returns the global tracer provider.
*/
public getTracerProvider(): TracerProvider {
return (
_global[GLOBAL_TRACE_API_KEY]?.(API_BACKWARDS_COMPATIBILITY_VERSION) ??
this._proxyTracerProvider
);
const traceSignal = getGlobal('trace');

if (traceSignal && isCompatible(traceSignal.version)) {
return traceSignal.instance;
}

return this._proxyTracerProvider;
}

/**
Expand All @@ -85,7 +81,7 @@ export class TraceAPI {

/** Remove the global tracer provider */
public disable() {
delete _global[GLOBAL_TRACE_API_KEY];
unregisterGlobal('trace');
this._proxyTracerProvider = new ProxyTracerProvider();
}

Expand Down
Loading