initial commit
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled

This commit is contained in:
Daniel Noulet 2026-10-07 15:11:35 +02:00
commit 951dd8d26b
175 changed files with 50041 additions and 0 deletions

View file

@ -0,0 +1,86 @@
package httpapi
import (
"net/http"
"time"
"github.com/go-chi/chi/v5"
)
// LoggingMiddleware emits one structured log line per request.
//
// The line carries the correlation id, the resolved client address and the
// authenticated subject, which together are enough to answer "who did what, from
// where, and which request" without correlating across systems.
func LoggingMiddleware(log 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()
recorder := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(recorder, r)
elapsed := time.Since(start)
args := []any{
"method", r.Method,
"path", r.URL.Path,
"status", recorder.status,
"duration", elapsed.Round(time.Microsecond).String(),
"durationMs", float64(elapsed.Microseconds()) / 1000,
"bytes", recorder.written,
"requestId", requestIDFrom(r.Context()),
}
if ip := clientIPFrom(r.Context()); ip != "" {
args = append(args, "clientIp", ip)
}
if subject, _ := actorFrom(r.Context()); subject != "" && subject != "anonymous" {
args = append(args, "subject", subject)
}
if route := chi.RouteContext(r.Context()).RoutePattern(); route != "" {
args = append(args, "route", route)
}
if query := r.URL.RawQuery; query != "" && len(query) < 256 {
args = append(args, "query", query)
}
switch {
case recorder.status >= 500:
log.Error("request", args...)
case recorder.status >= 400:
// A 4xx is a client mistake and is logged at warn so it is visible
// without being mistaken for a service fault.
log.Warn("request", args...)
case elapsed > time.Second:
// A slow success matters operationally, so it is surfaced even
// though it is not an error.
log.Warn("slow request", args...)
default:
log.Info("request", args...)
}
})
}
}
// MetricsMiddleware records request metrics.
func MetricsMiddleware(metrics *Metrics) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if metrics == nil {
next.ServeHTTP(w, r)
return
}
start := time.Now()
recorder := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(recorder, r)
// The route pattern, not the concrete path, is the label: a thousand
// distinct theme slugs must not become a thousand time series.
route := chi.RouteContext(r.Context()).RoutePattern()
if route == "" {
route = "unmatched"
}
metrics.ObserveRequest(r.Method, route, recorder.status, time.Since(start))
})
}
}