Route pattern silently lost for span names and metric labels when auth rebinds the request context

Summary

RoutePatternMiddleware (S01 middleware chain position # 7) reads r.Pattern on the exit path to recover the matched route pattern and propagate it to LabKit logging/tracing via httpserver.SetRoutePattern, giving spans low-cardinality names. The planned HTTP server metrics middleware (S03 metrics framework, #56 (closed), chain position #8 (closed)) follows the same pattern to populate its route metric label.

That read is fragile. Go's net/http.ServeMux stamps r.Pattern on the specific *http.Request value it dispatches. Any middleware positioned inner to #7 (closed)/#8 (closed) that rebinds the request via r = r.WithContext(...) produces a new *http.Request; the mux stamps that inner copy, while #7 (closed)/#8 (closed) still hold the outer copy whose Pattern is "". The matched pattern is silently lost.

Why it is latent today

The current authentication (S08 stub, position #9) and authorization (S09 stub, position #10) middlewares — both inner to #7 (closed)/#8 (closed) — pass the original *http.Request through unchanged (authz is return next; auth calls next.ServeHTTP(w, r) with the original r). So r.Pattern survives the inner chain and both #7 (closed) and #8 (closed) work correctly.

When it breaks

When real authentication replaces the stub and attaches the authenticated identity to the request context — the standard r = r.WithContext(ctxWithIdentity) pattern — route propagation silently breaks:

  • tracing spans get high-cardinality / empty names (the exact problem #7 (closed) exists to prevent);
  • the metrics route label collapses to "" for every request.

No error is raised; the degradation is silent. Latent until real auth lands, hence filed pre-emptively so the new auth implementation is written to avoid it.

Fix direction

Make the matched route pattern survive an inner context rebind, and have both #7 (closed) and #8 (closed) use one shared mechanism. Options:

  • a context-stored pointer holder seeded by an outer middleware and written by an innermost shim wrapping the mux — a pointer reached via ctx.Value survives WithContext, unlike the r.Pattern struct field;
  • have the auth/authz middlewares re-propagate r.Pattern onto the request they forward after rebinding;
  • expose LabKit's own routePatternHolder (currently seeded only inside the tracing middleware, so inert until tracing lands per #57) independent of tracing.

The new authentication implementation should follow whichever pattern is chosen so it does not silently drop the route pattern.

References

Edited by Suleimi Ahmed