Add structured HTTP request logging and increase timeouts
- Replace chi's Logger middleware with structured slog-based logging - Log request start (debug) and completion (info/warn/error by status) - Include method, path, status, duration_ms, remote_addr in logs - Increase request timeout from 8s to 30s for slow queries - Add read/write/idle timeouts to HTTP server config - Better server startup logging to confirm listening state
This commit is contained in:
parent
0ae89c33db
commit
3a9ec98d5c
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -301,3 +302,67 @@ func JSONValidationMiddleware(next http.Handler) http.Handler {
|
|||||||
_ = json.NewEncoder(w).Encode(response)
|
_ = json.NewEncoder(w).Encode(response)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// statusWriter wraps http.ResponseWriter to capture the status code
|
||||||
|
type statusWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
written bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *statusWriter) WriteHeader(statusCode int) {
|
||||||
|
if !sw.written {
|
||||||
|
sw.statusCode = statusCode
|
||||||
|
sw.written = true
|
||||||
|
}
|
||||||
|
sw.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *statusWriter) Write(b []byte) (int, error) {
|
||||||
|
if !sw.written {
|
||||||
|
sw.statusCode = http.StatusOK
|
||||||
|
sw.written = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return sw.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestLoggerMiddleware creates a structured logging middleware using slog.
|
||||||
|
func RequestLoggerMiddleware(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) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Wrap response writer to capture status
|
||||||
|
sw := &statusWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
||||||
|
|
||||||
|
// Log request start
|
||||||
|
logger.Debug("HTTP request started",
|
||||||
|
"method", r.Method,
|
||||||
|
"path", r.URL.Path,
|
||||||
|
"remote_addr", r.RemoteAddr,
|
||||||
|
"user_agent", r.UserAgent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serve the request
|
||||||
|
next.ServeHTTP(sw, r)
|
||||||
|
|
||||||
|
// Log request completion
|
||||||
|
duration := time.Since(start)
|
||||||
|
logLevel := slog.LevelInfo
|
||||||
|
if sw.statusCode >= http.StatusInternalServerError {
|
||||||
|
logLevel = slog.LevelError
|
||||||
|
} else if sw.statusCode >= http.StatusBadRequest {
|
||||||
|
logLevel = slog.LevelWarn
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log(r.Context(), logLevel, "HTTP request completed",
|
||||||
|
"method", r.Method,
|
||||||
|
"path", r.URL.Path,
|
||||||
|
"status", sw.statusCode,
|
||||||
|
"duration_ms", duration.Milliseconds(),
|
||||||
|
"remote_addr", r.RemoteAddr,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ func (s *Server) setupRoutes() {
|
|||||||
// Middleware
|
// Middleware
|
||||||
r.Use(middleware.RequestID)
|
r.Use(middleware.RequestID)
|
||||||
r.Use(middleware.RealIP)
|
r.Use(middleware.RealIP)
|
||||||
r.Use(middleware.Logger)
|
r.Use(RequestLoggerMiddleware(s.logger.Logger)) // Structured request logging
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
const requestTimeout = 8 * time.Second
|
const requestTimeout = 30 * time.Second // Increased from 8s for slow queries
|
||||||
r.Use(TimeoutMiddleware(requestTimeout))
|
r.Use(TimeoutMiddleware(requestTimeout))
|
||||||
r.Use(JSONResponseMiddleware)
|
r.Use(JSONResponseMiddleware)
|
||||||
|
|
||||||
|
|||||||
@ -57,16 +57,27 @@ func (s *Server) Start() error {
|
|||||||
port = "8080"
|
port = "8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
const readHeaderTimeout = 40 * time.Second
|
const (
|
||||||
|
readHeaderTimeout = 40 * time.Second
|
||||||
|
readTimeout = 60 * time.Second
|
||||||
|
writeTimeout = 60 * time.Second
|
||||||
|
idleTimeout = 120 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
s.srv = &http.Server{
|
s.srv = &http.Server{
|
||||||
Addr: ":" + port,
|
Addr: ":" + port,
|
||||||
Handler: s.router,
|
Handler: s.router,
|
||||||
ReadHeaderTimeout: readHeaderTimeout,
|
ReadHeaderTimeout: readHeaderTimeout,
|
||||||
|
ReadTimeout: readTimeout,
|
||||||
|
WriteTimeout: writeTimeout,
|
||||||
|
IdleTimeout: idleTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("Starting HTTP server", "port", port)
|
s.logger.Info("Starting HTTP server", "port", port, "addr", s.srv.Addr)
|
||||||
|
|
||||||
|
// Start in goroutine but log when actually listening
|
||||||
go func() {
|
go func() {
|
||||||
|
s.logger.Info("HTTP server listening", "addr", s.srv.Addr)
|
||||||
if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
s.logger.Error("HTTP server error", "error", err)
|
s.logger.Error("HTTP server error", "error", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user