UNPKG

n8n

Version:

n8n Workflow Automation Tool

294 lines • 12.7 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_settings_service_1 = require("./otel-settings.service"); const otel_constants_1 = require("./otel.constants"); const TRACER_NAME = 'n8n-workflow'; const UNKNOWN_ERROR_TYPE = 'UnknownError'; const OBJECT_ERROR_TYPE = 'Object'; function isError(status) { return status === 'error' || status === 'crashed'; } let ExecutionLevelTracer = class ExecutionLevelTracer { refreshTracer() { this.tracer = api_1.trace.getTracer(TRACER_NAME); } constructor(otelSettingsService, logger) { this.otelSettingsService = otelSettingsService; 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, ...(params.project?.id && { [otel_constants_1.ATTR.PROJECT_ID]: params.project.id }), ...buildCustomAttributes(otel_constants_1.ATTR.WORKFLOW_CUSTOM_PREFIX, params.workflow?.customAttributes), ...buildCustomAttributes(otel_constants_1.ATTR.PROJECT_CUSTOM_PREFIX, params.project?.customAttributes), }, 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)); if (params.error) { activeNodeSpan.setStatus({ code: api_1.SpanStatusCode.ERROR }); const recordableException = toRecordableException(params.error); if (recordableException) { activeNodeSpan.recordException(recordableException); } } else { activeNodeSpan.setStatus({ code: api_1.SpanStatusCode.OK }); } 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.otelSettingsService.getSettings().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_settings_service_1.OtelSettingsService, backend_common_1.Logger]) ], ExecutionLevelTracer); function buildCustomAttributes(prefix, attrs) { if (!attrs) return {}; const result = {}; for (const [k, v] of Object.entries(attrs)) { result[`${prefix}${k}`] = v; } return result; } function buildNodeEndAttributes(params) { const attrs = { [otel_constants_1.ATTR.NODE_ITEMS_INPUT]: params.inputItemCount, [otel_constants_1.ATTR.NODE_ITEMS_OUTPUT]: params.outputItemCount, ...buildCustomAttributes(otel_constants_1.ATTR.NODE_CUSTOM_PREFIX, params.customAttributes), }; 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 (error instanceof Error) return error.constructor.name; if (typeof error !== 'object' || error === null) return UNKNOWN_ERROR_TYPE; const record = error; const name = getNonEmptyString(record.name); if (name) return name; const constructorName = getConstructorName(record); if (constructorName && constructorName !== OBJECT_ERROR_TYPE) return constructorName; const description = getNonEmptyString(record.description); if (description && looksLikeErrorType(description)) return description; return UNKNOWN_ERROR_TYPE; } 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: getErrorType(error), stack: error.stack, }; } return undefined; } function getNonEmptyString(value) { if (typeof value !== 'string') return undefined; const trimmed = value.trim(); return trimmed === '' ? undefined : trimmed; } function getConstructorName(record) { const constructor = record.constructor; if (typeof constructor === 'function') return getNonEmptyString(constructor.name); if (typeof constructor !== 'object' || constructor === null) return undefined; return getNonEmptyString(constructor.name); } function looksLikeErrorType(value) { return /^[A-Z][\w.]*(Error|Exception)$/.test(value); } //# sourceMappingURL=execution-level-tracer.js.map