UNPKG

@kurrent/opentelemetry

Version:

KurrentDB NodeJS Client SDK interceptors for tracing with OpenTelemetry

306 lines 14.9 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, "multiStreamAppend", this._patchMultiStreamAppend()); this.wrap(moduleExports.KurrentDBClient.prototype, "appendRecords", this._patchAppendRecords()); 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, "multiStreamAppend"); this._unwrap(moduleExports.KurrentDBClient.prototype, "appendRecords"); 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, }; const auth = (0, utils_1.describeAuth)(options?.credentials, Boolean(this.credentialsProvider)); if (auth.username !== undefined) { attributes[attributes_1.KurrentAttributes.DATABASE_USER] = auth.username; } if (auth.kind !== undefined) { attributes[attributes_1.KurrentAttributes.KURRENT_DB_AUTH_KIND] = auth.kind; } 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(); } }; }; } _patchMultiStreamAppend() { const instrumentation = this; const tracer = instrumentation.tracer; return function multiStreamAppend(original, operation) { return async function (...args) { const [requests] = [...args]; const uri = await this.resolveUri(); const { hostname, port } = Instrumentation.getServerAddress(uri); const requestStartTime = Date.now(); const span = tracer.startSpan(attributes_1.KurrentAttributes.STREAM_MULTI_APPEND, { kind: api_1.SpanKind.CLIENT, startTime: requestStartTime, attributes: { [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, }, }); requests.forEach((request) => { const traceId = span.spanContext().traceId; const spanId = span.spanContext().spanId; request.events.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 { return await original.apply(this, [requests]); } catch (error) { Instrumentation.handleError(error, span); throw error; } finally { span.end(); } }; }; } _patchAppendRecords() { const instrumentation = this; const tracer = instrumentation.tracer; return function appendRecords(original, operation) { return async function (...args) { const [records] = [...args]; const uri = await this.resolveUri(); const { hostname, port } = Instrumentation.getServerAddress(uri); const requestStartTime = Date.now(); const span = tracer.startSpan(attributes_1.KurrentAttributes.STREAM_MULTI_APPEND, { kind: api_1.SpanKind.CLIENT, startTime: requestStartTime, attributes: { [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, }, }); const traceId = span.spanContext().traceId; const spanId = span.spanContext().spanId; records.forEach((record) => { const metadata = (record.record.metadata = record.record.metadata || {}); if ((0, utils_1.isJSONEventData)(record.record) && typeof metadata === "object") { record.record.metadata = { ...metadata, [TRACE_ID]: traceId, [SPAN_ID]: spanId, }; } }); try { return await original.apply(this, [records, args[1]]); } catch (error) { Instrumentation.handleError(error, span); throw error; } finally { span.end(); } }; }; } static applySubscriptionInstrumentation(spanName, subscription, uri, operation, authContext, 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, }; if (authContext.username !== undefined) { attributes[attributes_1.KurrentAttributes.DATABASE_USER] = authContext.username; } if (authContext.kind !== undefined) { attributes[attributes_1.KurrentAttributes.KURRENT_DB_AUTH_KIND] = authContext.kind; } 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); const authContext = (0, utils_1.describeAuth)(options?.credentials, Boolean(this.credentialsProvider)); this.resolveUri().then((uri) => Instrumentation.applySubscriptionInstrumentation(attributes_1.KurrentAttributes.STREAM_SUBSCRIBE, subscription, uri, operation, authContext, 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); const authContext = (0, utils_1.describeAuth)(options?.credentials, Boolean(this.credentialsProvider)); this.resolveUri().then((uri) => Instrumentation.applySubscriptionInstrumentation(attributes_1.KurrentAttributes.STREAM_SUBSCRIBE, subscription, uri, operation, authContext, 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