UNPKG

@fiberplane/hono-otel

Version:

Hono middleware to forward OpenTelemetry traces to a local instance of @fiberplane/studio

196 lines (195 loc) 7.88 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. */ import { EventEmitter } from "node:events"; const ADD_LISTENER_METHODS = [ "addListener", "on", "once", "prependListener", "prependOnceListener", ]; export class AbstractAsyncHooksContextManager { constructor() { this._kOtListeners = Symbol("OtListeners"); this._wrapped = false; } /** * 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 (target instanceof EventEmitter) { return this._bindEventEmitter(context, target); } if (typeof target === "function") { return this._bindFunction(context, target); } return target; } // biome-ignore lint/complexity/noBannedTypes: this is from the original code _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, }); /** * It isn't possible to tell Typescript that contextWrapper is the same as T * so we forced to cast as any here. */ // biome-ignore lint/suspicious/noExplicitAny: this is from the original code return contextWrapper; } /** * By default, EventEmitter call their callback with their context, which we do * not want, instead we will bind a specific context to all callbacks that * go through it. * @param context the context we want to bind * @param ee EventEmitter an instance of EventEmitter to patch */ _bindEventEmitter(context, ee) { const map = this._getPatchMap(ee); if (map !== undefined) { return ee; } this._createPatchMap(ee); // patch methods that add a listener to propagate context // biome-ignore lint/complexity/noForEach: this is from the original code ADD_LISTENER_METHODS.forEach((methodName) => { if (ee[methodName] === undefined) { return; } ee[methodName] = this._patchAddListener(ee, ee[methodName], context); }); // patch methods that remove a listener if (typeof ee.removeListener === "function") { ee.removeListener = this._patchRemoveListener(ee, ee.removeListener); } if (typeof ee.off === "function") { ee.off = this._patchRemoveListener(ee, ee.off); } // patch method that remove all listeners if (typeof ee.removeAllListeners === "function") { ee.removeAllListeners = this._patchRemoveAllListeners(ee, ee.removeAllListeners); } return ee; } /** * Patch methods that remove a given listener so that we match the "patched" * version of that listener (the one that propagate context). * @param ee EventEmitter instance * @param original reference to the patched method */ // biome-ignore lint/complexity/noBannedTypes: this is from the original code _patchRemoveListener(ee, original) { const contextManager = this; return function (event, listener) { const events = contextManager._getPatchMap(ee)?.[event]; if (events === undefined) { return original.call(this, event, listener); } const patchedListener = events.get(listener); return original.call(this, event, patchedListener || listener); }; } /** * Patch methods that remove all listeners so we remove our * internal references for a given event. * @param ee EventEmitter instance * @param original reference to the patched method */ // biome-ignore lint/complexity/noBannedTypes: this is from the original code _patchRemoveAllListeners(ee, original) { const contextManager = this; return function (event) { const map = contextManager._getPatchMap(ee); if (map !== undefined) { // biome-ignore lint/style/noArguments: this is from the original code if (arguments.length === 0) { contextManager._createPatchMap(ee); } else if (map[event] !== undefined) { delete map[event]; } } // biome-ignore lint/style/noArguments: this is from the original code return original.apply(this, arguments); }; } /** * Patch methods on an event emitter instance that can add listeners so we * can force them to propagate a given context. * @param ee EventEmitter instance * @param original reference to the patched method * @param [context] context to propagate when calling listeners */ _patchAddListener(ee, // biome-ignore lint/complexity/noBannedTypes: this is from the original code original, context) { const contextManager = this; return function (event, listener) { /** * This check is required to prevent double-wrapping the listener. * The implementation for ee.once wraps the listener and calls ee.on. * Without this check, we would wrap that wrapped listener. * This causes an issue because ee.removeListener depends on the onceWrapper * to properly remove the listener. If we wrap their wrapper, we break * that detection. */ if (contextManager._wrapped) { return original.call(this, event, listener); } let map = contextManager._getPatchMap(ee); if (map === undefined) { map = contextManager._createPatchMap(ee); } let listeners = map[event]; if (listeners === undefined) { listeners = new WeakMap(); map[event] = listeners; } const patchedListener = contextManager.bind(context, listener); // store a weak reference of the user listener to ours listeners.set(listener, patchedListener); /** * See comment at the start of this function for the explanation of this property. */ contextManager._wrapped = true; try { return original.call(this, event, patchedListener); } finally { contextManager._wrapped = false; } }; } _createPatchMap(ee) { const map = Object.create(null); // biome-ignore lint/suspicious/noExplicitAny: this is from the original code ee[this._kOtListeners] = map; return map; } _getPatchMap(ee) { return ee[this._kOtListeners]; } }