fix: replace deprecated otelhttp.WithMetricAttributesFn (#11257)
Use request-scoped otelhttp.Labeler instead, which is the recommended replacement per upstream otelhttp v0.67.0. - add withMetricLabels helper wrapping inner handler - migrate all 4 call sites in commands.go and gateway.go
Marcin Rataj committed
Mar 29, 2026 at 17:33 UTC
a78991e450f8ee7b17a4202ae2dc2a022e4d0868
2 files changed
+20
-13
core/corehttp/commands.go
+1
-3
@@ -146,9 +146,7 @@ func commandsOption(cctx oldcmds.Context, command *cmds.Command) ServeOption {
146
cmdHandler = withAuthSecrets(authorizations, cmdHandler)
147
}
148
149
- cmdHandler = otelhttp.NewHandler(cmdHandler, "corehttp.cmdsHandler",
150
- otelhttp.WithMetricAttributesFn(staticServerDomainAttrFn("api")),
151
- )
149
+ cmdHandler = otelhttp.NewHandler(withMetricLabels(cmdHandler, staticServerDomainAttrFn("api")), "corehttp.cmdsHandler")
150
mux.Handle(APIPath+"/", cmdHandler)
151
return mux, nil
152
}
core/corehttp/gateway.go
+19
-10
@@ -44,11 +44,10 @@ func GatewayOption(paths ...string) ServeOption {
44
45
handler := gateway.NewHandler(config, backend)
46
handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler)
47
- var otelOpts []otelhttp.Option
47
if fn := newServerDomainAttrFn(n); fn != nil {
49
- otelOpts = append(otelOpts, otelhttp.WithMetricAttributesFn(fn))
48
+ handler = withMetricLabels(handler, fn)
49
}
51
- handler = otelhttp.NewHandler(handler, "Gateway", otelOpts...)
50
+ handler = otelhttp.NewHandler(handler, "Gateway")
51
52
for _, p := range paths {
53
mux.Handle(p+"/", handler)
@@ -75,11 +74,10 @@ func HostnameOption() ServeOption {
74
var handler http.Handler
75
handler = gateway.NewHostnameHandler(config, backend, childMux)
76
handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler)
78
- var otelOpts []otelhttp.Option
77
if fn := newServerDomainAttrFn(n); fn != nil {
80
- otelOpts = append(otelOpts, otelhttp.WithMetricAttributesFn(fn))
78
+ handler = withMetricLabels(handler, fn)
79
}
82
- handler = otelhttp.NewHandler(handler, "HostnameGateway", otelOpts...)
80
+ handler = otelhttp.NewHandler(handler, "HostnameGateway")
81
82
mux.Handle("/", handler)
83
return childMux, nil
@@ -131,9 +129,7 @@ func Libp2pGatewayOption() ServeOption {
129
}
130
131
handler := gateway.NewHandler(gwConfig, &offlineGatewayErrWrapper{gwimpl: backend})
134
- handler = otelhttp.NewHandler(handler, "Libp2p-Gateway",
135
- otelhttp.WithMetricAttributesFn(staticServerDomainAttrFn("libp2p")),
136
- )
132
+ handler = otelhttp.NewHandler(withMetricLabels(handler, staticServerDomainAttrFn("libp2p")), "Libp2p-Gateway")
133
134
mux.Handle("/ipfs/", handler)
135
@@ -272,6 +268,19 @@ var defaultPaths = []string{"/ipfs/", "/ipns/", "/p2p/"}
268
// or "other".
269
var serverDomainAttrKey = attribute.Key("server.domain")
270
271
+// withMetricLabels wraps a handler so that otelhttp metric attributes are
272
+// added via the request-scoped [otelhttp.Labeler] instead of the deprecated
273
+// [otelhttp.WithMetricAttributesFn] option. The wrapper must run inside
274
+// [otelhttp.NewHandler] (which injects the labeler into the context).
275
+func withMetricLabels(next http.Handler, fn func(*http.Request) []attribute.KeyValue) http.Handler {
276
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
277
+ if l, ok := otelhttp.LabelerFromContext(r.Context()); ok {
278
+ l.Add(fn(r)...)
279
+ }
280
+ next.ServeHTTP(w, r)
281
+ })
282
+}
283
+
284
// staticServerDomainAttrFn returns a MetricAttributesFn that always returns
285
// a fixed server.domain value. Use for handlers where the domain is known
286
// statically (e.g. "api", "libp2p") to keep the label set consistent across
@@ -281,7 +290,7 @@ func staticServerDomainAttrFn(domain string) func(*http.Request) []attribute.Key
290
return func(*http.Request) []attribute.KeyValue { return attrs }
291
}
292
284
-// newServerDomainAttrFn returns an otelhttp.WithMetricAttributesFn callback
293
+// newServerDomainAttrFn returns an attribute callback for [withMetricLabels]
294
// that adds a server.domain attribute grouping requests by their matching
295
// Gateway.PublicGateways hostname suffix (e.g. "dweb.link", "ipfs.io").
296
// Requests that don't match any configured gateway get "other".