package server import ( "context" "crypto/rand" "encoding/hex" "log/slog" "net/http" "runtime/debug" "time" ) type contextKey int const requestIDKey contextKey = iota type statusRecorder struct { http.ResponseWriter status int } func (recorder *statusRecorder) WriteHeader(status int) { if recorder.status == 0 { recorder.status = status } recorder.ResponseWriter.WriteHeader(status) } func (recorder *statusRecorder) Write(data []byte) (int, error) { if recorder.status == 0 { recorder.status = http.StatusOK } return recorder.ResponseWriter.Write(data) } func (recorder *statusRecorder) Unwrap() http.ResponseWriter { return recorder.ResponseWriter } func (server *Server) observe(next http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { started := time.Now() requestID := newRequestID() writer.Header().Set("X-Request-ID", requestID) request = request.WithContext(context.WithValue(request.Context(), requestIDKey, requestID)) recorder := &statusRecorder{ResponseWriter: writer} defer func() { if recovered := recover(); recovered != nil { if recovered == http.ErrAbortHandler { panic(recovered) } slog.Error("request handler crashed", "request_id", requestID, "method", request.Method, "path", request.URL.Path, "panic", recovered, "stack", string(debug.Stack()), ) if recorder.status == 0 { writeError(recorder, http.StatusInternalServerError, internalErrorMessage(requestID)) } } server.logRequest(request, recorder.status, time.Since(started)) }() next.ServeHTTP(recorder, request) }) } func (server *Server) logRequest(request *http.Request, status int, duration time.Duration) { if status == 0 { status = http.StatusOK } slog.Log(request.Context(), requestLogLevel(request.Method, status), "request", "request_id", requestIDFrom(request), "method", request.Method, "path", request.URL.Path, "status", status, "duration_ms", duration.Milliseconds(), "client", server.clientAddress(request), ) } func requestLogLevel(method string, status int) slog.Level { switch { case status >= http.StatusInternalServerError: return slog.LevelError case status >= http.StatusBadRequest && status != http.StatusUnauthorized: return slog.LevelWarn case method != http.MethodGet && method != http.MethodHead: return slog.LevelInfo default: return slog.LevelDebug } } func requestIDFrom(request *http.Request) string { if requestID, ok := request.Context().Value(requestIDKey).(string); ok { return requestID } return "" } func newRequestID() string { buffer := make([]byte, 6) if _, err := rand.Read(buffer); err != nil { return "unknown" } return hex.EncodeToString(buffer) } func internalErrorMessage(requestID string) string { return "Something went wrong on the server (reference " + requestID + "). The server log has the details." }