Skip to content

Add Labeler for custom metric attributes#204

Merged
emcfarlane merged 8 commits intoconnectrpc:mainfrom
michaljurecko:add-labeler-and-metric-attributes-fn
Mar 18, 2026
Merged

Add Labeler for custom metric attributes#204
emcfarlane merged 8 commits intoconnectrpc:mainfrom
michaljurecko:add-labeler-and-metric-attributes-fn

Conversation

@michaljurecko
Copy link
Copy Markdown
Contributor

@michaljurecko michaljurecko commented Feb 8, 2026

Summary

Add a Labeler type — a context-bound, mutex-protected attribute accumulator that handlers can use to add custom metric attributes during request processing. Accessed via LabelerFromContext / ContextWithLabeler.

This follows the same pattern as otelhttp.Labeler.

  • Attributes are added only to metrics, not to spans — metric attributes directly affect time series cardinality, so they must be controlled independently from trace span attributes
  • Works for unary and streaming RPCs (both client and server)
  • The interceptor injects a Labeler into the context automatically if one isn't already present
  • Backward-compatible, additive change — no existing APIs are modified

Why Labeler and not WithMetricAttributesFn

The upstream otelhttp library had both Labeler and WithMetricAttributesFn. After discussion, WithMetricAttributesFn was deprecated in favor of Labeler. This PR follows that decision and only adds Labeler.

Motivation

We need to add custom attributes (e.g. product.variant) to the standard metrics (rpc.server.duration, etc.) to distinguish between product variants. Defining entirely custom metrics would be unnecessarily verbose when the standard metrics already capture the right measurements — they just lack the ability to carry custom attributes.

The otelhttp instrumentation library solves this exact problem with its Labeler. This PR adapts the same pattern for ConnectRPC.

Relates to #199.

@michaljurecko michaljurecko force-pushed the add-labeler-and-metric-attributes-fn branch from 14817da to 23553fe Compare February 8, 2026 08:17
@michaljurecko michaljurecko marked this pull request as ready for review February 8, 2026 08:18
@michaljurecko michaljurecko force-pushed the add-labeler-and-metric-attributes-fn branch from 23553fe to 89accf0 Compare February 8, 2026 20:03
@michaljurecko
Copy link
Copy Markdown
Contributor Author

I added additional tests for the streaming client: TestLabelerStreamingClient and TestMetricAttributesFnStreamingClient.

@michaljurecko
Copy link
Copy Markdown
Contributor Author

Hi @jhump, could you please take a look when you have a moment?

I mainly want to know if this kind of change is acceptable for you,
or if we should look for another workaround instead (we don’t want to maintain our own fork).

@stefanvanburen
Copy link
Copy Markdown
Member

drive-by as I have no standing here, but I noticed that upstream they're considering if the Labeler API is going to be deprecated (or doing something else entirely): open-telemetry/opentelemetry-go-contrib#8402

@michaljurecko michaljurecko force-pushed the add-labeler-and-metric-attributes-fn branch from 7b9c64a to d00f1d3 Compare February 12, 2026 07:48
@michaljurecko
Copy link
Copy Markdown
Contributor Author

michaljurecko commented Feb 12, 2026

@stefanvanburen Thank you for the feedback! Since the Labeler API appears to be under reconsideration upstream (open-telemetry/opentelemetry-go-contrib#8402), I've removed it from this PR and kept only WithMetricAttributesFn, which is uncontroversial and widely adopted across OTel instrumentation libraries.

@michaljurecko michaljurecko changed the title Add Labeler and WithMetricAttributesFn for custom metric attributes Add WithMetricAttributesFn for custom metric attributes Feb 16, 2026
@michaljurecko
Copy link
Copy Markdown
Contributor Author

@timostamm Would it be possible to move forward with this PR please?

@stefanvanburen
Copy link
Copy Markdown
Member

@michaljurecko once again, not a maintainer, but it looks like upstream they've now reversed their decision and suggest they're going towards the Labeler API and removing WithMetricAttributesFn. Would it make more sense to wait to see how the upstream shakes out?

@michaljurecko
Copy link
Copy Markdown
Contributor Author

michaljurecko commented Feb 20, 2026

@stefanvanburen ok, I didn't notice that the discussion took the opposite direction there yesterday.

open-telemetry/opentelemetry-go-contrib#8402 (comment)

From the SIG meeting: the Labeler provides things that WithMetricAttributesFn can't do, but the opposite isn't true. So we could deprecate and remove WithMetricAttributesFn, and use only the Labeler

@michaljurecko
Copy link
Copy Markdown
Contributor Author

open-telemetry/opentelemetry-go-contrib#8587

merged: otelhttp: Deprecate WithMetricAttributesFn in favor of Labeler

I will update this PR.

@michaljurecko michaljurecko force-pushed the add-labeler-and-metric-attributes-fn branch from d00f1d3 to 75e472f Compare March 5, 2026 06:38
@michaljurecko michaljurecko changed the title Add WithMetricAttributesFn for custom metric attributes Add Labeler for custom metric attributes Mar 5, 2026
@michaljurecko
Copy link
Copy Markdown
Contributor Author

Hi @stefanvanburen, I've updated the PR based on the discussion. The upstream otelhttp has since deprecated WithMetricAttributesFn in favor of Labeler, so this PR now only adds the Labeler API. Force-pushed with a clean commit history.

Introduce a Labeler type that allows instrumented ConnectRPC handlers
to add custom attributes to metrics during request processing. The
labeler is stored in context and provides thread-safe Add/Get methods.

This follows the same pattern as otelhttp's Labeler implementation.

Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
Inject the Labeler into the request context at the start of each RPC
(unary, streaming client, streaming handler). Separate metric attributes
from span attributes so that Labeler attributes are added only to
metrics, not to spans.

The streamingState now carries a reference to the Labeler and uses
metricAttributes() for all metric recordings.

Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
@michaljurecko michaljurecko force-pushed the add-labeler-and-metric-attributes-fn branch from 75e472f to 388f7c4 Compare March 5, 2026 06:41
Copy link
Copy Markdown
Collaborator

@emcfarlane emcfarlane left a comment

Choose a reason for hiding this comment

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

Thanks! Looking good some comments around structure.

Comment thread streaming.go Outdated
Comment thread labeler.go Outdated
Comment thread interceptor.go
Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
@michaljurecko michaljurecko requested a review from emcfarlane March 6, 2026 05:05
Copy link
Copy Markdown
Collaborator

@emcfarlane emcfarlane left a comment

Choose a reason for hiding this comment

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

Ran a benchmark to check impact on perf (with the unary fix applied):

goos: darwin
goarch: arm64
pkg: connectrpc.com/otelconnect
cpu: Apple M2
                           │   old.txt    │              new.txt               │
                           │    sec/op    │    sec/op     vs base              │
StreamingBase-8              63.94µ ± 12%   61.80µ ± 26%       ~ (p=0.937 n=6)
StreamingWithInterceptor-8   62.85µ ±  2%   65.00µ ±  3%  +3.42% (p=0.009 n=6)
UnaryBase-8                  37.59µ ± 21%   37.78µ ±  2%       ~ (p=0.818 n=6)
UnaryWithInterceptor-8       38.11µ ±  2%   38.93µ ±  9%  +2.16% (p=0.002 n=6)
geomean                      48.98µ         49.30µ        +0.65%

                           │   old.txt    │              new.txt               │
                           │     B/op     │     B/op      vs base              │
StreamingBase-8              20.10Ki ± 8%   20.68Ki ± 7%       ~ (p=0.180 n=6)
StreamingWithInterceptor-8   27.42Ki ± 5%   27.76Ki ± 4%       ~ (p=0.394 n=6)
UnaryBase-8                  17.30Ki ± 4%   17.45Ki ± 5%       ~ (p=0.818 n=6)
UnaryWithInterceptor-8       20.21Ki ± 2%   20.43Ki ± 5%       ~ (p=0.240 n=6)
geomean                      20.95Ki        21.27Ki       +1.52%

                           │  old.txt   │              new.txt               │
                           │ allocs/op  │ allocs/op   vs base                │
StreamingBase-8              172.5 ± 0%   173.0 ± 1%       ~ (p=0.545 n=6)
StreamingWithInterceptor-8   234.0 ± 0%   238.0 ± 0%  +1.71% (p=0.002 n=6)
UnaryBase-8                  152.0 ± 0%   152.0 ± 0%       ~ (p=1.000 n=6) ¹
UnaryWithInterceptor-8       190.0 ± 0%   194.0 ± 0%  +2.11% (p=0.002 n=6)
geomean                      184.8        186.7       +1.02%
¹ all samples are equal

@pkwarren ok?

Comment thread interceptor.go Outdated
Comment thread labeler.go Outdated
@emcfarlane emcfarlane requested a review from pkwarren March 10, 2026 17:38
More idiomatic Go per context.WithValue docs.

Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
Skip slices.Clone + concat when the labeler has no custom attributes,
matching the fast-path already used in streaming's metricAttributes().

Signed-off-by: Michal Jurecko <michal.jurecko@gmail.com>
@michaljurecko michaljurecko force-pushed the add-labeler-and-metric-attributes-fn branch from f09b011 to 44d81e5 Compare March 10, 2026 19:13
@emcfarlane emcfarlane merged commit 91897cf into connectrpc:main Mar 18, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants