Skip to content

feat: Metrics extended labels#414

Merged
CarlosGamero merged 16 commits intomainfrom
feat/metrics_extend_labels
Mar 16, 2026
Merged

feat: Metrics extended labels#414
CarlosGamero merged 16 commits intomainfrom
feat/metrics_extend_labels

Conversation

@CarlosGamero
Copy link
Collaborator

@CarlosGamero CarlosGamero commented Mar 16, 2026

Add custom label support to Prometheus metric base classes

Both PrometheusMessageTimeMetric and PrometheusMessageCounter now accept an optional Labels type parameter that allows subclasses to register additional Prometheus labels beyond the built-in defaults.

The labelNames param is required when Labels is specified, and omitted entirely when it's not, enforced at the type level via a conditional type, so there's no runtime overhead and no breaking change for existing implementations.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for custom labels in Prometheus metrics for flexible data dimensionality.
    • Introduced new counter classes for message result tracking and generic message counting.
  • Documentation

    • Improved README with clearer installation instructions, usage examples, and consolidated metric guidelines.
  • Tests

    • Added comprehensive test coverage for new counter implementations and labeled metric scenarios.

@CarlosGamero CarlosGamero self-assigned this Mar 16, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

The PR refactors the metrics package to support custom labels in Prometheus metrics. It introduces a new PrometheusMessageCounter base class with a generic Labels parameter, refactors existing counter implementations to extend this base, and updates type definitions to enable dynamic label composition alongside default labels.

Changes

Cohort / File(s) Summary
Documentation
packages/metrics/README.md
Rewrote package description, added installation and usage sections, introduced example showing MessageMultiMetricManager integration, and updated Prometheus metrics documentation to reflect consolidated multi-metric approach.
Public API Exports
packages/metrics/lib/index.ts
Exposed two new counter classes: PrometheusMessageCounter and PrometheusMessageResultCounter.
Base Metric Infrastructure
packages/metrics/lib/prometheus/PrometheusMessageMetric.ts, packages/metrics/lib/prometheus/types.ts
Added Labels generic parameter to PrometheusMessageMetric class and PrometheusMetricParams type; changed metricParams field from private to protected; introduced DefaultLabels type and conditional LabelNames mapping for dynamic label composition.
Counter Metrics
packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts, packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts, packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts, packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts, packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts, packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts
Introduced new PrometheusMessageCounter abstract base class with label support; refactored PrometheusMessageErrorCounter to extend it and use calculateCount pattern; added new PrometheusMessageResultCounter class; comprehensive test coverage for all counter variants with and without custom labels.
Time Metrics
packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts, packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.spec.ts
Added generic Labels parameter and PrometheusMetricTimeParams type; introduced getLabelValuesForProcessedMessage hook for custom label composition; updated histogram label construction and observation logic; added comprehensive test suite covering base and labeled configurations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Improved message logging #385 — Updates to ProcessedMessageMetadata shape and logging flow interact with this PR's consumption of that metadata across metric calculation and label composition.

Suggested reviewers

  • kibertoad
  • kjamrog

Poem

🐰 Labels now dance with custom grace,
Prometheus metrics find their place,
Counters count with labels true,
Time flows freely, errors too! ✨
Generic bounds set metrics free! 🎯

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title "feat: Metrics extended labels" directly reflects the main objective of the changeset, which extends the metrics system with custom label support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/metrics_extend_labels
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CarlosGamero CarlosGamero marked this pull request as ready for review March 16, 2026 13:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts (1)

30-36: Minor inconsistency: using this.metricParams instead of the method parameter.

Line 35 accesses this.metricParams.labelNames while the method receives metricParams as a parameter. Although they reference the same object at runtime (due to base class behavior), using the parameter would be more consistent and make the method's data flow clearer.

♻️ Suggested fix
     labelNames: [
       'messageType',
       'version',
       'queue',
       'result',
-      ...(this.metricParams.labelNames ?? []),
+      ...(metricParams.labelNames ?? []),
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts`
around lines 30 - 36, In the labelNames array inside PrometheusMessageTimeMetric
(the method that builds the metric config), replace usage of
this.metricParams.labelNames with the method parameter metricParams.labelNames
to keep the data flow consistent; update the spread to
...(metricParams.labelNames ?? []) so the function consistently uses its
incoming metricParams rather than the instance field.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts`:
- Around line 32-39: The helper mockCounterCalls hides regressions because it
fakes promClient.register.getSingleMetric and prevents createMetric() from ever
constructing a real client.Counter and exposing its labelNames; change the test
to use a real Counter on a fresh/cleared promClient.register or alternatively
spy on client.Counter's constructor so you can capture its constructor args
(labelNames) while still allowing the real Counter behavior; specifically update
mockCounterCalls to clear the registry (or stop mocking
register.getSingleMetric), create or spy-on client.Counter to capture labelNames
and preserve real inc() behavior, and assert against those captured labelNames
in the PrometheusMessageCounter tests.

In
`@packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts`:
- Around line 7-11: The current PrometheusMetricCounterParams type allows
partial or invalid label sets because it types labelNames as Labels[]; change
the API to accept a readonly tuple for labelNames (e.g. labelNames: readonly
[...L]) and derive the union as type Labels = typeof labelNames[number] so
callers must pass a const tuple (const labelNames = [...] as const) to get
precise unions; update PrometheusMetricCounterParams/PrometheusMetricParams
usages to expect a readonly tuple and add a compile-time exclusion for reserved
labels (e.g. require Extract<Labels, Reserved> extends never) and/or enforce
exact coverage by validating the tuple-derived union matches the expected Labels
set.

In `@packages/metrics/README.md`:
- Around line 64-66: Add a language tag ("text") to the fenced code blocks that
contain the three formula snippets so markdownlint stops flagging them; locate
the triple-backtick blocks that wrap the formulas "value =
messageProcessingEndTimestamp - messageProcessingStartTimestamp", "value =
messageProcessingEndTimestamp - messageTimestamp", and "value =
messageProcessingStartTimestamp - messageTimestamp" and change their opening
fences from ``` to ```text, and do the same for the other two identical
occurrences mentioned (the blocks around those formulas at the other locations).
- Around line 165-170: The code reads metadata.processingResult.retryReason
without narrowing to a variant that actually contains that field; change
getLabelValuesForProcessedMessage to first narrow the union (e.g., const pr =
metadata.processingResult; if (pr.status === 'retryLater' && 'retryReason' in
pr) return { reason: pr.retryReason }; else return { reason: 'unknown' }) or
implement an explicit type-guard for the RetryLater variant before accessing
retryReason, or alternatively switch this metric to an error-based counter
instead of a reason label.

---

Nitpick comments:
In
`@packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts`:
- Around line 30-36: In the labelNames array inside PrometheusMessageTimeMetric
(the method that builds the metric config), replace usage of
this.metricParams.labelNames with the method parameter metricParams.labelNames
to keep the data flow consistent; update the spread to
...(metricParams.labelNames ?? []) so the function consistently uses its
incoming metricParams rather than the instance field.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c42800ce-c219-46a2-a64e-da18640a4e7a

📥 Commits

Reviewing files that changed from the base of the PR and between 7b8b4ef and 9fe0b01.

📒 Files selected for processing (11)
  • packages/metrics/README.md
  • packages/metrics/lib/index.ts
  • packages/metrics/lib/prometheus/PrometheusMessageMetric.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageByStatusCounter.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.ts
  • packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts (1)

44-48: Default implementation returns empty object regardless of Labels constraint.

When Labels is not never, subclasses must override getLabelValuesForProcessedMessage to provide values for the registered labelNames. The default implementation returning {} as LabelValues<Labels> works because:

  1. When Labels is never, LabelValues<never> accepts an empty object
  2. When Labels is specified, the type system requires labelNames in params, signaling to developers they must provide values

This is a reasonable API design, but consider adding a JSDoc comment clarifying that subclasses must override this method when specifying custom Labels.

📝 Suggested documentation
+  /**
+   * Override this method to provide values for custom labels.
+   * Must be implemented when the class specifies a non-never Labels type.
+   */
   protected getLabelValuesForProcessedMessage(
     _metadata: ProcessedMessageMetadata<MessagePayload>,
   ): LabelValues<Labels> {
     return {} as LabelValues<Labels>
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts`
around lines 44 - 48, Add a JSDoc comment to the protected method
getLabelValuesForProcessedMessage (in PrometheusMessageCounter) explaining that
the default implementation returns an empty LabelValues and that subclasses MUST
override this method when the generic Labels type is not never to supply values
for any registered labelNames; mention that when Labels is never the empty
object is valid and that implementations should accept a
ProcessedMessageMetadata<MessagePayload> parameter and return
LabelValues<Labels> populated for the metric's labelNames.
packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts (1)

29-35: Label order differs from PrometheusMessageCounter.

PrometheusMessageTimeMetric uses ['messageType', 'version', 'queue', 'result', ...] while PrometheusMessageCounter uses ['queue', 'messageType', 'version', 'result', ...]. While Prometheus doesn't care about label order, inconsistency could confuse developers inspecting raw metrics. Consider aligning the order across metric types.

♻️ Align label order with counter
       labelNames: [
-        'messageType',
-        'version',
         'queue',
+        'messageType',
+        'version',
         'result',
         ...(this.metricParams.labelNames ?? []),
       ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts`
around lines 29 - 35, PrometheusMessageTimeMetric's labelNames array order is
inconsistent with PrometheusMessageCounter; update the labelNames in
PrometheusMessageTimeMetric to match the counter's ordering by putting 'queue'
first (i.e., use ['queue', 'messageType', 'version', 'result',
...(this.metricParams.labelNames ?? [])]) so both metrics share the same label
order; locate the labelNames declaration in PrometheusMessageTimeMetric and
adjust it to match PrometheusMessageCounter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts`:
- Around line 44-48: Add a JSDoc comment to the protected method
getLabelValuesForProcessedMessage (in PrometheusMessageCounter) explaining that
the default implementation returns an empty LabelValues and that subclasses MUST
override this method when the generic Labels type is not never to supply values
for any registered labelNames; mention that when Labels is never the empty
object is valid and that implementations should accept a
ProcessedMessageMetadata<MessagePayload> parameter and return
LabelValues<Labels> populated for the metric's labelNames.

In
`@packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts`:
- Around line 29-35: PrometheusMessageTimeMetric's labelNames array order is
inconsistent with PrometheusMessageCounter; update the labelNames in
PrometheusMessageTimeMetric to match the counter's ordering by putting 'queue'
first (i.e., use ['queue', 'messageType', 'version', 'result',
...(this.metricParams.labelNames ?? [])]) so both metrics share the same label
order; locate the labelNames declaration in PrometheusMessageTimeMetric and
adjust it to match PrometheusMessageCounter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2fd7d78-82d5-418b-b071-2be065e3d13d

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe0b01 and 3bc6389.

📒 Files selected for processing (10)
  • packages/metrics/README.md
  • packages/metrics/lib/index.ts
  • packages/metrics/lib/prometheus/PrometheusMessageMetric.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageErrorCounter.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.spec.ts
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageResultCounter.ts
  • packages/metrics/lib/prometheus/metrics/message-time/PrometheusMessageTimeMetric.ts
  • packages/metrics/lib/prometheus/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/metrics/lib/prometheus/metrics/message-error/PrometheusMessageCounter.spec.ts

@CarlosGamero CarlosGamero merged commit 0044ca8 into main Mar 16, 2026
7 checks passed
@CarlosGamero CarlosGamero deleted the feat/metrics_extend_labels branch March 16, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants