UNPKG

n8n

Version:

n8n Workflow Automation Tool

256 lines 11 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExecutionLevelTracer = void 0; const backend_common_1 = require("@n8n/backend-common"); const di_1 = require("@n8n/di"); const api_1 = require("@opentelemetry/api"); const execution_level_tracer_types_1 = require("./execution-level-tracer.types"); const otel_config_1 = require("./otel.config"); const otel_constants_1 = require("./otel.constants"); const TRACER_NAME = 'n8n-workflow'; function isError(status) { return status === 'error' || status === 'crashed'; } let ExecutionLevelTracer = class ExecutionLevelTracer { constructor(config, logger) { this.config = config; this.logger = logger; this.activeWorkflowSpans = new Map(); this.activeNodeSpansByExecutionId = new Map(); this.tracer = api_1.trace.getTracer(TRACER_NAME); } startWorkflow(params) { try { const parentCtx = this.parseTraceParentHeaders(params.tracingContext); const links = this.buildContinuationLinks(params.linkTo); const span = this.tracer.startSpan('workflow.execute', { attributes: { [otel_constants_1.ATTR.WORKFLOW_ID]: params.workflow.id, [otel_constants_1.ATTR.WORKFLOW_NAME]: params.workflow.name, [otel_constants_1.ATTR.WORKFLOW_VERSION_ID]: params.workflow.versionId ?? '', [otel_constants_1.ATTR.WORKFLOW_NODE_COUNT]: params.workflow.nodeCount, [otel_constants_1.ATTR.EXECUTION_ID]: params.executionId, }, links, }, parentCtx); this.activeWorkflowSpans.set(params.executionId, { span }); return toTracingParentContext(span); } catch (error) { this.logger.warn('Failed to start workflow span', { executionId: params.executionId, error: error instanceof Error ? error.message : String(error), }); throw error; } } endWorkflow(params) { try { const tracked = this.activeWorkflowSpans.get(params.executionId); if (!tracked) return; const { span } = tracked; span.setAttributes({ [otel_constants_1.ATTR.EXECUTION_MODE]: params.mode, [otel_constants_1.ATTR.EXECUTION_STATUS]: params.status, [otel_constants_1.ATTR.EXECUTION_IS_RETRY]: params.isRetry, ...(params.retryOf ? { [otel_constants_1.ATTR.EXECUTION_RETRY_OF]: params.retryOf } : {}), }); span.setStatus({ code: isError(params.status) ? api_1.SpanStatusCode.ERROR : api_1.SpanStatusCode.OK }); if (isError(params.status) && params.error) { span.setAttribute(otel_constants_1.ATTR.EXECUTION_ERROR_TYPE, getErrorType(params.error)); const recordableException = toRecordableException(params.error); if (recordableException) { span.recordException(recordableException); } } this.endDanglingNodeSpans(params.executionId); span.end(); } catch (error) { this.logger.warn('Failed to end workflow span', { executionId: params.executionId, error: error instanceof Error ? error.message : String(error), }); throw error; } finally { this.activeWorkflowSpans.delete(params.executionId); } } startNode(params) { try { const parentCtx = this.findWorkflowSpanContext(params.executionId); if (!parentCtx) { this.logger.warn('Trying to start a node without a pre-existing parent workflow trace - ignoring'); return; } const span = this.tracer.startSpan('node.execute', { attributes: { [otel_constants_1.ATTR.NODE_ID]: params.node.id, [otel_constants_1.ATTR.NODE_NAME]: params.node.name, [otel_constants_1.ATTR.NODE_TYPE]: params.node.type, [otel_constants_1.ATTR.NODE_TYPE_VERSION]: params.node.typeVersion, }, }, parentCtx); let executionNodes = this.activeNodeSpansByExecutionId.get(params.executionId); if (!executionNodes) { executionNodes = new Map(); this.activeNodeSpansByExecutionId.set(params.executionId, executionNodes); } executionNodes.set(params.node.name, { span }); } catch (error) { this.logger.warn('Failed to start node span', { executionId: params.executionId, nodeName: params.node.name, error: error instanceof Error ? error.message : String(error), }); throw error; } } endNode(params) { try { const executionNodes = this.activeNodeSpansByExecutionId.get(params.executionId); const nodeStart = executionNodes?.get(params.node.name); if (!nodeStart) return; const { span: activeNodeSpan } = nodeStart; activeNodeSpan.setAttributes(buildNodeEndAttributes(params)); activeNodeSpan.setStatus({ code: api_1.SpanStatusCode.OK }); if (params.error) { activeNodeSpan.setStatus({ code: api_1.SpanStatusCode.ERROR }); const recordableException = toRecordableException(params.error); if (recordableException) { activeNodeSpan.recordException(recordableException); } } activeNodeSpan.end(); executionNodes?.delete(params.node.name); } catch (error) { this.logger.warn('Failed to end node span', { executionId: params.executionId, nodeName: params.node.name, error: error instanceof Error ? error.message : String(error), }); throw error; } } injectTraceHeaders(executionId, nodeName, headers) { try { if (!this.config.injectOutbound) return; const span = this.findMostSpecificSpan(executionId, nodeName); if (!span) return; api_1.propagation.inject(api_1.trace.setSpan(api_1.context.active(), span), headers); } catch (error) { this.logger.warn('Failed to inject trace headers', { executionId, error: error instanceof Error ? error.message : String(error), }); throw error; } } parseTraceParentHeaders(tracingContext) { return tracingContext ? api_1.propagation.extract(api_1.context.active(), tracingContext) : api_1.context.active(); } buildContinuationLinks(linkTo) { if (!linkTo) return undefined; const extracted = api_1.propagation.extract(api_1.context.active(), linkTo); const spanContext = api_1.trace.getSpanContext(extracted); if (!spanContext) return undefined; return [ { context: spanContext, attributes: { [otel_constants_1.ATTR.CONTINUATION_REASON]: 'resume' }, }, ]; } findWorkflowSpanContext(executionId) { const tracked = this.activeWorkflowSpans.get(executionId); return tracked ? api_1.trace.setSpan(api_1.context.active(), tracked.span) : undefined; } findMostSpecificSpan(executionId, nodeName) { return ((nodeName ? this.activeNodeSpansByExecutionId.get(executionId)?.get(nodeName)?.span : undefined) ?? this.activeWorkflowSpans.get(executionId)?.span); } endDanglingNodeSpans(executionId) { const executionNodes = this.activeNodeSpansByExecutionId.get(executionId); if (!executionNodes) return; for (const tracked of executionNodes.values()) { terminateSpan(tracked.span, 'workflow_cancelled'); } this.activeNodeSpansByExecutionId.delete(executionId); } }; exports.ExecutionLevelTracer = ExecutionLevelTracer; exports.ExecutionLevelTracer = ExecutionLevelTracer = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [otel_config_1.OtelConfig, backend_common_1.Logger]) ], ExecutionLevelTracer); function buildNodeEndAttributes(params) { const attrs = { [otel_constants_1.ATTR.NODE_ITEMS_INPUT]: params.inputItemCount, [otel_constants_1.ATTR.NODE_ITEMS_OUTPUT]: params.outputItemCount, }; if (params.customAttributes) { for (const [key, value] of Object.entries(params.customAttributes)) { attrs[`n8n.node.custom.${key}`] = value; } } return attrs; } function toTracingParentContext(span) { const carrier = {}; api_1.propagation.inject(api_1.trace.setSpan(api_1.context.active(), span), carrier); return { traceparent: carrier.traceparent, tracestate: carrier.tracestate }; } function terminateSpan(span, reason) { span.setAttribute(otel_constants_1.ATTR.NODE_TERMINATION_REASON, reason); span.setStatus({ code: api_1.SpanStatusCode.ERROR }); span.end(); } function getErrorType(error) { if (typeof error !== 'object' || error === null) return 'UnknownError'; const record = error; const name = record.name; if (typeof name === 'string' && name.trim() !== '') return name; if ((0, execution_level_tracer_types_1.isEndNodeError)(error)) { return error.constructor.name; } return 'UnknownError'; } function toRecordableException(error) { if (error instanceof Error || typeof error === 'string') return error; if ((0, execution_level_tracer_types_1.isEndNodeError)(error)) { return { message: error.message, name: error.constructor.name, stack: error.stack, }; } return undefined; } //# sourceMappingURL=execution-level-tracer.js.map