UNPKG

nestjs-otel

Version:
150 lines (149 loc) 6.57 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WideEventInterceptor = void 0; const common_1 = require("@nestjs/common"); const api_1 = require("@opentelemetry/api"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const opentelemetry_constants_1 = require("../opentelemetry.constants"); const wide_event_context_1 = require("./wide-event.context"); const wide_event_span_processor_1 = require("./wide-event.span-processor"); /** * Opens a wide event for each request and flushes the accumulated * attributes onto the span that was active when the request entered the * interceptor (usually the root HTTP span created by instrumentation). * * Register globally with APP_INTERCEPTOR or per controller with * UseInterceptors. * * @publicApi */ let WideEventInterceptor = class WideEventInterceptor { options; constructor(options) { this.options = options; } intercept(executionContext, next) { const bag = new Map(); bag.set("code.function.name", `${executionContext.getClass().name}.${executionContext.getHandler().name}`); this.seed(bag, executionContext); const activeContext = api_1.context.active(); // The middleware-captured span is the local root on Express / plain // Fastify (alive until the response ends). On stacks that wrap each // request phase in its own span (e.g. @fastify/otel) it can be an // ephemeral span that has already ended by flush time, so we keep the // interceptor-time active span as a live fallback and pick whichever is // still recording when we flush. const rootSpan = this.rootSpanFromRequest(executionContext); const activeSpan = api_1.trace.getSpan(activeContext); const contextWithBag = activeContext.setValue(wide_event_context_1.WIDE_EVENT_CONTEXT_KEY, bag); return new rxjs_1.Observable((subscriber) => { let terminated = false; const subscription = api_1.context.with(contextWithBag, () => next .handle() .pipe((0, operators_1.tap)({ error: (error) => { terminated = true; bag.set("error.type", error instanceof Error ? (error.constructor?.name ?? "Error") : "unknown"); if (error instanceof Error) { bag.set("error.message", error.message); if (error.stack) { bag.set("error.stack", error.stack); } } this.targetSpan(rootSpan, activeSpan)?.setStatus({ code: api_1.SpanStatusCode.ERROR, }); }, complete: () => { terminated = true; }, }), (0, operators_1.finalize)(() => { if (terminated) { this.flush(bag, rootSpan, activeSpan); } })) .subscribe(subscriber)); return () => subscription.unsubscribe(); }); } rootSpanFromRequest(executionContext) { if (typeof executionContext.getType !== "function" || executionContext.getType() !== "http") { return; } const request = executionContext .switchToHttp() .getRequest(); return (request?.[wide_event_context_1.WIDE_EVENT_ROOT_SPAN] ?? request?.raw?.[wide_event_context_1.WIDE_EVENT_ROOT_SPAN]); } /** * Picks the span the wide event should be written to, preferring the * local-root span (when the WideEventSpanProcessor is registered), then the * middleware-captured root span, then the interceptor-time active span — * skipping any that have already ended. */ targetSpan(rootSpan, activeSpan) { const traceId = (activeSpan ?? rootSpan)?.spanContext().traceId; const localRoot = traceId ? (0, wide_event_span_processor_1.getLocalRootSpan)(traceId) : undefined; return this.pickSpan(localRoot, rootSpan, activeSpan); } pickSpan(...candidates) { for (const candidate of candidates) { if (candidate?.isRecording()) { return candidate; } } return; } seed(bag, executionContext) { const seed = this.options?.wideEvents?.seed; if (!seed) { return; } try { const result = seed(executionContext); if (result == null) { return; } for (const [key, value] of Object.entries(result)) { if (value !== undefined) { bag.set(key, value); } } } catch (error) { bag.set("wide_event.seed.error", error instanceof Error ? error.message : String(error)); } } flush(bag, rootSpan, activeSpan) { const span = this.targetSpan(rootSpan, activeSpan); if (!span) { return; } span.setAttributes(Object.fromEntries(bag)); span.setAttribute("nestjs_otel.wide_event", true); } }; exports.WideEventInterceptor = WideEventInterceptor; exports.WideEventInterceptor = WideEventInterceptor = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_1.Optional)()), __param(0, (0, common_1.Inject)(opentelemetry_constants_1.OPENTELEMETRY_MODULE_OPTIONS)), __metadata("design:paramtypes", [Object]) ], WideEventInterceptor);