UNPKG

n8n

Version:

n8n Workflow Automation Tool

193 lines 9.18 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExpressionObservabilityProvider = void 0; const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const di_1 = require("@n8n/di"); const expression_runtime_1 = require("@n8n/expression-runtime"); const api_1 = require("@opentelemetry/api"); const n8n_workflow_1 = require("n8n-workflow"); const prom_client_1 = __importDefault(require("prom-client")); const expression_observability_constants_1 = require("./expression-observability.constants"); const expression_observability_formatters_1 = require("./expression-observability.formatters"); let ExpressionObservabilityProvider = class ExpressionObservabilityProvider { constructor(config, logger, globalConfig) { this.config = config; this.logger = logger; this.scopedLogger = this.logger.scoped('expression-engine'); if (!this.config.observabilityEnabled || this.config.engine !== 'vm') { this.metrics = expression_runtime_1.NoOpProvider.metrics; this.traces = expression_runtime_1.NoOpProvider.traces; this.logs = expression_runtime_1.NoOpProvider.logs; return; } this.prefix = globalConfig.endpoints.metrics.prefix; this.registerMetrics(); this.metrics = { counter: (name, value, tags) => this.counter(name, value, tags), gauge: (name, value, tags) => this.gauge(name, value, tags), histogram: (name, value, tags) => this.histogram(name, value, tags), }; this.traces = { startSpan: (name, attributes) => this.startSpan(name, attributes), }; this.logs = { error: (message, error, context) => this.scopedLogger.error(message, { error, ...context }), warn: (message, context) => this.scopedLogger.warn(message, context), info: (message, context) => this.scopedLogger.info(message, context), debug: (message, context) => this.scopedLogger.debug(message, context), }; } registerMetrics() { for (const def of Object.values(expression_runtime_1.EXPRESSION_METRICS)) { const promName = (0, expression_observability_formatters_1.toPromName)(def.name, def.kind, this.prefix); switch (def.kind) { case 'counter': new prom_client_1.default.Counter({ name: promName, help: def.help, labelNames: def.labels, }); break; case 'gauge': new prom_client_1.default.Gauge({ name: promName, help: def.help, labelNames: def.labels, }); break; case 'histogram': new prom_client_1.default.Histogram({ name: promName, help: def.help, labelNames: def.labels, buckets: expression_observability_constants_1.DURATION_BUCKETS_SECONDS, }); break; default: { const _exhaustive = def.kind; throw new n8n_workflow_1.UnexpectedError(`Unknown metric kind: ${String(_exhaustive)}`); } } } } counter(name, value, tags) { const promName = (0, expression_observability_formatters_1.toPromName)(name, 'counter', this.prefix); const counter = prom_client_1.default.register.getSingleMetric(promName); if (!counter) { this.scopedLogger.warn('Emitted unknown expression metric', { name }); return; } if (tags) counter.inc(tags, value); else counter.inc(value); } gauge(name, value, tags) { const promName = (0, expression_observability_formatters_1.toPromName)(name, 'gauge', this.prefix); const gauge = prom_client_1.default.register.getSingleMetric(promName); if (!gauge) { this.scopedLogger.warn('Emitted unknown expression metric', { name }); return; } if (tags) gauge.set(tags, value); else gauge.set(value); } histogram(name, value, tags) { const promName = (0, expression_observability_formatters_1.toPromName)(name, 'histogram', this.prefix); const histogram = prom_client_1.default.register.getSingleMetric(promName); if (!histogram) { this.scopedLogger.warn('Emitted unknown expression metric', { name }); return; } if (tags) histogram.observe(tags, value); else histogram.observe(value); if (name === expression_runtime_1.EXPRESSION_METRICS.evaluationDuration.name) this.maybeRecordSpan(value, tags); } maybeRecordSpan(durationSeconds, tags) { const { tracesEnabled, slowEvaluationThresholdMs } = this.config; if (!tracesEnabled) return; const decision = this.tailSample(durationSeconds, tags); if (decision === 'drop') return; const slowThresholdSeconds = slowEvaluationThresholdMs / 1000; const outcome = tags?.status === 'error' ? 'error' : durationSeconds > slowThresholdSeconds ? 'slow' : 'healthy'; const tracer = this.getTracer(); const errorType = tags?.type && tags.type !== 'none' ? tags.type : undefined; const span = tracer.startSpan('expression.evaluate', { attributes: { [expression_observability_constants_1.ATTRIBUTE.EXPRESSION_ENGINE]: 'vm', [expression_observability_constants_1.ATTRIBUTE.EXPRESSION_DURATION_SECONDS]: durationSeconds, [expression_observability_constants_1.ATTRIBUTE.EXPRESSION_OUTCOME]: outcome, ...(errorType ? { [expression_observability_constants_1.ATTRIBUTE.EXPRESSION_ERROR_TYPE]: errorType } : {}), }, }); if (tags?.status === 'error') span.setStatus({ code: api_1.SpanStatusCode.ERROR }); span.end(); } tailSample(durationSeconds, tags) { if (tags?.status === 'error') return 'keep'; const { slowEvaluationThresholdMs, tracesSampleRate } = this.config; if (durationSeconds > slowEvaluationThresholdMs / 1000) return 'keep'; if (tracesSampleRate > 0 && Math.random() < tracesSampleRate) return 'keep'; return 'drop'; } startSpan(name, attributes) { if (!this.config.tracesEnabled) return expression_runtime_1.NoOpProvider.traces.startSpan(name, attributes); const tracer = this.getTracer(); const otelSpan = tracer.startSpan(name, { attributes: (0, expression_observability_formatters_1.normalizeAttributes)(attributes), }); return { setStatus: (status) => otelSpan.setStatus({ code: status === 'ok' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR, }), setAttribute: (key, value) => { const normalized = (0, expression_observability_formatters_1.normalizeAttributeValue)(value); if (normalized !== undefined) otelSpan.setAttribute(key, normalized); }, recordException: (error) => otelSpan.recordException(error), end: () => otelSpan.end(), }; } getTracer() { this.tracer ??= api_1.trace.getTracer(expression_observability_constants_1.TRACER_NAME); return this.tracer; } }; exports.ExpressionObservabilityProvider = ExpressionObservabilityProvider; exports.ExpressionObservabilityProvider = ExpressionObservabilityProvider = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [config_1.ExpressionEngineConfig, backend_common_1.Logger, config_1.GlobalConfig]) ], ExpressionObservabilityProvider); //# sourceMappingURL=expression-observability.provider.js.map