-
Notifications
You must be signed in to change notification settings - Fork 0
fix: wire OTelMiddleware as global HTTP middleware so traces appear in Jaeger #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,11 +48,12 @@ type Route struct { | |
|
|
||
| // StandardHTTPRouter implements both HTTPRouter and http.Handler interfaces | ||
| type StandardHTTPRouter struct { | ||
| name string | ||
| routes []Route | ||
| mu sync.RWMutex | ||
| serverDeps []string // Names of HTTP server modules this router depends on | ||
| serveMux *http.ServeMux | ||
| name string | ||
| routes []Route | ||
| mu sync.RWMutex | ||
| serverDeps []string // Names of HTTP server modules this router depends on | ||
| serveMux *http.ServeMux | ||
| globalMiddlewares []HTTPMiddleware | ||
| } | ||
|
|
||
| // NewStandardHTTPRouter creates a new HTTP router | ||
|
|
@@ -138,6 +139,17 @@ func (r *StandardHTTPRouter) AddRouteWithMiddleware(method, path string, handler | |
| } | ||
| } | ||
|
|
||
| // AddGlobalMiddleware appends a middleware that wraps every request served by | ||
| // this router, regardless of which route is matched. Global middlewares are | ||
| // applied in the order they are added, before any per-route middlewares. | ||
| // This is the correct place to attach cross-cutting concerns such as | ||
| // distributed tracing that must observe all traffic. | ||
| func (r *StandardHTTPRouter) AddGlobalMiddleware(mw HTTPMiddleware) { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
| r.globalMiddlewares = append(r.globalMiddlewares, mw) | ||
| } | ||
|
|
||
| // HasRoute checks if a route with the given method and path already exists | ||
| func (r *StandardHTTPRouter) HasRoute(method, path string) bool { | ||
| r.mu.RLock() | ||
|
|
@@ -150,16 +162,29 @@ func (r *StandardHTTPRouter) HasRoute(method, path string) bool { | |
| return false | ||
| } | ||
|
|
||
| // ServeHTTP implements the http.Handler interface | ||
| // ServeHTTP implements the http.Handler interface. | ||
| // Global middlewares (e.g. OTEL tracing) are applied around the entire mux so | ||
| // every request — including health-checks and pipeline-triggered routes — is | ||
| // instrumented, regardless of how the route was registered. | ||
| func (r *StandardHTTPRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||
| r.mu.RLock() | ||
| defer r.mu.RUnlock() | ||
|
|
||
|
Comment on lines
169
to
172
|
||
| var base http.Handler | ||
| if r.serveMux != nil { | ||
| r.serveMux.ServeHTTP(w, req) | ||
| base = r.serveMux | ||
| } else { | ||
| http.NotFound(w, req) | ||
| base = http.NotFoundHandler() | ||
| } | ||
|
|
||
| // Wrap with global middlewares in reverse registration order so that the | ||
| // first-added middleware is outermost (executes first). | ||
| handler := base | ||
| for i := len(r.globalMiddlewares) - 1; i >= 0; i-- { | ||
| handler = r.globalMiddlewares[i].Process(handler) | ||
| } | ||
|
|
||
| handler.ServeHTTP(w, req) | ||
| } | ||
|
|
||
| // Start compiles all registered routes into the internal ServeMux. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,14 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // modules to the HTTP router. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func wiringHooks() []plugin.WiringHook { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []plugin.WiringHook{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Run at priority 100 (highest) so OTEL wraps all other middleware | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and every request — including health, metrics, and pipeline routes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // registered later — is captured in a trace span. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Name: "observability.otel-middleware", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Priority: 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Hook: wireOTelMiddleware, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Name: "observability.health-endpoints", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Priority: 50, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -132,6 +140,26 @@ func wireLogEndpoint(app modular.Application, _ *config.WorkflowConfig) error { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // wireOTelMiddleware registers any OTelMiddleware instances as global middleware | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // on every available StandardHTTPRouter so that all inbound HTTP requests are | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // wrapped in an OpenTelemetry trace span. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func wireOTelMiddleware(app modular.Application, _ *config.WorkflowConfig) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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) | |
| } | |
| } | |
| 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) | |
| } | |
| } |
There was a problem hiding this comment.
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.