"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 semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
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));
            }
            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 });
                activeNodeSpan.addEvent('exception', {
                    [semantic_conventions_1.ATTR_EXCEPTION_MESSAGE]: params.error.message,
                    [semantic_conventions_1.ATTR_EXCEPTION_TYPE]: params.error.constructor.name,
                    [semantic_conventions_1.ATTR_EXCEPTION_STACKTRACE]: params.error.stack,
                });
            }
            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;
    const ctor = record.constructor;
    if (typeof ctor === 'function' && ctor.name && ctor.name !== 'Object')
        return ctor.name;
    return 'UnknownError';
}
//# sourceMappingURL=execution-level-tracer.js.map