// Path: Backend/internal/httpx/middleware.go package httpx import ( "context" "log/slog" "net/http" "time" "github.com/google/uuid" ) type contextKey string const requestIDContextKey contextKey = "requestID" type statusRecorder struct { http.ResponseWriter statusCode int } func (r *statusRecorder) WriteHeader(statusCode int) { r.statusCode = statusCode r.ResponseWriter.WriteHeader(statusCode) } func RequestID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestID := uuid.NewString() ctx := context.WithValue(r.Context(), requestIDContextKey, requestID) w.Header().Set("X-Request-ID", requestID) next.ServeHTTP(w, r.WithContext(ctx)) }) } func Recoverer(logger *slog.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if recovered := recover(); recovered != nil { requestID := RequestIDFromContext(r.Context()) logger.Error("panic recovered", "request_id", requestID, "panic", recovered, "path", r.URL.Path) WriteError(w, http.StatusInternalServerError, requestID, "internal_error", "An unexpected error occurred.") } }() next.ServeHTTP(w, r) }) } } func RequestLogger(logger *slog.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { startedAt := time.Now() recorder := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK} next.ServeHTTP(recorder, r) logger.Info( "http request", "request_id", RequestIDFromContext(r.Context()), "method", r.Method, "path", r.URL.Path, "status", recorder.statusCode, "duration", time.Since(startedAt).String(), ) }) } } func RequestIDFromContext(ctx context.Context) string { requestID, ok := ctx.Value(requestIDContextKey).(string) if !ok { return "" } return requestID }