Comment blocks in cmd/minstrel and 30 smaller internal packages, rewritten
to rule 32's standard with each citation read in context: task, milestone
and rule ids go, and so do the stories they were attached to ("used to",
"Before #N", measurements from one install, quotes, pointers to Fabled
Librarian's records and to docs/superpowers files that are not in the
repo). Where a story wrapped a real constraint, the constraint stays as a
present-tense fact.
Comments that had gone stale are corrected:
- taste.Config: the tuned values are overlaid by internal/recsettings;
it is no longer "a later follow-up".
- StorageConfig: the data dir holds art, caches and the stream secret,
not only playlist covers.
- the Subsonic album list and getUser comments describe what they do
today rather than a milestone that would change it.
Checked: the non-comment token stream of every changed file matches
HEAD, and gofmt -l is empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.3 KiB
Go
69 lines
2.3 KiB
Go
package server
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
|
|
)
|
|
|
|
// requestLog is an slog-based access log middleware. chi ships
|
|
// middleware.Logger but it writes to the standard library's log package,
|
|
// not slog, so output ordering and formatting drift from the rest of the
|
|
// server's logs. We use a thin wrapper instead.
|
|
//
|
|
// The /healthz path is skipped because the docker-compose healthcheck
|
|
// hits it every 5s and would otherwise drown real signals (~17k lines/day).
|
|
//
|
|
// Severity is keyed off the response status so 4xx/5xx surface even when
|
|
// the logger level is set above Info.
|
|
//
|
|
// The `remote` attribute holds the address resolved through the configured
|
|
// reverse-proxy depth, NOT the raw socket peer. Behind a proxy — the normal
|
|
// deployment for anything public — the socket peer is the proxy, so every
|
|
// line would carry the same useless address, and the access log would
|
|
// disagree with the Active-sessions list about who connected.
|
|
//
|
|
// trustedHops is a func because this middleware is constructed at boot while
|
|
// the value is editable at runtime, and — since Router() registers
|
|
// this before it builds the settings service — because it lets the accessor
|
|
// be wired before the thing it reads exists. auth.ClientIP tolerates a depth
|
|
// of 0, which is what a nil service reports.
|
|
func requestLog(logger *slog.Logger, trustedHops func() int) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/healthz" {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
start := time.Now()
|
|
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
|
next.ServeHTTP(ww, r)
|
|
status := ww.Status()
|
|
hops := 0
|
|
if trustedHops != nil {
|
|
hops = trustedHops()
|
|
}
|
|
attrs := []any{
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"status", status,
|
|
"duration_ms", time.Since(start).Milliseconds(),
|
|
"request_id", middleware.GetReqID(r.Context()),
|
|
"remote", auth.ClientIP(r, hops),
|
|
}
|
|
switch {
|
|
case status >= 500:
|
|
logger.Error("http", attrs...)
|
|
case status >= 400:
|
|
logger.Warn("http", attrs...)
|
|
default:
|
|
logger.Info("http", attrs...)
|
|
}
|
|
})
|
|
}
|
|
}
|