From c8f317720b513c92f0987d99c432f1edb2693648 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 12:50:00 +0100 Subject: [PATCH 01/16] metricParams is protected --- packages/metrics/lib/prometheus/PrometheusMessageMetric.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts b/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts index 1e9e8d7a..28e3ef7f 100644 --- a/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts +++ b/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts @@ -18,7 +18,7 @@ export abstract class PrometheusMessageMetric< protected readonly messageVersionGeneratingFunction: MessageVersionGeneratingFunction - private readonly metricParams: MetricParams + protected readonly metricParams: MetricParams /** * @param metricParams - metrics parameters (see PrometheusMetricParams) From dbf179bffcb16b09f0d550c507b1114afbb1e49f Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 12:57:37 +0100 Subject: [PATCH 02/16] Adding PrometheusMessageCounter --- packages/metrics/lib/index.ts | 1 + .../message-error/PrometheusMessageCounter.ts | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts diff --git a/packages/metrics/lib/index.ts b/packages/metrics/lib/index.ts index 797fc0b2..05484478 100644 --- a/packages/metrics/lib/index.ts +++ b/packages/metrics/lib/index.ts @@ -1,4 +1,5 @@ export * from './MessageMultiMetricManager.ts' +export * from './prometheus/metrics/message-error/PrometheusMessageCounter.ts' export * from './prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts' export * from './prometheus/metrics/message-time/PrometheusMessageLifetimeMetric.ts' export * from './prometheus/metrics/message-time/PrometheusMessageProcessingTimeMetric.ts' diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts new file mode 100644 index 00000000..d7c761f3 --- /dev/null +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts @@ -0,0 +1,54 @@ +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type promClient from 'prom-client' +import type { Counter, LabelValues } from 'prom-client' +import { PrometheusMessageMetric } from '../../PrometheusMessageMetric.ts' +import type { PrometheusMetricParams } from '../../types.ts' + +export type PrometheusMetricCounterParams< + MessagePayload extends object, + Labels extends string = never, +> = PrometheusMetricParams & + ([Labels] extends [never] ? { labelNames?: never[] } : { labelNames: Labels[] }) + +export abstract class PrometheusMessageCounter< + MessagePayload extends object, + Labels extends string = never, +> extends PrometheusMessageMetric< + MessagePayload, + Counter<'queue' | 'messageType' | 'version' | Labels>, + PrometheusMetricCounterParams +> { + protected createMetric( + client: typeof promClient, + metricParams: PrometheusMetricParams, + ): Counter<'queue' | 'messageType' | 'version' | Labels> { + return new client.Counter({ + name: metricParams.name, + help: metricParams.helpDescription, + labelNames: ['queue', 'messageType', 'version', ...(this.metricParams.labelNames ?? [])], + }) + } + + registerProcessedMessage(metadata: ProcessedMessageMetadata): void { + const count = this.calculateCount(metadata) + if (count === null) return + + this.metric.inc( + { + queue: metadata.queueName, + messageType: metadata.messageType, + version: this.messageVersionGeneratingFunction(metadata), + ...this.getLabelValuesForProcessedMessage(metadata), + } as LabelValues<'queue' | 'messageType' | 'version' | Labels>, + count, + ) + } + + protected abstract getLabelValuesForProcessedMessage( + metadata: ProcessedMessageMetadata, + ): LabelValues + + protected abstract calculateCount( + metadata: ProcessedMessageMetadata, + ): number | null +} From 29af2c7b139808231c8f73e0521aa9f510a14bcc Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 12:59:02 +0100 Subject: [PATCH 03/16] Error counter migrated and status fixes --- .../PrometheusMessageErrorCounter.spec.ts | 4 ++ .../PrometheusMessageErrorCounter.ts | 43 +++++++------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts index efe1ab8f..7012c7a3 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts @@ -21,6 +21,7 @@ describe('PrometheusMessageErrorCounter', () => { { name: 'test_metric', helpDescription: 'test description', + labelNames: ['errorReason'], messageVersion: (metadata: ProcessedMessageMetadata) => { registeredMessages.push(metadata) // Mocking it to check if value is registered properly return undefined @@ -84,6 +85,7 @@ describe('PrometheusMessageErrorCounter', () => { { name: 'Test metric', helpDescription: 'test description', + labelNames: ['errorReason'], }, promClient, ) @@ -118,6 +120,7 @@ describe('PrometheusMessageErrorCounter', () => { { name: 'Test metric', helpDescription: 'test description', + labelNames: ['errorReason'], }, promClient, ) @@ -164,6 +167,7 @@ describe('PrometheusMessageErrorCounter', () => { { name: 'Test metric', helpDescription: 'test description', + labelNames: ['errorReason'], messageVersion: (metadata: ProcessedMessageMetadata) => metadata.message?.metadata?.schemaVersion, }, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts index 32bc8c46..cc73aec6 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts @@ -1,37 +1,24 @@ import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' -import type promClient from 'prom-client' -import type { Counter } from 'prom-client' -import { PrometheusMessageMetric } from '../../PrometheusMessageMetric.ts' -import type { PrometheusMetricParams } from '../../types.ts' +import type { LabelValues } from 'prom-client' +import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' export class PrometheusMessageErrorCounter< MessagePayload extends object, -> extends PrometheusMessageMetric< - MessagePayload, - Counter<'queue' | 'messageType' | 'version' | 'errorReason'> -> { - protected createMetric( - client: typeof promClient, - metricParams: PrometheusMetricParams, - ): Counter { - return new client.Counter({ - name: metricParams.name, - help: metricParams.helpDescription, - labelNames: ['queue', 'messageType', 'version', 'errorReason'], - }) +> extends PrometheusMessageCounter { + protected getLabelValuesForProcessedMessage( + metadata: ProcessedMessageMetadata, + ): LabelValues<'errorReason'> { + return { + errorReason: + metadata.processingResult.status === 'error' + ? metadata.processingResult.errorReason + : undefined, + } } - registerProcessedMessage(metadata: ProcessedMessageMetadata): void { - if (metadata.processingResult.status !== 'error') return + protected calculateCount(metadata: ProcessedMessageMetadata): number | null { + if (metadata.processingResult.status !== 'error') return null - this.metric.inc( - { - queue: metadata.queueName, - messageType: metadata.messageType, - errorReason: metadata.processingResult.errorReason, - version: this.messageVersionGeneratingFunction(metadata), - }, - 1, - ) + return 1 } } From 0534fbdbf639918902dcd891d4b19e8c026fa0f1 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 14:19:30 +0100 Subject: [PATCH 04/16] Adding tests --- .../PrometheusMessageCounter.spec.ts | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts new file mode 100644 index 00000000..a575d564 --- /dev/null +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts @@ -0,0 +1,147 @@ +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type { Counter, LabelValues } from 'prom-client' +import * as promClient from 'prom-client' +import { describe, expect, it, vi } from 'vitest' +import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' + +type TestMessage = { + id: string + messageType: 'test' +} + +// Concrete implementation with no custom labels +class TestCounter extends PrometheusMessageCounter { + protected getLabelValuesForProcessedMessage(): LabelValues { + return {} + } + + protected calculateCount(metadata: ProcessedMessageMetadata): number | null { + return metadata.processingResult.status === 'consumed' ? 1 : null + } +} + +// Concrete implementation with custom labels +class TestCounterWithLabels extends PrometheusMessageCounter { + protected getLabelValuesForProcessedMessage( + metadata: ProcessedMessageMetadata, + ): LabelValues<'result'> { + return { result: metadata.processingResult.status } + } + + protected calculateCount(): number | null { + return 1 + } +} + +const mockCounterCalls = () => { + const counterCalls: { labels: Record; value?: number }[] = [] + vi.spyOn(promClient.register, 'getSingleMetric').mockReturnValue({ + inc(labels: Record, value?: number) { + counterCalls.push({ labels, value }) + }, + } as Counter) + return counterCalls +} + +describe('PrometheusMessageCounter', () => { + describe('labelNames', () => { + it('allows omitting labelNames when no custom Labels type is defined', () => { + // No TypeScript error and no runtime error when labelNames is omitted + expect( + () => new TestCounter({ name: 'test_counter', helpDescription: 'test' }, promClient), + ).not.toThrow() + }) + + it('registers only base labels when no custom labels are defined', () => { + // Given + const counterCalls = mockCounterCalls() + const metric = new TestCounter({ name: 'test_counter', helpDescription: 'test' }, promClient) + const message: TestMessage = { id: '1', messageType: 'test' } + + // When + metric.registerProcessedMessage({ + messageId: message.id, + messageType: message.messageType, + processingResult: { status: 'consumed' }, + message, + queueName: 'test-queue', + messageTimestamp: Date.now(), + messageProcessingStartTimestamp: Date.now(), + messageProcessingEndTimestamp: Date.now(), + }) + + // Then + expect(counterCalls).toMatchInlineSnapshot(` + [ + { + "labels": { + "messageType": "test", + "queue": "test-queue", + "version": undefined, + }, + "value": 1, + }, + ] + `) + }) + }) + + it('skips increment when calculateCount returns null', () => { + // Given + const counterCalls = mockCounterCalls() + const metric = new TestCounter({ name: 'test_counter', helpDescription: 'test' }, promClient) + const message: TestMessage = { id: '1', messageType: 'test' } + + // When + metric.registerProcessedMessage({ + messageId: message.id, + messageType: message.messageType, + processingResult: { status: 'error', errorReason: 'invalidMessage' }, + message, + queueName: 'test-queue', + messageTimestamp: Date.now(), + messageProcessingStartTimestamp: Date.now(), + messageProcessingEndTimestamp: Date.now(), + }) + + // Then + expect(counterCalls).toHaveLength(0) + }) + + it('registers custom labels when Labels type is defined', () => { + // Given + const counterCalls = mockCounterCalls() + const metric = new TestCounterWithLabels( + { name: 'test_counter_labels', helpDescription: 'test', labelNames: ['result'] }, + promClient, + ) + const message: TestMessage = { id: '1', messageType: 'test' } + + // When + metric.registerProcessedMessage({ + messageId: message.id, + messageType: message.messageType, + processingResult: { status: 'consumed' }, + message, + queueName: 'test-queue', + messageTimestamp: Date.now(), + messageProcessingStartTimestamp: Date.now(), + messageProcessingEndTimestamp: Date.now(), + }) + + // Then + expect(counterCalls).toMatchInlineSnapshot(` + [ + { + "labels": { + "messageType": "test", + "queue": "test-queue", + "result": "consumed", + "version": undefined, + }, + "value": 1, + }, + ] + `) + }) +}) From aea99b8c17422ae0f89de84b87feb2b5e2a21260 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 14:25:20 +0100 Subject: [PATCH 05/16] Adding PrometheusMessageByStatusCounter --- packages/metrics/lib/index.ts | 1 + .../PrometheusMessageByStatusCounter.spec.ts | 145 ++++++++++++++++++ .../PrometheusMessageByStatusCounter.ts | 19 +++ 3 files changed, 165 insertions(+) create mode 100644 packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts create mode 100644 packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts diff --git a/packages/metrics/lib/index.ts b/packages/metrics/lib/index.ts index 05484478..4d44672e 100644 --- a/packages/metrics/lib/index.ts +++ b/packages/metrics/lib/index.ts @@ -1,4 +1,5 @@ export * from './MessageMultiMetricManager.ts' +export * from './prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts' export * from './prometheus/metrics/message-error/PrometheusMessageCounter.ts' export * from './prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts' export * from './prometheus/metrics/message-time/PrometheusMessageLifetimeMetric.ts' diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts new file mode 100644 index 00000000..c5b88c03 --- /dev/null +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts @@ -0,0 +1,145 @@ +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type { Counter } from 'prom-client' +import * as promClient from 'prom-client' +import { describe, expect, it, vi } from 'vitest' +import { PrometheusMessageByStatusCounter } from './PrometheusMessageByStatusCounter.ts' + +type TestMessage = { + id: string + messageType: 'test' + metadata?: { + schemaVersion: string + } +} + +const mockCounterCalls = () => { + const counterCalls: { labels: Record; value?: number }[] = [] + vi.spyOn(promClient.register, 'getSingleMetric').mockReturnValue({ + inc(labels: Record, value?: number) { + counterCalls.push({ labels, value }) + }, + } as Counter) + return counterCalls +} + +const buildMetadata = ( + message: TestMessage, + overrides?: Partial>, +): ProcessedMessageMetadata => ({ + messageId: message.id, + messageType: message.messageType, + processingResult: { status: 'consumed' }, + message, + queueName: 'test-queue', + messageTimestamp: Date.now(), + messageProcessingStartTimestamp: Date.now(), + messageProcessingEndTimestamp: Date.now(), + ...overrides, +}) + +describe('PrometheusMessageByStatusCounter', () => { + it.each([ + { status: 'consumed' }, + { status: 'published' }, + { status: 'retryLater' }, + { status: 'error', errorReason: 'handlerError' }, + ] as const)('registers resultStatus label for %o', (processingResult) => { + // Given + const counterCalls = mockCounterCalls() + const metric = new PrometheusMessageByStatusCounter( + { name: 'test_metric', helpDescription: 'test', labelNames: ['resultStatus'] }, + promClient, + ) + + // When + metric.registerProcessedMessage( + buildMetadata({ id: '1', messageType: 'test' }, { processingResult }), + ) + + // Then + expect(counterCalls).toHaveLength(1) + expect(counterCalls[0]?.labels).toMatchObject({ resultStatus: processingResult.status }) + expect(counterCalls[0]?.value).toBe(1) + }) + + it('registers base labels alongside resultStatus', () => { + // Given + const counterCalls = mockCounterCalls() + const metric = new PrometheusMessageByStatusCounter( + { name: 'test_metric', helpDescription: 'test', labelNames: ['resultStatus'] }, + promClient, + ) + + // When + metric.registerProcessedMessage( + buildMetadata( + { id: '1', messageType: 'test' }, + { queueName: 'my-queue', processingResult: { status: 'consumed' } }, + ), + ) + + // Then + expect(counterCalls).toMatchInlineSnapshot(` + [ + { + "labels": { + "messageType": "test", + "queue": "my-queue", + "resultStatus": "consumed", + "version": undefined, + }, + "value": 1, + }, + ] + `) + }) + + it('resolves version from message metadata', () => { + // Given + const counterCalls = mockCounterCalls() + const metric = new PrometheusMessageByStatusCounter( + { + name: 'test_metric', + helpDescription: 'test', + labelNames: ['resultStatus'], + messageVersion: (metadata) => metadata.message?.metadata?.schemaVersion, + }, + promClient, + ) + const messages: TestMessage[] = [ + { id: '1', messageType: 'test' }, + { id: '2', messageType: 'test', metadata: { schemaVersion: '2.0.0' } }, + ] + + // When + for (const message of messages) { + metric.registerProcessedMessage( + buildMetadata(message, { processingResult: { status: 'consumed' } }), + ) + } + + // Then + expect(counterCalls).toMatchInlineSnapshot(` + [ + { + "labels": { + "messageType": "test", + "queue": "test-queue", + "resultStatus": "consumed", + "version": undefined, + }, + "value": 1, + }, + { + "labels": { + "messageType": "test", + "queue": "test-queue", + "resultStatus": "consumed", + "version": "2.0.0", + }, + "value": 1, + }, + ] + `) + }) +}) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts new file mode 100644 index 00000000..2fca7bac --- /dev/null +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts @@ -0,0 +1,19 @@ +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type { LabelValues } from 'prom-client' +import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' + +export class PrometheusMessageByStatusCounter< + MessagePayload extends object, +> extends PrometheusMessageCounter { + protected getLabelValuesForProcessedMessage( + metadata: ProcessedMessageMetadata, + ): LabelValues<'resultStatus'> { + return { + resultStatus: metadata.processingResult.status, + } + } + + protected calculateCount(): number | null { + return 1 + } +} From 8389f9b02cd218495b29cda9bb8b2a892e8f2088 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 14:28:28 +0100 Subject: [PATCH 06/16] Update to have backward compatible change --- .../message-error/PrometheusMessageByStatusCounter.ts | 2 +- .../metrics/message-error/PrometheusMessageCounter.ts | 8 +++++--- .../message-error/PrometheusMessageErrorCounter.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts index 2fca7bac..568fc2c4 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts @@ -5,7 +5,7 @@ import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' export class PrometheusMessageByStatusCounter< MessagePayload extends object, > extends PrometheusMessageCounter { - protected getLabelValuesForProcessedMessage( + protected override getLabelValuesForProcessedMessage( metadata: ProcessedMessageMetadata, ): LabelValues<'resultStatus'> { return { diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts index d7c761f3..47848af2 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts @@ -44,9 +44,11 @@ export abstract class PrometheusMessageCounter< ) } - protected abstract getLabelValuesForProcessedMessage( - metadata: ProcessedMessageMetadata, - ): LabelValues + protected getLabelValuesForProcessedMessage( + _metadata: ProcessedMessageMetadata, + ): LabelValues { + return {} as LabelValues + } protected abstract calculateCount( metadata: ProcessedMessageMetadata, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts index cc73aec6..4da29992 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts @@ -5,7 +5,7 @@ import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' export class PrometheusMessageErrorCounter< MessagePayload extends object, > extends PrometheusMessageCounter { - protected getLabelValuesForProcessedMessage( + protected override getLabelValuesForProcessedMessage( metadata: ProcessedMessageMetadata, ): LabelValues<'errorReason'> { return { From 619bc192a99ae96ae10b07655f0a016e1875d163 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 14:30:13 +0100 Subject: [PATCH 07/16] Allowing adding labels on PrometheusMessageTimeMetric --- .../PrometheusMessageTimeMetric.spec.ts | 127 ++++++++++++++++++ .../PrometheusMessageTimeMetric.ts | 35 ++++- 2 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.spec.ts diff --git a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.spec.ts b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.spec.ts new file mode 100644 index 00000000..781783dc --- /dev/null +++ b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.spec.ts @@ -0,0 +1,127 @@ +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type { Histogram, LabelValues } from 'prom-client' +import * as promClient from 'prom-client' +import { describe, expect, it, vi } from 'vitest' +import { PrometheusMessageTimeMetric } from './PrometheusMessageTimeMetric.ts' + +type TestMessage = { + id: string + messageType: 'test' +} + +// Concrete implementation with no custom labels +class TestTimeMetric extends PrometheusMessageTimeMetric { + protected calculateObservedValue(metadata: ProcessedMessageMetadata): number | null { + return metadata.messageProcessingEndTimestamp - metadata.messageProcessingStartTimestamp + } +} + +// Concrete implementation with custom labels +class TestTimeMetricWithLabels extends PrometheusMessageTimeMetric { + protected calculateObservedValue(metadata: ProcessedMessageMetadata): number | null { + return metadata.messageProcessingEndTimestamp - metadata.messageProcessingStartTimestamp + } + + protected override getLabelValuesForProcessedMessage(): LabelValues<'env'> { + return { env: 'production' } + } +} + +const mockObservedValues = () => { + const observedValues: { labels: Record; value: number }[] = [] + vi.spyOn(promClient.register, 'getSingleMetric').mockReturnValue({ + observe(labels: Record, value: number) { + observedValues.push({ labels, value }) + }, + } as Histogram) + return observedValues +} + +const buildMetadata = ( + message: TestMessage, + overrides?: Partial>, +): ProcessedMessageMetadata => ({ + messageId: message.id, + messageType: message.messageType, + processingResult: { status: 'consumed' }, + message, + queueName: 'test-queue', + messageTimestamp: Date.now(), + messageProcessingStartTimestamp: 1000, + messageProcessingEndTimestamp: 1100, + ...overrides, +}) + +describe('PrometheusMessageTimeMetric', () => { + describe('labelNames', () => { + it('allows omitting labelNames when no custom Labels type is defined', () => { + expect( + () => + new TestTimeMetric( + { name: 'test_histogram', helpDescription: 'test', buckets: [10, 50, 100] }, + promClient, + ), + ).not.toThrow() + }) + + it('registers only base labels when no custom labels are defined', () => { + // Given + const observedValues = mockObservedValues() + const metric = new TestTimeMetric( + { name: 'test_histogram', helpDescription: 'test', buckets: [10, 50, 100] }, + promClient, + ) + + // When + metric.registerProcessedMessage(buildMetadata({ id: '1', messageType: 'test' })) + + // Then + expect(observedValues).toMatchInlineSnapshot(` + [ + { + "labels": { + "messageType": "test", + "queue": "test-queue", + "result": "consumed", + "version": undefined, + }, + "value": 100, + }, + ] + `) + }) + }) + + it('registers custom labels alongside base labels', () => { + // Given + const observedValues = mockObservedValues() + const metric = new TestTimeMetricWithLabels( + { + name: 'test_histogram_labels', + helpDescription: 'test', + buckets: [10, 50, 100], + labelNames: ['env'], + }, + promClient, + ) + + // When + metric.registerProcessedMessage(buildMetadata({ id: '1', messageType: 'test' })) + + // Then + expect(observedValues).toMatchInlineSnapshot(` + [ + { + "labels": { + "env": "production", + "messageType": "test", + "queue": "test-queue", + "result": "consumed", + "version": undefined, + }, + "value": 100, + }, + ] + `) + }) +}) diff --git a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts index 0dc29be9..5dbd93cb 100644 --- a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts +++ b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts @@ -1,28 +1,42 @@ import type { MakeRequired } from '@lokalise/universal-ts-utils/node' import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type promClient from 'prom-client' -import type { Histogram } from 'prom-client' +import type { Histogram, LabelValues } from 'prom-client' import { PrometheusMessageMetric } from '../../PrometheusMessageMetric.ts' import type { PrometheusMetricParams } from '../../types.ts' +export type PrometheusMetricTimeParams< + MessagePayload extends object, + Labels extends string = never, +> = MakeRequired, 'buckets'> & + ([Labels] extends [never] ? { labelNames?: never[] } : { labelNames: Labels[] }) + export abstract class PrometheusMessageTimeMetric< MessagePayload extends object, + Labels extends string = never, > extends PrometheusMessageMetric< MessagePayload, - Histogram<'messageType' | 'version' | 'queue' | 'result'>, - MakeRequired, 'buckets'> + Histogram<'messageType' | 'version' | 'queue' | 'result' | Labels>, + PrometheusMetricTimeParams > { protected createMetric( client: typeof promClient, - metricParams: MakeRequired, 'buckets'>, - ): Histogram<'messageType' | 'version' | 'queue' | 'result'> { + metricParams: PrometheusMetricTimeParams, + ): Histogram<'messageType' | 'version' | 'queue' | 'result' | Labels> { return new client.Histogram({ name: metricParams.name, help: metricParams.helpDescription, buckets: metricParams.buckets, - labelNames: ['messageType', 'version', 'queue', 'result'], + labelNames: [ + 'messageType', + 'version', + 'queue', + 'result', + ...(this.metricParams.labelNames ?? []), + ], }) } + registerProcessedMessage(metadata: ProcessedMessageMetadata): void { const observedValue: number | null = this.calculateObservedValue(metadata) @@ -35,11 +49,18 @@ export abstract class PrometheusMessageTimeMetric< version: this.messageVersionGeneratingFunction(metadata), queue: metadata.queueName, result: metadata.processingResult.status, - }, + ...this.getLabelValuesForProcessedMessage(metadata), + } as LabelValues<'messageType' | 'version' | 'queue' | 'result' | Labels>, observedValue, ) } + protected getLabelValuesForProcessedMessage( + _metadata: ProcessedMessageMetadata, + ): LabelValues { + return {} as LabelValues + } + protected abstract calculateObservedValue( metadata: ProcessedMessageMetadata, ): number | null From d47d3c430886714642d0b6ccadf128df831dacb4 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 14:33:01 +0100 Subject: [PATCH 08/16] Readme improvement --- packages/metrics/README.md | 238 ++++++++++++++++++++++++++++++++++--- 1 file changed, 222 insertions(+), 16 deletions(-) diff --git a/packages/metrics/README.md b/packages/metrics/README.md index d610a0fd..4b8271a9 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -1,27 +1,233 @@ # Metrics -This packages contains utilities for collecting metrics in `@message-queue-toolkit` +This package contains utilities for collecting metrics in `@message-queue-toolkit`. + +## Installation + +```sh +npm install @message-queue-toolkit/metrics +``` + +## Overview + +All metrics implement the `MessageMetricsManager` interface from `@message-queue-toolkit/core`, which means they can be passed directly to any `AbstractQueueService` via the `messageMetricsManager` option. + +```ts +import { PrometheusMessageProcessingTimeMetric } from '@message-queue-toolkit/metrics' + +const metric = new PrometheusMessageProcessingTimeMetric({ + name: 'message_processing_duration_ms', + helpDescription: 'Time spent processing a message', + buckets: [10, 50, 100, 500, 1000], +}) + +// Pass to your queue service +const service = new MyQueueService({ messageMetricsManager: metric }) +``` + +--- ## Prometheus metrics -Metrics that use [Prometheus](https://prometheus.io/) toolkit and [prom-client](https://github.com/siimon/prom-client) library +All Prometheus metrics use [prom-client](https://github.com/siimon/prom-client) under the hood. + +### Base parameters + +All metrics accept `PrometheusMetricParams`: + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | `string` | yes | Prometheus metric name | +| `helpDescription` | `string` | yes | Prometheus metric description | +| `buckets` | `number[]` | histograms only | Histogram bucket boundaries | +| `messageVersion` | `string \| (metadata) => string \| undefined` | no | Static version string or function to extract version from message metadata | + +An optional second argument accepts a custom `prom-client` instance (useful for testing or multi-registry setups). + +--- + +### Histogram metrics (time-based) + +Use `Histogram` to measure message timing. Base labels registered on every observation: + +| Label | Value | +|---|---| +| `messageType` | Message type identifier | +| `version` | Resolved message version | +| `queue` | Queue or topic name | +| `result` | Processing result status (`consumed`, `published`, `retryLater`, `error`) | + +#### Built-in implementations + +**`PrometheusMessageProcessingTimeMetric`** +Measures elapsed time from when processing started to when it ended. +``` +value = messageProcessingEndTimestamp - messageProcessingStartTimestamp +``` + +**`PrometheusMessageLifetimeMetric`** +Measures elapsed time from when the message was originally sent to when it was fully processed. Includes any time the message spent waiting in the queue. +``` +value = messageProcessingEndTimestamp - messageTimestamp +``` +Skips observation if `messageTimestamp` is not available. + +**`PrometheusMessageQueueTimeMetric`** +Measures elapsed time from when the message was originally sent to when processing started (i.e., queue wait time only). +``` +value = messageProcessingStartTimestamp - messageTimestamp +``` +Skips observation if `messageTimestamp` is not available. + +#### Custom histogram with extra labels + +Extend `PrometheusMessageTimeMetric` to add custom labels. Pass `labelNames` in the params and override `getLabelValuesForProcessedMessage`: + +```ts +import { PrometheusMessageTimeMetric } from '@message-queue-toolkit/metrics' +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type { LabelValues } from 'prom-client' + +class MyProcessingTimeMetric extends PrometheusMessageTimeMetric { + protected calculateObservedValue(metadata: ProcessedMessageMetadata): number | null { + return metadata.messageProcessingEndTimestamp - metadata.messageProcessingStartTimestamp + } + + protected getLabelValuesForProcessedMessage(): LabelValues<'env'> { + return { env: process.env.NODE_ENV ?? 'unknown' } + } +} + +const metric = new MyProcessingTimeMetric({ + name: 'message_processing_duration_ms', + helpDescription: 'Processing time by environment', + buckets: [10, 50, 100, 500], + labelNames: ['env'], +}) +``` + +--- + +### Counter metrics (event-based) + +Use `Counter` to count message events. Base labels registered on every increment: + +| Label | Value | +|---|---| +| `messageType` | Message type identifier | +| `version` | Resolved message version | +| `queue` | Queue or topic name | + +#### Built-in implementations + +**`PrometheusMessageErrorCounter`** +Counts messages that result in an error. Adds an `errorReason` label. Skips non-error messages. + +```ts +import { PrometheusMessageErrorCounter } from '@message-queue-toolkit/metrics' + +const metric = new PrometheusMessageErrorCounter({ + name: 'message_errors_total', + helpDescription: 'Number of messages that failed processing', + labelNames: ['errorReason'], +}) +``` + +**`PrometheusMessageByStatusCounter`** +Counts all messages, labelled by their processing result status. + +```ts +import { PrometheusMessageByStatusCounter } from '@message-queue-toolkit/metrics' + +const metric = new PrometheusMessageByStatusCounter({ + name: 'messages_by_status_total', + helpDescription: 'Number of messages processed, by result status', + labelNames: ['resultStatus'], +}) +``` + +Adds a `resultStatus` label with values: `consumed`, `published`, `retryLater`, `error`. + +#### Custom counter with extra labels + +Extend `PrometheusMessageCounter` and implement `calculateCount` and `getLabelValuesForProcessedMessage`: + +```ts +import { PrometheusMessageCounter } from '@message-queue-toolkit/metrics' +import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' +import type { LabelValues } from 'prom-client' + +class MyRetryCounter extends PrometheusMessageCounter { + protected calculateCount(metadata: ProcessedMessageMetadata): number | null { + return metadata.processingResult.status === 'retryLater' ? 1 : null + } + + protected getLabelValuesForProcessedMessage( + metadata: ProcessedMessageMetadata, + ): LabelValues<'reason'> { + return { reason: metadata.processingResult.status === 'retryLater' + ? metadata.processingResult.retryReason + : 'unknown' } + } +} + +const metric = new MyRetryCounter({ + name: 'message_retries_total', + helpDescription: 'Number of messages scheduled for retry', + labelNames: ['reason'], +}) +``` + +When no custom labels are needed, omit `labelNames`: + +```ts +class MySimpleCounter extends PrometheusMessageCounter { + protected calculateCount(metadata: ProcessedMessageMetadata): number | null { + return metadata.processingResult.status === 'consumed' ? 1 : null + } + + protected getLabelValuesForProcessedMessage(): LabelValues { + return {} + } +} + +const metric = new MySimpleCounter({ + name: 'messages_consumed_total', + helpDescription: 'Number of successfully consumed messages', +}) +``` -### MessageProcessingPrometheusMetric -Abstract class implementing `MessageMetricsManager` interface, that can be injected into `AbstractQueueService` from `@message-queue-toolkit/core`. +--- -It uses [Histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) metric to collect message processing times with labels: -- `messageType` - message type -- `version` - message version -- `queue` - name of the queue or topic -- `result` - processing result +### Using multiple metrics together -See [MessageProcessingPrometheusMetric.ts](lib/prometheus/MessageProcessingPrometheusMetric.ts) for available parameters. +`MessageMultiMetricManager` aggregates multiple `MessageMetricsManager` instances and fans out each `registerProcessedMessage` call to all of them. -There are following non-abstract implementations available: -- `MessageProcessingTimeMetric` - registers elapsed time from start to the end of message processing -- `MessageLifetimeMetric` - registers elapsed time from the point where message was initially sent, to the point when it was processed. -Note: if message is waiting in the queue due to high load or barrier, the waiting time is included in the measurement +```ts +import { + MessageMultiMetricManager, + PrometheusMessageProcessingTimeMetric, + PrometheusMessageErrorCounter, + PrometheusMessageByStatusCounter, +} from '@message-queue-toolkit/metrics' -### MessageProcessingMultiMetrics -Implementation of `MessageMetricsManager` that allows to use multiple `MessageProcessingPrometheusMetric` instances. +const metricsManager = new MessageMultiMetricManager([ + new PrometheusMessageProcessingTimeMetric({ + name: 'message_processing_duration_ms', + helpDescription: 'Message processing time', + buckets: [10, 50, 100, 500, 1000], + }), + new PrometheusMessageErrorCounter({ + name: 'message_errors_total', + helpDescription: 'Messages that failed processing', + labelNames: ['errorReason'], + }), + new PrometheusMessageByStatusCounter({ + name: 'messages_by_status_total', + helpDescription: 'Messages processed by status', + labelNames: ['resultStatus'], + }), +]) +const service = new MyQueueService({ messageMetricsManager: metricsManager }) +``` From 9fe0b01f7a490236bbaeabf39d1c2bdaf3b46a86 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 14:37:37 +0100 Subject: [PATCH 09/16] Lint fixes --- .../metrics/message-error/PrometheusMessageCounter.spec.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts index a575d564..94ca8b92 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts @@ -11,10 +11,6 @@ type TestMessage = { // Concrete implementation with no custom labels class TestCounter extends PrometheusMessageCounter { - protected getLabelValuesForProcessedMessage(): LabelValues { - return {} - } - protected calculateCount(metadata: ProcessedMessageMetadata): number | null { return metadata.processingResult.status === 'consumed' ? 1 : null } @@ -22,7 +18,7 @@ class TestCounter extends PrometheusMessageCounter { // Concrete implementation with custom labels class TestCounterWithLabels extends PrometheusMessageCounter { - protected getLabelValuesForProcessedMessage( + protected override getLabelValuesForProcessedMessage( metadata: ProcessedMessageMetadata, ): LabelValues<'result'> { return { result: metadata.processingResult.status } From 26cd040a6d4a542b9010a47350371cb43b21fe62 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 15:22:22 +0100 Subject: [PATCH 10/16] Typo fix --- packages/metrics/lib/index.ts | 2 +- ...ts => PrometheusMessageResultCounter.spec.ts} | 16 ++++++++-------- ...nter.ts => PrometheusMessageResultCounter.ts} | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) rename packages/metrics/lib/prometheus/metrics/message-error/{PrometheusMessageByStatusCounter.spec.ts => PrometheusMessageResultCounter.spec.ts} (89%) rename packages/metrics/lib/prometheus/metrics/message-error/{PrometheusMessageByStatusCounter.ts => PrometheusMessageResultCounter.ts} (67%) diff --git a/packages/metrics/lib/index.ts b/packages/metrics/lib/index.ts index 4d44672e..578f98b4 100644 --- a/packages/metrics/lib/index.ts +++ b/packages/metrics/lib/index.ts @@ -1,7 +1,7 @@ export * from './MessageMultiMetricManager.ts' -export * from './prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts' export * from './prometheus/metrics/message-error/PrometheusMessageCounter.ts' export * from './prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts' +export * from './prometheus/metrics/message-error/PrometheusMessageResultCounter.ts' export * from './prometheus/metrics/message-time/PrometheusMessageLifetimeMetric.ts' export * from './prometheus/metrics/message-time/PrometheusMessageProcessingTimeMetric.ts' export * from './prometheus/metrics/message-time/PrometheusMessageQueueTimeMetric.ts' diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts similarity index 89% rename from packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts rename to packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts index c5b88c03..5b051455 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts @@ -2,7 +2,7 @@ import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type { Counter } from 'prom-client' import * as promClient from 'prom-client' import { describe, expect, it, vi } from 'vitest' -import { PrometheusMessageByStatusCounter } from './PrometheusMessageByStatusCounter.ts' +import { PrometheusMessageResultCounter } from './PrometheusMessageResultCounter.ts' type TestMessage = { id: string @@ -37,7 +37,7 @@ const buildMetadata = ( ...overrides, }) -describe('PrometheusMessageByStatusCounter', () => { +describe('PrometheusMessageResultCounter', () => { it.each([ { status: 'consumed' }, { status: 'published' }, @@ -46,8 +46,8 @@ describe('PrometheusMessageByStatusCounter', () => { ] as const)('registers resultStatus label for %o', (processingResult) => { // Given const counterCalls = mockCounterCalls() - const metric = new PrometheusMessageByStatusCounter( - { name: 'test_metric', helpDescription: 'test', labelNames: ['resultStatus'] }, + const metric = new PrometheusMessageResultCounter( + { name: 'test_metric', helpDescription: 'test', labelNames: ['result'] }, promClient, ) @@ -65,8 +65,8 @@ describe('PrometheusMessageByStatusCounter', () => { it('registers base labels alongside resultStatus', () => { // Given const counterCalls = mockCounterCalls() - const metric = new PrometheusMessageByStatusCounter( - { name: 'test_metric', helpDescription: 'test', labelNames: ['resultStatus'] }, + const metric = new PrometheusMessageResultCounter( + { name: 'test_metric', helpDescription: 'test', labelNames: ['result'] }, promClient, ) @@ -97,11 +97,11 @@ describe('PrometheusMessageByStatusCounter', () => { it('resolves version from message metadata', () => { // Given const counterCalls = mockCounterCalls() - const metric = new PrometheusMessageByStatusCounter( + const metric = new PrometheusMessageResultCounter( { name: 'test_metric', helpDescription: 'test', - labelNames: ['resultStatus'], + labelNames: ['result'], messageVersion: (metadata) => metadata.message?.metadata?.schemaVersion, }, promClient, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts similarity index 67% rename from packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts rename to packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts index 568fc2c4..d0dbaeae 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts @@ -2,14 +2,14 @@ import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type { LabelValues } from 'prom-client' import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' -export class PrometheusMessageByStatusCounter< +export class PrometheusMessageResultCounter< MessagePayload extends object, -> extends PrometheusMessageCounter { +> extends PrometheusMessageCounter { protected override getLabelValuesForProcessedMessage( metadata: ProcessedMessageMetadata, - ): LabelValues<'resultStatus'> { + ): LabelValues<'result'> { return { - resultStatus: metadata.processingResult.status, + result: metadata.processingResult.status, } } From feb4a4765643409909dec17bc68ff3db4df51d37 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 15:29:48 +0100 Subject: [PATCH 11/16] AI comment for consistency --- packages/metrics/README.md | 75 ++++++++----------- .../PrometheusMessageCounter.spec.ts | 1 + .../message-error/PrometheusMessageCounter.ts | 15 +++- .../PrometheusMessageErrorCounter.spec.ts | 1 + .../PrometheusMessageResultCounter.spec.ts | 13 ++-- .../PrometheusMessageResultCounter.ts | 12 +-- 6 files changed, 53 insertions(+), 64 deletions(-) diff --git a/packages/metrics/README.md b/packages/metrics/README.md index 4b8271a9..b59b4102 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -117,81 +117,73 @@ Use `Counter` to count message events. Base labels registered on every increment | `messageType` | Message type identifier | | `version` | Resolved message version | | `queue` | Queue or topic name | +| `result` | Processing result status (`consumed`, `published`, `retryLater`, `error`) | #### Built-in implementations -**`PrometheusMessageErrorCounter`** -Counts messages that result in an error. Adds an `errorReason` label. Skips non-error messages. +**`PrometheusMessageResultCounter`** +Counts all processed messages using only the built-in base labels. No extra configuration needed. ```ts -import { PrometheusMessageErrorCounter } from '@message-queue-toolkit/metrics' +import { PrometheusMessageResultCounter } from '@message-queue-toolkit/metrics' -const metric = new PrometheusMessageErrorCounter({ - name: 'message_errors_total', - helpDescription: 'Number of messages that failed processing', - labelNames: ['errorReason'], +const metric = new PrometheusMessageResultCounter({ + name: 'messages_total', + helpDescription: 'Number of messages processed', }) ``` -**`PrometheusMessageByStatusCounter`** -Counts all messages, labelled by their processing result status. +**`PrometheusMessageErrorCounter`** +Counts only messages that result in an error. Adds an `errorReason` label. Skips all non-error messages. ```ts -import { PrometheusMessageByStatusCounter } from '@message-queue-toolkit/metrics' +import { PrometheusMessageErrorCounter } from '@message-queue-toolkit/metrics' -const metric = new PrometheusMessageByStatusCounter({ - name: 'messages_by_status_total', - helpDescription: 'Number of messages processed, by result status', - labelNames: ['resultStatus'], +const metric = new PrometheusMessageErrorCounter({ + name: 'message_errors_total', + helpDescription: 'Number of messages that failed processing', + labelNames: ['errorReason'], }) ``` -Adds a `resultStatus` label with values: `consumed`, `published`, `retryLater`, `error`. - #### Custom counter with extra labels -Extend `PrometheusMessageCounter` and implement `calculateCount` and `getLabelValuesForProcessedMessage`: +Extend `PrometheusMessageCounter` and implement `calculateCount`. Override `getLabelValuesForProcessedMessage` when adding custom labels: ```ts import { PrometheusMessageCounter } from '@message-queue-toolkit/metrics' import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type { LabelValues } from 'prom-client' -class MyRetryCounter extends PrometheusMessageCounter { - protected calculateCount(metadata: ProcessedMessageMetadata): number | null { - return metadata.processingResult.status === 'retryLater' ? 1 : null +class MyRegionCounter extends PrometheusMessageCounter { + protected calculateCount(): number | null { + return 1 } - protected getLabelValuesForProcessedMessage( + protected override getLabelValuesForProcessedMessage( metadata: ProcessedMessageMetadata, - ): LabelValues<'reason'> { - return { reason: metadata.processingResult.status === 'retryLater' - ? metadata.processingResult.retryReason - : 'unknown' } + ): LabelValues<'region'> { + return { region: metadata.message.region } } } -const metric = new MyRetryCounter({ - name: 'message_retries_total', - helpDescription: 'Number of messages scheduled for retry', - labelNames: ['reason'], +const metric = new MyRegionCounter({ + name: 'messages_by_region_total', + helpDescription: 'Number of messages processed, by region', + labelNames: ['region'], }) ``` -When no custom labels are needed, omit `labelNames`: +When no custom labels are needed, omit `labelNames` and skip overriding `getLabelValuesForProcessedMessage`: ```ts -class MySimpleCounter extends PrometheusMessageCounter { +class MyConsumedCounter extends PrometheusMessageCounter { protected calculateCount(metadata: ProcessedMessageMetadata): number | null { return metadata.processingResult.status === 'consumed' ? 1 : null } - - protected getLabelValuesForProcessedMessage(): LabelValues { - return {} - } } -const metric = new MySimpleCounter({ +const metric = new MyConsumedCounter({ name: 'messages_consumed_total', helpDescription: 'Number of successfully consumed messages', }) @@ -207,8 +199,8 @@ const metric = new MySimpleCounter({ import { MessageMultiMetricManager, PrometheusMessageProcessingTimeMetric, + PrometheusMessageResultCounter, PrometheusMessageErrorCounter, - PrometheusMessageByStatusCounter, } from '@message-queue-toolkit/metrics' const metricsManager = new MessageMultiMetricManager([ @@ -217,16 +209,15 @@ const metricsManager = new MessageMultiMetricManager([ helpDescription: 'Message processing time', buckets: [10, 50, 100, 500, 1000], }), + new PrometheusMessageResultCounter({ + name: 'messages_total', + helpDescription: 'Messages processed', + }), new PrometheusMessageErrorCounter({ name: 'message_errors_total', helpDescription: 'Messages that failed processing', labelNames: ['errorReason'], }), - new PrometheusMessageByStatusCounter({ - name: 'messages_by_status_total', - helpDescription: 'Messages processed by status', - labelNames: ['resultStatus'], - }), ]) const service = new MyQueueService({ messageMetricsManager: metricsManager }) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts index 94ca8b92..6b5b4696 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts @@ -73,6 +73,7 @@ describe('PrometheusMessageCounter', () => { "labels": { "messageType": "test", "queue": "test-queue", + "result": "consumed", "version": undefined, }, "value": 1, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts index 47848af2..86ebeebb 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts @@ -15,17 +15,23 @@ export abstract class PrometheusMessageCounter< Labels extends string = never, > extends PrometheusMessageMetric< MessagePayload, - Counter<'queue' | 'messageType' | 'version' | Labels>, + Counter<'queue' | 'messageType' | 'version' | 'result' | Labels>, PrometheusMetricCounterParams > { protected createMetric( client: typeof promClient, metricParams: PrometheusMetricParams, - ): Counter<'queue' | 'messageType' | 'version' | Labels> { + ): Counter<'queue' | 'messageType' | 'version' | 'result' | Labels> { return new client.Counter({ name: metricParams.name, help: metricParams.helpDescription, - labelNames: ['queue', 'messageType', 'version', ...(this.metricParams.labelNames ?? [])], + labelNames: [ + 'queue', + 'messageType', + 'version', + 'result', + ...(this.metricParams.labelNames ?? []), + ], }) } @@ -37,9 +43,10 @@ export abstract class PrometheusMessageCounter< { queue: metadata.queueName, messageType: metadata.messageType, + result: metadata.processingResult.status, version: this.messageVersionGeneratingFunction(metadata), ...this.getLabelValuesForProcessedMessage(metadata), - } as LabelValues<'queue' | 'messageType' | 'version' | Labels>, + } as LabelValues<'queue' | 'messageType' | 'version' | 'result' | Labels>, count, ) } diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts index 7012c7a3..5f61238c 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts @@ -151,6 +151,7 @@ describe('PrometheusMessageErrorCounter', () => { "errorReason": "handlerError", "messageType": "test", "queue": "test-queue", + "result": "error", "version": undefined, }, "value": 1, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts index 5b051455..7951a34a 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts @@ -47,7 +47,7 @@ describe('PrometheusMessageResultCounter', () => { // Given const counterCalls = mockCounterCalls() const metric = new PrometheusMessageResultCounter( - { name: 'test_metric', helpDescription: 'test', labelNames: ['result'] }, + { name: 'test_metric', helpDescription: 'test' }, promClient, ) @@ -58,7 +58,7 @@ describe('PrometheusMessageResultCounter', () => { // Then expect(counterCalls).toHaveLength(1) - expect(counterCalls[0]?.labels).toMatchObject({ resultStatus: processingResult.status }) + expect(counterCalls[0]?.labels).toMatchObject({ result: processingResult.status }) expect(counterCalls[0]?.value).toBe(1) }) @@ -66,7 +66,7 @@ describe('PrometheusMessageResultCounter', () => { // Given const counterCalls = mockCounterCalls() const metric = new PrometheusMessageResultCounter( - { name: 'test_metric', helpDescription: 'test', labelNames: ['result'] }, + { name: 'test_metric', helpDescription: 'test' }, promClient, ) @@ -85,7 +85,7 @@ describe('PrometheusMessageResultCounter', () => { "labels": { "messageType": "test", "queue": "my-queue", - "resultStatus": "consumed", + "result": "consumed", "version": undefined, }, "value": 1, @@ -101,7 +101,6 @@ describe('PrometheusMessageResultCounter', () => { { name: 'test_metric', helpDescription: 'test', - labelNames: ['result'], messageVersion: (metadata) => metadata.message?.metadata?.schemaVersion, }, promClient, @@ -125,7 +124,7 @@ describe('PrometheusMessageResultCounter', () => { "labels": { "messageType": "test", "queue": "test-queue", - "resultStatus": "consumed", + "result": "consumed", "version": undefined, }, "value": 1, @@ -134,7 +133,7 @@ describe('PrometheusMessageResultCounter', () => { "labels": { "messageType": "test", "queue": "test-queue", - "resultStatus": "consumed", + "result": "consumed", "version": "2.0.0", }, "value": 1, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts index d0dbaeae..5f4eb3c3 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts @@ -1,18 +1,8 @@ -import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' -import type { LabelValues } from 'prom-client' import { PrometheusMessageCounter } from './PrometheusMessageCounter.ts' export class PrometheusMessageResultCounter< MessagePayload extends object, -> extends PrometheusMessageCounter { - protected override getLabelValuesForProcessedMessage( - metadata: ProcessedMessageMetadata, - ): LabelValues<'result'> { - return { - result: metadata.processingResult.status, - } - } - +> extends PrometheusMessageCounter { protected calculateCount(): number | null { return 1 } From d9ef3e65e88b2dbe0583bdac0cdae4f3cadacdba Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 16:17:26 +0100 Subject: [PATCH 12/16] AI suggestion to simplify code --- .../lib/prometheus/PrometheusMessageMetric.ts | 7 +++++-- .../message-error/PrometheusMessageCounter.spec.ts | 11 ++++++----- .../message-error/PrometheusMessageCounter.ts | 10 ++-------- .../message-time/PrometheusMessageTimeMetric.ts | 5 ++--- packages/metrics/lib/prometheus/types.ts | 12 ++++++++++-- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts b/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts index 28e3ef7f..191cf42d 100644 --- a/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts +++ b/packages/metrics/lib/prometheus/PrometheusMessageMetric.ts @@ -9,8 +9,11 @@ import type { MessageVersionGeneratingFunction, PrometheusMetricParams } from '. export abstract class PrometheusMessageMetric< MessagePayload extends object, MetricType extends Metric, - MetricParams extends - PrometheusMetricParams = PrometheusMetricParams, + Labels extends string = never, + MetricParams extends PrometheusMetricParams = PrometheusMetricParams< + MessagePayload, + Labels + >, > implements MessageMetricsManager { /** Fallbacks to null if metrics are disabled on app level */ diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts index 6b5b4696..47fe260c 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts @@ -17,11 +17,11 @@ class TestCounter extends PrometheusMessageCounter { } // Concrete implementation with custom labels -class TestCounterWithLabels extends PrometheusMessageCounter { +class TestCounterWithLabels extends PrometheusMessageCounter { protected override getLabelValuesForProcessedMessage( - metadata: ProcessedMessageMetadata, - ): LabelValues<'result'> { - return { result: metadata.processingResult.status } + _metadata: ProcessedMessageMetadata, + ): LabelValues<'test'> { + return { test: 'test' } } protected calculateCount(): number | null { @@ -109,7 +109,7 @@ describe('PrometheusMessageCounter', () => { // Given const counterCalls = mockCounterCalls() const metric = new TestCounterWithLabels( - { name: 'test_counter_labels', helpDescription: 'test', labelNames: ['result'] }, + { name: 'test_counter_labels', helpDescription: 'test', labelNames: ['test'] }, promClient, ) const message: TestMessage = { id: '1', messageType: 'test' } @@ -134,6 +134,7 @@ describe('PrometheusMessageCounter', () => { "messageType": "test", "queue": "test-queue", "result": "consumed", + "test": "test", "version": undefined, }, "value": 1, diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts index 86ebeebb..6b9b6b49 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts @@ -4,23 +4,17 @@ import type { Counter, LabelValues } from 'prom-client' import { PrometheusMessageMetric } from '../../PrometheusMessageMetric.ts' import type { PrometheusMetricParams } from '../../types.ts' -export type PrometheusMetricCounterParams< - MessagePayload extends object, - Labels extends string = never, -> = PrometheusMetricParams & - ([Labels] extends [never] ? { labelNames?: never[] } : { labelNames: Labels[] }) - export abstract class PrometheusMessageCounter< MessagePayload extends object, Labels extends string = never, > extends PrometheusMessageMetric< MessagePayload, Counter<'queue' | 'messageType' | 'version' | 'result' | Labels>, - PrometheusMetricCounterParams + Labels > { protected createMetric( client: typeof promClient, - metricParams: PrometheusMetricParams, + metricParams: PrometheusMetricParams, ): Counter<'queue' | 'messageType' | 'version' | 'result' | Labels> { return new client.Counter({ name: metricParams.name, diff --git a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts index 5dbd93cb..aabf2f3f 100644 --- a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts +++ b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts @@ -1,4 +1,3 @@ -import type { MakeRequired } from '@lokalise/universal-ts-utils/node' import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type promClient from 'prom-client' import type { Histogram, LabelValues } from 'prom-client' @@ -8,8 +7,7 @@ import type { PrometheusMetricParams } from '../../types.ts' export type PrometheusMetricTimeParams< MessagePayload extends object, Labels extends string = never, -> = MakeRequired, 'buckets'> & - ([Labels] extends [never] ? { labelNames?: never[] } : { labelNames: Labels[] }) +> = PrometheusMetricParams & { buckets: number[] } export abstract class PrometheusMessageTimeMetric< MessagePayload extends object, @@ -17,6 +15,7 @@ export abstract class PrometheusMessageTimeMetric< > extends PrometheusMessageMetric< MessagePayload, Histogram<'messageType' | 'version' | 'queue' | 'result' | Labels>, + Labels, PrometheusMetricTimeParams > { protected createMetric( diff --git a/packages/metrics/lib/prometheus/types.ts b/packages/metrics/lib/prometheus/types.ts index 3d21b43c..24180488 100644 --- a/packages/metrics/lib/prometheus/types.ts +++ b/packages/metrics/lib/prometheus/types.ts @@ -3,7 +3,7 @@ import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' /** * Parameters used for registering message processing metrics in Prometheus */ -export type PrometheusMetricParams = { +export type PrometheusMetricParams = { /** * Prometheus metric name */ @@ -23,8 +23,16 @@ export type PrometheusMetricParams = { * Message version used as a label - can be static string or method resolving version based on payload */ messageVersion?: string | MessageVersionGeneratingFunction -} +} & LabelNames export type MessageVersionGeneratingFunction = ( messageMetadata: ProcessedMessageMetadata, ) => string | undefined + +export type DefaultLabels = 'queue' | 'messageType' | 'version' | 'result' + +type LabelNames = [Labels] extends [never] + ? { labelNames?: never[] } + : [Extract] extends [never] + ? { labelNames: Labels[] } + : never From bd5efb98d9ee7a8f628a42eb057631748584da15 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 16:26:21 +0100 Subject: [PATCH 13/16] Symplifying type --- .../message-error/PrometheusMessageCounter.ts | 16 +++++----------- .../message-time/PrometheusMessageTimeMetric.ts | 8 ++++---- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts index 6b9b6b49..067ab7b5 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts @@ -2,30 +2,24 @@ import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type promClient from 'prom-client' import type { Counter, LabelValues } from 'prom-client' import { PrometheusMessageMetric } from '../../PrometheusMessageMetric.ts' -import type { PrometheusMetricParams } from '../../types.ts' +import type { DefaultLabels, PrometheusMetricParams } from '../../types.ts' export abstract class PrometheusMessageCounter< MessagePayload extends object, Labels extends string = never, > extends PrometheusMessageMetric< MessagePayload, - Counter<'queue' | 'messageType' | 'version' | 'result' | Labels>, + Counter, Labels > { protected createMetric( client: typeof promClient, metricParams: PrometheusMetricParams, - ): Counter<'queue' | 'messageType' | 'version' | 'result' | Labels> { + ): Counter { return new client.Counter({ name: metricParams.name, help: metricParams.helpDescription, - labelNames: [ - 'queue', - 'messageType', - 'version', - 'result', - ...(this.metricParams.labelNames ?? []), - ], + labelNames: ['queue', 'messageType', 'version', 'result', ...(this.metricParams.labelNames ?? [])], }) } @@ -40,7 +34,7 @@ export abstract class PrometheusMessageCounter< result: metadata.processingResult.status, version: this.messageVersionGeneratingFunction(metadata), ...this.getLabelValuesForProcessedMessage(metadata), - } as LabelValues<'queue' | 'messageType' | 'version' | 'result' | Labels>, + } as LabelValues, count, ) } diff --git a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts index aabf2f3f..ce01681e 100644 --- a/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts +++ b/packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts @@ -2,7 +2,7 @@ import type { ProcessedMessageMetadata } from '@message-queue-toolkit/core' import type promClient from 'prom-client' import type { Histogram, LabelValues } from 'prom-client' import { PrometheusMessageMetric } from '../../PrometheusMessageMetric.ts' -import type { PrometheusMetricParams } from '../../types.ts' +import type { DefaultLabels, PrometheusMetricParams } from '../../types.ts' export type PrometheusMetricTimeParams< MessagePayload extends object, @@ -14,14 +14,14 @@ export abstract class PrometheusMessageTimeMetric< Labels extends string = never, > extends PrometheusMessageMetric< MessagePayload, - Histogram<'messageType' | 'version' | 'queue' | 'result' | Labels>, + Histogram, Labels, PrometheusMetricTimeParams > { protected createMetric( client: typeof promClient, metricParams: PrometheusMetricTimeParams, - ): Histogram<'messageType' | 'version' | 'queue' | 'result' | Labels> { + ): Histogram { return new client.Histogram({ name: metricParams.name, help: metricParams.helpDescription, @@ -49,7 +49,7 @@ export abstract class PrometheusMessageTimeMetric< queue: metadata.queueName, result: metadata.processingResult.status, ...this.getLabelValuesForProcessedMessage(metadata), - } as LabelValues<'messageType' | 'version' | 'queue' | 'result' | Labels>, + } as LabelValues, observedValue, ) } From 47f1b24afc08a7b4f375c8d6d1e94d2e836a3837 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 16:26:28 +0100 Subject: [PATCH 14/16] readme updated --- packages/metrics/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/metrics/README.md b/packages/metrics/README.md index b59b4102..74d0ab1c 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -41,6 +41,7 @@ All metrics accept `PrometheusMetricParams`: | `helpDescription` | `string` | yes | Prometheus metric description | | `buckets` | `number[]` | histograms only | Histogram bucket boundaries | | `messageVersion` | `string \| (metadata) => string \| undefined` | no | Static version string or function to extract version from message metadata | +| `labelNames` | `Labels[]` | when `Labels` is specified | Names of the custom labels to register. Must not overlap with `DefaultLabels` (`queue`, `messageType`, `version`, `result`) — TypeScript enforces this at compile time | An optional second argument accepts a custom `prom-client` instance (useful for testing or multi-registry setups). @@ -81,7 +82,7 @@ Skips observation if `messageTimestamp` is not available. #### Custom histogram with extra labels -Extend `PrometheusMessageTimeMetric` to add custom labels. Pass `labelNames` in the params and override `getLabelValuesForProcessedMessage`: +Extend `PrometheusMessageTimeMetric` to add custom labels. Pass `labelNames` in the params and override `getLabelValuesForProcessedMessage`. Custom label names must not conflict with `DefaultLabels` — using a reserved name (e.g. `'result'`) will produce a TypeScript compile error: ```ts import { PrometheusMessageTimeMetric } from '@message-queue-toolkit/metrics' @@ -148,7 +149,7 @@ const metric = new PrometheusMessageErrorCounter({ #### Custom counter with extra labels -Extend `PrometheusMessageCounter` and implement `calculateCount`. Override `getLabelValuesForProcessedMessage` when adding custom labels: +Extend `PrometheusMessageCounter` and implement `calculateCount`. Override `getLabelValuesForProcessedMessage` when adding custom labels. Same as histograms, custom label names must not conflict with `DefaultLabels`: ```ts import { PrometheusMessageCounter } from '@message-queue-toolkit/metrics' From ac0a0c0cb67da235b1aa02d2476944039406fe15 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 16:28:47 +0100 Subject: [PATCH 15/16] lint fix --- .../message-error/PrometheusMessageCounter.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts index 067ab7b5..7c9fa5fd 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts @@ -7,11 +7,7 @@ import type { DefaultLabels, PrometheusMetricParams } from '../../types.ts' export abstract class PrometheusMessageCounter< MessagePayload extends object, Labels extends string = never, -> extends PrometheusMessageMetric< - MessagePayload, - Counter, - Labels -> { +> extends PrometheusMessageMetric, Labels> { protected createMetric( client: typeof promClient, metricParams: PrometheusMetricParams, @@ -19,7 +15,13 @@ export abstract class PrometheusMessageCounter< return new client.Counter({ name: metricParams.name, help: metricParams.helpDescription, - labelNames: ['queue', 'messageType', 'version', 'result', ...(this.metricParams.labelNames ?? [])], + labelNames: [ + 'queue', + 'messageType', + 'version', + 'result', + ...(this.metricParams.labelNames ?? []), + ], }) } From 3bc6389f5133ff0a8ca675e91286d90a9f7dcc66 Mon Sep 17 00:00:00 2001 From: CarlosGamero Date: Mon, 16 Mar 2026 16:30:48 +0100 Subject: [PATCH 16/16] Test fix --- .../metrics/message-error/PrometheusMessageErrorCounter.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts index 5f61238c..52ed7db3 100644 --- a/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts +++ b/packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts @@ -215,6 +215,7 @@ describe('PrometheusMessageErrorCounter', () => { "errorReason": "retryLaterExceeded", "messageType": "test", "queue": "test-queue", + "result": "error", "version": undefined, }, "value": 1, @@ -224,6 +225,7 @@ describe('PrometheusMessageErrorCounter', () => { "errorReason": "retryLaterExceeded", "messageType": "test", "queue": "test-queue", + "result": "error", "version": "1.0.0", }, "value": 1,