UNPKG

libmodulor

Version:

A TypeScript library to create platform-agnostic applications

135 lines (134 loc) 5.26 kB
import { context, SpanStatusCode, trace } from '@opentelemetry/api'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, } from '@opentelemetry/instrumentation'; import { LIB_NAME } from '../convention.js'; const LIB_VERSION = ''; const LIB_MIN_VERSION = '*'; const NAME = `instrumentation-${LIB_NAME}`; const TRACABLES = { [LIB_NAME]: { SimpleUCManager: [ 'commitTx', 'confirmClient', 'execClient', 'execServer', 'initServer', 'persist', 'persistProjection', 'rollbackTx', ], UCExecChecker: ['exec'], UCInputFilesProcessor: ['exec'], UCInputValidator: ['exec'], UCOutputFilesProcessor: ['exec'], }, [`${LIB_NAME}/uc-data-store-knex`]: { KnexUCDataStore: [ 'clear', 'destroy', 'exists', 'init', 'initSync', 'read', 'readProjection', 'startTx', 'supportedSpecificBindings', 'testKey', 'write', 'writeBulk', 'writeProjection', ], }, }; export class InstrumentationOTL extends InstrumentationBase { constructor(config) { super(NAME, LIB_VERSION, config); } init() { const namespaces = Object.keys(TRACABLES); const defs = namespaces.map((namespace) => new InstrumentationNodeModuleDefinition(this.moduleName(namespace, this._config.libPath), [LIB_MIN_VERSION], (moduleExports) => this.wrapAll(moduleExports, namespace), (moduleExports) => this.unwrapAll(moduleExports, namespace))); return defs; } moduleName(namespace, libPath) { if (!libPath) { return namespace; // e.g. LIB_NAME, LIB_NAME/uc-data-store-knex } if (!namespace.includes('/')) { return libPath; // ../../../index.js } const moduleName = namespace.split('/')[1]; return libPath.replace('.js', `.${moduleName}.js`); // e.g. ../../../index.uc-data-store-knex.js } patchMethod(namespace, className, methodName) { return (original) => { const tracer = this.tracer; const spanName = `${className}.${methodName}`; // biome-ignore lint/suspicious/noExplicitAny: can be anything return function patched(...args) { const span = tracer.startSpan(spanName, { attributes: { 'scope.name': namespace, }, }); const ctx = trace.setSpan(context.active(), span); return context.with(ctx, () => { try { const res = original.apply(this, args); if (res instanceof Promise) { return res .then((value) => { span.setStatus({ code: SpanStatusCode.OK }); return value; }) .catch((err) => { span.recordException(err); span.setStatus({ code: SpanStatusCode.ERROR, message: err.message, }); throw err; }) .finally(() => { span.end(); }); } span.setStatus({ code: SpanStatusCode.OK }); span.end(); return res; } catch (err) { span.recordException(err); span.setStatus({ code: SpanStatusCode.ERROR, message: err.message, }); span.end(); throw err; } }); }; }; } unwrapAll(moduleExports, namespace) { // biome-ignore lint/style/noNonNullAssertion: we want it const entries = Object.entries(TRACABLES[namespace]); for (const [className, methodNames] of entries) { for (const methodName of methodNames) { // biome-ignore lint/style/noNonNullAssertion: we want it this._unwrap(moduleExports[className].prototype, methodName); } } return moduleExports; } wrapAll(moduleExports, namespace) { // biome-ignore lint/style/noNonNullAssertion: we want it const entries = Object.entries(TRACABLES[namespace]); for (const [className, methodNames] of entries) { for (const methodName of methodNames) { this._wrap( // biome-ignore lint/style/noNonNullAssertion: we want it moduleExports[className].prototype, methodName, this.patchMethod(namespace, className, methodName).bind(this)); } } return moduleExports; } }