package httpd import ( "fmt" "log/slog" "net/http" "runtime/debug" "strings" "time" "github.com/go-chi/chi/v5/middleware" "github.com/aoagents/agent-orchestrator/backend/internal/httpd/envelope" "github.com/aoagents/agent-orchestrator/backend/internal/ports" "github.com/aoagents/agent-orchestrator/backend/internal/telemetrymeta" ) func recoverTelemetry(log *slog.Logger, sink ports.EventSink) func(http.Handler) http.Handler { log = loggerOrDefault(log) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if rec := recover(); rec != nil { stack := string(debug.Stack()) log.Error("http handler panic", "id", middleware.GetReqID(r.Context()), "method", r.Method, "path", r.URL.Path, "panic", fmt.Sprint(rec), "stack", stack, ) if sink != nil { path := telemetrymeta.RoutePattern(r) panicKind := telemetrymeta.PanicKind(rec) sink.Emit(r.Context(), ports.TelemetryEvent{ Name: "ao.daemon.panic", Source: "http", OccurredAt: time.Now().UTC(), Level: ports.TelemetryLevelError, RequestID: middleware.GetReqID(r.Context()), Payload: map[string]any{ "component": "httpd", "operation": "http_request_panic", "method": r.Method, "path": path, "panic_kind": panicKind, "stack_fingerprint": telemetrymeta.Fingerprint("httpd", "http_request_panic", path, panicKind, stack), "fingerprint": telemetrymeta.Fingerprint("httpd", "http_request_panic", r.Method, path, panicKind), }, }) } writeRecoveredError(w, r) } }() next.ServeHTTP(w, r) }) } } func writeRecoveredError(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/") { envelope.WriteAPIError(w, r, http.StatusInternalServerError, "internal_error", "INTERNAL_ERROR", "Internal server error", nil) return } envelope.WriteJSON(w, http.StatusInternalServerError, map[string]any{ "status": "error", }) }