mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-04 02:37:46 +02:00
fix(core): Re-register expression metrics after Prometheus registry reset (#31484)
This commit is contained in:
parent
2431a43ac1
commit
ccf401c720
|
|
@ -110,6 +110,48 @@ describe('ExpressionObservabilityProvider', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('survives a global registry clear', () => {
|
||||
it('re-registers expression metrics on emission without warning after a clear', async () => {
|
||||
const provider = new ExpressionObservabilityProvider(
|
||||
buildConfig(),
|
||||
buildLogger(),
|
||||
buildGlobalConfig(),
|
||||
);
|
||||
|
||||
promClient.register.clear();
|
||||
|
||||
provider.metrics.counter(EXPRESSION_METRICS.poolAcquired.name, 3);
|
||||
provider.metrics.gauge(EXPRESSION_METRICS.codeCacheSize.name, 7);
|
||||
provider.metrics.histogram(EXPRESSION_METRICS.evaluationDuration.name, 0.02, {
|
||||
status: 'success',
|
||||
type: 'none',
|
||||
});
|
||||
|
||||
expect(scopedLogger.warn).not.toHaveBeenCalled();
|
||||
|
||||
const output = await promClient.register.metrics();
|
||||
expect(output).toContain('n8n_expression_pool_acquired_total 3');
|
||||
expect(output).toContain('n8n_expression_code_cache_size 7');
|
||||
expect(output).toContain('n8n_expression_evaluation_duration_seconds_count');
|
||||
});
|
||||
|
||||
it('still warns for genuinely unknown metric names after a clear', () => {
|
||||
const provider = new ExpressionObservabilityProvider(
|
||||
buildConfig(),
|
||||
buildLogger(),
|
||||
buildGlobalConfig(),
|
||||
);
|
||||
|
||||
promClient.register.clear();
|
||||
|
||||
provider.metrics.counter('test.unknown', 1);
|
||||
|
||||
expect(scopedLogger.warn).toHaveBeenCalledWith('Emitted unknown expression metric', {
|
||||
name: 'test.unknown',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tail sampling', () => {
|
||||
const startSpanMock = jest.fn().mockReturnValue({
|
||||
setStatus: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ export class ExpressionObservabilityProvider implements ObservabilityProvider {
|
|||
|
||||
private tracer?: Tracer;
|
||||
|
||||
private readonly metricDefs = new Map<string, MetricDef>(
|
||||
(Object.values(EXPRESSION_METRICS) as MetricDef[]).map((def) => [def.name, def]),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly config: ExpressionEngineConfig,
|
||||
private readonly logger: Logger,
|
||||
|
|
@ -58,7 +62,9 @@ export class ExpressionObservabilityProvider implements ObservabilityProvider {
|
|||
|
||||
this.prefix = globalConfig.endpoints.metrics.prefix;
|
||||
|
||||
this.registerMetrics();
|
||||
for (const metric of this.metricDefs.values()) {
|
||||
this.getOrRegisterMetric(metric);
|
||||
}
|
||||
|
||||
this.metrics = {
|
||||
counter: (name, value, tags) => this.counter(name, value, tags),
|
||||
|
|
@ -78,71 +84,65 @@ export class ExpressionObservabilityProvider implements ObservabilityProvider {
|
|||
};
|
||||
}
|
||||
|
||||
private registerMetrics(): void {
|
||||
for (const def of Object.values(EXPRESSION_METRICS) as MetricDef[]) {
|
||||
const promName = toPromName(def.name, def.kind, this.prefix);
|
||||
switch (def.kind) {
|
||||
case 'counter':
|
||||
new promClient.Counter({
|
||||
name: promName,
|
||||
help: def.help,
|
||||
labelNames: def.labels,
|
||||
});
|
||||
break;
|
||||
case 'gauge':
|
||||
new promClient.Gauge({
|
||||
name: promName,
|
||||
help: def.help,
|
||||
labelNames: def.labels,
|
||||
});
|
||||
break;
|
||||
case 'histogram':
|
||||
new promClient.Histogram({
|
||||
name: promName,
|
||||
help: def.help,
|
||||
labelNames: def.labels,
|
||||
buckets: DURATION_BUCKETS_SECONDS,
|
||||
});
|
||||
break;
|
||||
default: {
|
||||
const _exhaustive: never = def.kind;
|
||||
throw new UnexpectedError(`Unknown metric kind: ${String(_exhaustive)}`);
|
||||
}
|
||||
private getOrRegisterMetric(def: MetricDef) {
|
||||
const promName = toPromName(def.name, def.kind, this.prefix);
|
||||
const existing = promClient.register.getSingleMetric(promName);
|
||||
if (existing) return existing;
|
||||
|
||||
switch (def.kind) {
|
||||
case 'counter':
|
||||
return new promClient.Counter({
|
||||
name: promName,
|
||||
help: def.help,
|
||||
labelNames: def.labels,
|
||||
});
|
||||
case 'gauge':
|
||||
return new promClient.Gauge({
|
||||
name: promName,
|
||||
help: def.help,
|
||||
labelNames: def.labels,
|
||||
});
|
||||
case 'histogram':
|
||||
return new promClient.Histogram({
|
||||
name: promName,
|
||||
help: def.help,
|
||||
labelNames: def.labels,
|
||||
buckets: DURATION_BUCKETS_SECONDS,
|
||||
});
|
||||
default: {
|
||||
const _exhaustive: never = def.kind;
|
||||
throw new UnexpectedError(`Unknown metric kind: ${String(_exhaustive)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getMetricDef(name: string, kind: MetricDef['kind']) {
|
||||
const def = this.metricDefs.get(name);
|
||||
if (def?.kind === kind) return def;
|
||||
this.scopedLogger.warn('Emitted unknown expression metric', { name });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private counter(name: string, value: number, tags?: Record<string, string>): void {
|
||||
const promName = toPromName(name, 'counter', this.prefix);
|
||||
const counter = promClient.register.getSingleMetric(promName) as Counter<string> | undefined;
|
||||
if (!counter) {
|
||||
this.scopedLogger.warn('Emitted unknown expression metric', { name });
|
||||
return;
|
||||
}
|
||||
const def = this.getMetricDef(name, 'counter');
|
||||
if (!def) return;
|
||||
const counter = this.getOrRegisterMetric(def) as Counter<string>;
|
||||
if (tags) counter.inc(tags, value);
|
||||
else counter.inc(value);
|
||||
}
|
||||
|
||||
private gauge(name: string, value: number, tags?: Record<string, string>): void {
|
||||
const promName = toPromName(name, 'gauge', this.prefix);
|
||||
const gauge = promClient.register.getSingleMetric(promName) as Gauge<string> | undefined;
|
||||
if (!gauge) {
|
||||
this.scopedLogger.warn('Emitted unknown expression metric', { name });
|
||||
return;
|
||||
}
|
||||
const def = this.getMetricDef(name, 'gauge');
|
||||
if (!def) return;
|
||||
const gauge = this.getOrRegisterMetric(def) as Gauge<string>;
|
||||
if (tags) gauge.set(tags, value);
|
||||
else gauge.set(value);
|
||||
}
|
||||
|
||||
private histogram(name: string, value: number, tags?: Record<string, string>): void {
|
||||
const promName = toPromName(name, 'histogram', this.prefix);
|
||||
const histogram = promClient.register.getSingleMetric(promName) as
|
||||
| Histogram<string>
|
||||
| undefined;
|
||||
if (!histogram) {
|
||||
this.scopedLogger.warn('Emitted unknown expression metric', { name });
|
||||
return;
|
||||
}
|
||||
const def = this.getMetricDef(name, 'histogram');
|
||||
if (!def) return;
|
||||
const histogram = this.getOrRegisterMetric(def) as Histogram<string>;
|
||||
if (tags) histogram.observe(tags, value);
|
||||
else histogram.observe(value);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user