n8n
Version:
n8n Workflow Automation Tool
193 lines • 9.18 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);
};
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