Introduces server/ sub-package as the server-side companion to the existing Client. Includes Router (over http.ServeMux with groups and mounting), graceful shutdown with signal handling, health endpoints (/healthz, /readyz), and built-in middlewares (RequestID, Recovery, Logging). Zero external dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
40 lines
1020 B
Go
40 lines
1020 B
Go
package server
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Logging returns a middleware that logs each request's method, path,
|
|
// status code, duration, and request ID using the provided structured logger.
|
|
func Logging(logger *slog.Logger) Middleware {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
|
|
sw := &statusWriter{ResponseWriter: w, status: http.StatusOK}
|
|
next.ServeHTTP(sw, r)
|
|
|
|
duration := time.Since(start)
|
|
attrs := []slog.Attr{
|
|
slog.String("method", r.Method),
|
|
slog.String("path", r.URL.Path),
|
|
slog.Int("status", sw.status),
|
|
slog.Duration("duration", duration),
|
|
}
|
|
|
|
if id := RequestIDFromContext(r.Context()); id != "" {
|
|
attrs = append(attrs, slog.String("request_id", id))
|
|
}
|
|
|
|
level := slog.LevelInfo
|
|
if sw.status >= http.StatusInternalServerError {
|
|
level = slog.LevelError
|
|
}
|
|
|
|
logger.LogAttrs(r.Context(), level, "request completed", attrs...)
|
|
})
|
|
}
|
|
}
|