n8n
Version:
n8n Workflow Automation Tool
256 lines • 11 kB
JavaScript
"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