UNPKG

@kurrent/opentelemetry

Version:

KurrentDB NodeJS Client SDK interceptors for tracing with OpenTelemetry

201 lines 9.51 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-this-alias */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Instrumentation = void 0; const api_1 = require("@opentelemetry/api"); const instrumentation_1 = require("@opentelemetry/instrumentation"); const attributes_1 = require("./attributes"); const version_1 = require("./version"); const utils_1 = require("./utils"); const TRACE_ID = "$traceId"; const SPAN_ID = "$spanId"; class Instrumentation extends instrumentation_1.InstrumentationBase { constructor(config = {}) { super(version_1.INSTRUMENTATION_NAME, version_1.INSTRUMENTATION_VERSION, config); } init() { return new instrumentation_1.InstrumentationNodeModuleDefinition("@kurrent/kurrentdb-client", ["1.*"], this._onPatchMain(), this._onUnPatchMain()); } _onPatchMain() { return (moduleExports) => { this.wrap(moduleExports.KurrentDBClient.prototype, "appendToStream", this._patchAppendToStream()); this.wrap(moduleExports.KurrentDBClient.prototype, "subscribeToStream", this._patchCatchUpSubscription()); this.wrap(moduleExports.KurrentDBClient.prototype, "subscribeToAll", this._patchCatchUpSubscription()); this.wrap(moduleExports.KurrentDBClient.prototype, "subscribeToPersistentSubscriptionToStream", this._patchPersistentSubscription()); this.wrap(moduleExports.KurrentDBClient.prototype, "subscribeToPersistentSubscriptionToAll", this._patchPersistentSubscription()); return moduleExports; }; } wrap(target, name, replacementFactory) { this._wrap(target, name, (originalMethod) => replacementFactory(originalMethod, name)); } _onUnPatchMain() { return (moduleExports) => { this._diag.debug("un-patching"); this._unwrap(moduleExports.KurrentDBClient.prototype, "appendToStream"); this._unwrap(moduleExports.KurrentDBClient.prototype, "subscribeToStream"); this._unwrap(moduleExports.KurrentDBClient.prototype, "subscribeToPersistentSubscriptionToStream"); this._unwrap(moduleExports.KurrentDBClient.prototype, "subscribeToPersistentSubscriptionToAll"); }; } _patchAppendToStream() { const instrumentation = this; const tracer = instrumentation.tracer; return function appendToStream(original, operation) { return async function (...args) { const [streamName, events, options] = [...args]; let actualEvents; const uri = await this.resolveUri(); const { hostname, port } = Instrumentation.getServerAddress(uri); const attributes = { [attributes_1.KurrentAttributes.KURRENT_DB_STREAM]: streamName, [attributes_1.KurrentAttributes.SERVER_ADDRESS]: hostname, [attributes_1.KurrentAttributes.SERVER_PORT]: port, [attributes_1.KurrentAttributes.DATABASE_SYSTEM]: version_1.INSTRUMENTATION_NAME, [attributes_1.KurrentAttributes.DATABASE_OPERATION]: operation, }; if (options?.credentials) { attributes[attributes_1.KurrentAttributes.DATABASE_USER] = options.credentials.username; } const span = tracer.startSpan(attributes_1.KurrentAttributes.STREAM_APPEND, { kind: api_1.SpanKind.CLIENT, attributes, }); if (Array.isArray(events)) { actualEvents = events; } else { actualEvents = [events]; } const traceId = span.spanContext().traceId; const spanId = span.spanContext().spanId; actualEvents.forEach((event) => { const metadata = (event.metadata = event.metadata || {}); if ((0, utils_1.isJSONEventData)(event) && typeof metadata === "object") { event.metadata = { ...metadata, [TRACE_ID]: traceId, [SPAN_ID]: spanId, }; } }); try { const result = await original.apply(this, [ streamName, actualEvents, options, ]); return result; } catch (error) { throw Instrumentation.handleError(error, span); } finally { span.end(); } }; }; } static applySubscriptionInstrumentation(spanName, subscription, uri, operation, options, tracer) { if (!(0, utils_1.hasConvertGrpcEventMethod)(subscription)) return; const originalConvertGrpcEvent = subscription.convertGrpcEvent; subscription.convertGrpcEvent = function (grpcEvent) { const resolved = originalConvertGrpcEvent.apply(subscription, [ grpcEvent, ]); const resolvedEvent = resolved; const metadata = resolvedEvent?.event?.metadata; if (typeof metadata !== "object" || metadata === null) return resolved; const parentContext = Instrumentation.restoreContext(metadata); const { hostname, port } = Instrumentation.getServerAddress(uri); const subscriptionId = subscription.id; const attributes = { [attributes_1.KurrentAttributes.KURRENT_DB_STREAM]: resolvedEvent?.event?.streamId, [attributes_1.KurrentAttributes.KURRENT_DB_EVENT_ID]: resolvedEvent?.event?.id, [attributes_1.KurrentAttributes.KURRENT_DB_EVENT_TYPE]: resolvedEvent?.event?.type, [attributes_1.KurrentAttributes.KURRENT_DB_SUBSCRIPTION_ID]: subscriptionId, [attributes_1.KurrentAttributes.SERVER_ADDRESS]: hostname, [attributes_1.KurrentAttributes.SERVER_PORT]: port, [attributes_1.KurrentAttributes.DATABASE_SYSTEM]: version_1.INSTRUMENTATION_NAME, [attributes_1.KurrentAttributes.DATABASE_OPERATION]: operation, [attributes_1.KurrentAttributes.DATABASE_USER]: options?.credentials?.username, }; const span = tracer.startSpan(spanName, { attributes, kind: api_1.SpanKind.CONSUMER, }, parentContext); try { return resolved; } catch (error) { throw Instrumentation.handleError(error, span); } finally { span.end(); } }; } _patchCatchUpSubscription() { const instrumentation = this; const tracer = instrumentation.tracer; return function subscribe(original, operation) { return function (...args) { let options; if (operation == "subscribeToStream") { options = args[1]; } else { options = args[0]; } const subscription = original.apply(this, args); this.resolveUri().then((uri) => Instrumentation.applySubscriptionInstrumentation(attributes_1.KurrentAttributes.STREAM_SUBSCRIBE, subscription, uri, operation, options, tracer)); return subscription; }; }; } _patchPersistentSubscription() { const instrumentation = this; const tracer = instrumentation.tracer; return function subscribe(original, operation) { return function (...args) { let options; if (operation === "subscribeToPersistentSubscriptionToStream") { options = args[2]; } else { options = args[1]; } const subscription = original.apply(this, args); this.resolveUri().then((uri) => Instrumentation.applySubscriptionInstrumentation(attributes_1.KurrentAttributes.STREAM_SUBSCRIBE, subscription, uri, operation, options, tracer)); return subscription; }; }; } static restoreContext = (metadata, isRemote = true) => { const traceId = metadata[TRACE_ID]; const spanId = metadata[SPAN_ID]; return api_1.trace.setSpanContext(api_1.context.active(), { traceId, spanId, traceFlags: api_1.TraceFlags.SAMPLED, isRemote, }); }; static handleError = (error, span) => { span.recordException(error); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message }); throw error; }; static getServerAddress = (resolvedUri) => { const uri = new URL(`http://${resolvedUri}`); // the protocol is not important because we are only interested in the hostname and port const hostname = uri.hostname; const port = uri.port; return { hostname, port }; }; } exports.Instrumentation = Instrumentation; //# sourceMappingURL=instrumentation.js.map