Skip to content

fix: wire OTelMiddleware as global HTTP middleware so traces appear in Jaeger#160

Merged
intel352 merged 1 commit intomainfrom
fix/otel-middleware-global-wiring
Feb 25, 2026
Merged

fix: wire OTelMiddleware as global HTTP middleware so traces appear in Jaeger#160
intel352 merged 1 commit intomainfrom
fix/otel-middleware-global-wiring

Conversation

@intel352
Copy link
Contributor

Summary

  • OTelMiddleware.Process() was never called — the module was instantiated but had no path into the HTTP handler chain, so no traces were emitted.
  • Added StandardHTTPRouter.AddGlobalMiddleware() which wraps the entire mux before dispatching, ensuring every request (health probes, metrics, pipeline triggers, static files) gets a trace span.
  • Added observability.otel-middleware wiring hook (priority 100) that runs at startup and registers any OTelMiddleware found in the service registry as a global middleware on every StandardHTTPRouter.

Root cause

StandardHTTPRouter.ServeHTTP delegated directly to the inner serveMux with no outer middleware chain. Per-route middlewares (via AddRouteWithMiddleware) were available, but the OTEL middleware module had no wiring hook to attach itself — it just registered as a service and sat unused.

Changes

File Change
module/http_router.go Add globalMiddlewares []HTTPMiddleware field; AddGlobalMiddleware() method; ServeHTTP applies global chain before mux
plugins/observability/wiring.go New wireOTelMiddleware hook at priority 100
plugins/observability/plugin.go Register observability.otel-middleware in manifest WiringHooks
plugins/observability/plugin_test.go Update counts: 6 module types, 5 wiring hooks

Test plan

  • go test ./... — all packages pass
  • Tag v0.2.4, update workflow-cloud to use it, cross-compile and deploy to minikube
  • Hit endpoints via curl http://localhost:8080/...
  • Confirm workflow-cloud appears in curl -s http://localhost:16686/api/services

🤖 Generated with Claude Code

…n Jaeger

The `http.middleware.otel` module was instantiated but its `Process()` method
was never called — it had no path into the HTTP handler chain.

Two changes fix this:

1. `StandardHTTPRouter.AddGlobalMiddleware()` — new method that appends a
   middleware to a per-router slice. `ServeHTTP` now wraps the inner mux with
   all registered global middlewares before dispatching, so every request
   (health, metrics, pipeline triggers, and static routes) is observed.

2. `observability.otel-middleware` wiring hook (priority 100) — runs at
   startup and calls `AddGlobalMiddleware` for every `OTelMiddleware` found in
   the service registry, connecting it to every `StandardHTTPRouter`.

The observability plugin manifest and tests are updated to reflect the new
wiring hook (5 total) and the already-present `http.middleware.otel` module
type (6 total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 03:45
@intel352 intel352 merged commit 9251834 into main Feb 25, 2026
12 checks passed
@intel352 intel352 deleted the fix/otel-middleware-global-wiring branch February 25, 2026 03:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR wires the OpenTelemetry HTTP middleware into the request handling path by adding support for router-level “global” middleware and registering the OTEL middleware on all StandardHTTPRouter instances via an observability wiring hook, so inbound requests produce trace spans (e.g., visible in Jaeger).

Changes:

  • Add StandardHTTPRouter.AddGlobalMiddleware() and apply globalMiddlewares around the entire mux in ServeHTTP.
  • Add a new observability wiring hook (observability.otel-middleware, priority 100) to register OTelMiddleware globally on all routers.
  • Update observability plugin manifest/tests to include the new wiring hook and module type counts.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
module/http_router.go Adds global middleware support and wraps the router’s mux with a global middleware chain in ServeHTTP.
plugins/observability/wiring.go Introduces wireOTelMiddleware wiring hook to attach OTelMiddleware to all StandardHTTPRouters.
plugins/observability/plugin.go Registers the new wiring hook name in the observability plugin manifest.
plugins/observability/plugin_test.go Updates manifest/module type/wiring hook count expectations and hook name list.

Comment on lines 169 to 172
func (r *StandardHTTPRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.mu.RLock()
defer r.mu.RUnlock()

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

ServeHTTP holds r.mu.RLock for the entire request (due to the deferred RUnlock) and then calls middleware Process() and handler.ServeHTTP while the lock is held. This risks self-deadlock if any handler/middleware tries to mutate the router (e.g., AddRouteWithMiddleware/AddGlobalMiddleware) and also blocks hot-add route updates until the request completes. Consider copying serveMux and globalMiddlewares under the lock, releasing the lock, then building the middleware chain and serving the request without holding r.mu.

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +150
defer r.mu.Unlock()
r.globalMiddlewares = append(r.globalMiddlewares, mw)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

AddGlobalMiddleware/ServeHTTP introduce new global middleware behavior, but there are existing router tests (module/http_test.go) that don't currently validate that global middlewares run (and run in the expected order) for matched routes and 404s. Adding unit tests for AddGlobalMiddleware + ServeHTTP would help prevent regressions in tracing and other cross-cutting middleware.

Suggested change
defer r.mu.Unlock()
r.globalMiddlewares = append(r.globalMiddlewares, mw)
defer r.mu.Unlock()
r.globalMiddlewares = append(r.globalMiddlewares, mw)
// Rebuild the mux if we've already started so that newly added global
// middlewares take effect for all subsequent requests (hot-add support).
if r.serveMux != nil {
r.rebuildMuxLocked()
}

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +159
for _, svc := range app.SvcRegistry() {
otelMw, ok := svc.(*module.OTelMiddleware)
if !ok {
continue
}
for _, routerSvc := range app.SvcRegistry() {
router, ok := routerSvc.(*module.StandardHTTPRouter)
if !ok {
continue
}
router.AddGlobalMiddleware(otelMw)
}
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

wireOTelMiddleware currently does a nested iteration over app.SvcRegistry() (OTelMiddleware × routers). Even if the registry is usually small, this is avoidable and makes the hook harder to extend. Consider collecting the routers once (and possibly the OTel middlewares once) and then wiring them in a single pass.

Suggested change
for _, svc := range app.SvcRegistry() {
otelMw, ok := svc.(*module.OTelMiddleware)
if !ok {
continue
}
for _, routerSvc := range app.SvcRegistry() {
router, ok := routerSvc.(*module.StandardHTTPRouter)
if !ok {
continue
}
router.AddGlobalMiddleware(otelMw)
}
}
var (
routers []*module.StandardHTTPRouter
middlewares []*module.OTelMiddleware
)
// Collect all routers and OTel middleware instances in a single pass.
for _, svc := range app.SvcRegistry() {
if router, ok := svc.(*module.StandardHTTPRouter); ok {
routers = append(routers, router)
continue
}
if otelMw, ok := svc.(*module.OTelMiddleware); ok {
middlewares = append(middlewares, otelMw)
}
}
// Register each OTel middleware as global middleware on every router.
for _, router := range routers {
for _, otelMw := range middlewares {
router.AddGlobalMiddleware(otelMw)
}
}

Copilot uses AI. Check for mistakes.
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.

2 participants