UNPKG

@opentelemetry/context-zone-peer-dep

Version:
211 lines 7.89 kB
/* * 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. */ /// <reference types="zone.js" /> import { ROOT_CONTEXT } from '@opentelemetry/api'; import { isListenerObject } from './util'; /* Key name to be used to save a context reference in Zone */ const ZONE_CONTEXT_KEY = 'OT_ZONE_CONTEXT'; /** * ZoneContextManager * This module provides an easy functionality for tracing action between asynchronous operations in web. * It was not possible with standard [StackContextManager]{@link https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-web/src/StackContextManager.ts}. * It heavily depends on [zone.js]{@link https://www.npmjs.com/package/zone.js}. * It stores the information about context in zone. Each Context will have always new Zone; * It also supports binding a certain Span to a target that has "addEventListener" and "removeEventListener". * When this happens a new zone is being created and the provided Span is being assigned to this zone. */ export class ZoneContextManager { /** * whether the context manager is enabled or not */ _enabled = false; /** * Helps to create a unique name for the zones - part of zone name */ _zoneCounter = 0; /** * Returns the active context from certain zone name * @param activeZone */ _activeContextFromZone(activeZone) { return (activeZone && activeZone.get(ZONE_CONTEXT_KEY)) || ROOT_CONTEXT; } /** * @param context A context (span) to be executed within target function * @param target Function to be executed within the context */ // eslint-disable-next-line @typescript-eslint/ban-types _bindFunction(context, target) { const manager = this; const contextWrapper = function (...args) { return manager.with(context, () => target.apply(this, args)); }; Object.defineProperty(contextWrapper, 'length', { enumerable: false, configurable: true, writable: false, value: target.length, }); return contextWrapper; } /** * @param context A context (span) to be bind to target * @param obj target object on which the listeners will be patched */ _bindListener(context, obj) { const target = obj; if (target.__ot_listeners !== undefined) { return obj; } target.__ot_listeners = {}; if (typeof target.addEventListener === 'function') { target.addEventListener = this._patchAddEventListener(target, target.addEventListener, context); } if (typeof target.removeEventListener === 'function') { target.removeEventListener = this._patchRemoveEventListener(target, target.removeEventListener); } return obj; } /** * Creates a new unique zone name */ _createZoneName() { this._zoneCounter++; const random = Math.random(); return `${this._zoneCounter}-${random}`; } /** * Creates a new zone * @param zoneName zone name * @param context A context (span) to be bind with Zone */ _createZone(zoneName, context) { return Zone.current.fork({ name: zoneName, properties: { [ZONE_CONTEXT_KEY]: context, }, }); } /** * Returns the active zone */ _getActiveZone() { return Zone.current; } /** * Patches addEventListener method * @param target any target that has "addEventListener" method * @param original reference to the patched method * @param [context] context to be bind to the listener */ _patchAddEventListener(target, original, context) { const contextManager = this; return function (event, listener, opts) { if (target.__ot_listeners === undefined) { target.__ot_listeners = {}; } let listeners = target.__ot_listeners[event]; if (listeners === undefined) { listeners = new WeakMap(); target.__ot_listeners[event] = listeners; } const patchedListener = contextManager.bind(context, listener); // store a weak reference of the user listener to ours listeners.set(listener, patchedListener); return original.call(this, event, patchedListener, opts); }; } /** * Patches removeEventListener method * @param target any target that has "removeEventListener" method * @param original reference to the patched method */ _patchRemoveEventListener(target, original) { return function (event, listener) { if (target.__ot_listeners === undefined || target.__ot_listeners[event] === undefined) { return original.call(this, event, listener); } const events = target.__ot_listeners[event]; const patchedListener = events.get(listener); events.delete(listener); return original.call(this, event, patchedListener || listener); }; } /** * Returns the active context */ active() { if (!this._enabled) { return ROOT_CONTEXT; } const activeZone = this._getActiveZone(); const active = this._activeContextFromZone(activeZone); if (active) { return active; } return ROOT_CONTEXT; } /** * Binds a the certain context or the active one to the target function and then returns the target * @param context A context (span) to be bind to target * @param target a function or event emitter. When target or one of its callbacks is called, * the provided context will be used as the active context for the duration of the call. */ bind(context, target) { // if no specific context to propagate is given, we use the current one if (context === undefined) { context = this.active(); } if (typeof target === 'function') { return this._bindFunction(context, target); } else if (isListenerObject(target)) { this._bindListener(context, target); } return target; } /** * Disable the context manager (clears all the contexts) */ disable() { this._enabled = false; return this; } /** * Enables the context manager and creates a default(root) context */ enable() { this._enabled = true; return this; } /** * Calls the callback function [fn] with the provided [context]. * If [context] is undefined then it will use the active context. * The context will be set as active * @param context A context (span) to be called with provided callback * @param fn Callback function * @param thisArg optional receiver to be used for calling fn * @param args optional arguments forwarded to fn */ with(context, fn, thisArg, ...args) { const zoneName = this._createZoneName(); const newZone = this._createZone(zoneName, context); return newZone.run(fn, thisArg, args); } } //# sourceMappingURL=ZoneContextManager.js.map