package servermiddleware import ( "log/slog" "net/http" "time" "git.maronato.dev/maronato/goshort/internal/config" "git.maronato.dev/maronato/goshort/internal/util/logging" "github.com/go-chi/chi/v5/middleware" "go.opentelemetry.io/otel/trace" ) type RequestLogFormatter struct { printStack bool verbose config.VerboseLevel } func NewLogFormatter(cfg *config.Config) *RequestLogFormatter { return &RequestLogFormatter{ printStack: true, verbose: cfg.Verbose, } } func (rl *RequestLogFormatter) DisableStackPrinting() { rl.printStack = false } // RequestLogEntry is a struct that implements the LogEntry interface. type RequestLogEntry struct { l *slog.Logger printStack bool verbose config.VerboseLevel } func (rl *RequestLogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry { //nolint:ireturn // This is the signature of the interface. ctx := r.Context() l := logging.FromCtx(ctx) reqID := middleware.GetReqID(ctx) var requestGroup slog.Attr traceID := trace.SpanContextFromContext(ctx).TraceID().String() if rl.verbose >= config.VerboseLevelAccessLogs { scheme := "http" if r.TLS != nil { scheme = "https" } requestGroup = slog.Group("request", slog.String("trace_id", traceID), slog.String("id", reqID), slog.String("method", r.Method), slog.String("path", r.URL.Path), slog.String("host", r.Host), slog.String("remote_addr", r.RemoteAddr), slog.String("scheme", scheme), slog.String("user_agent", r.UserAgent()), slog.String("referer", r.Referer()), slog.String("content_type", r.Header.Get("Content-Type")), slog.Int64("content_length", r.ContentLength), ) } else { requestGroup = slog.Group("request", slog.String("trace_id", traceID), slog.String("id", reqID), slog.String("method", r.Method), slog.String("path", r.URL.Path), slog.String("remote_addr", r.RemoteAddr), ) } l = l.With(requestGroup) return &RequestLogEntry{ l: l, printStack: rl.printStack, verbose: rl.verbose, } } func (le *RequestLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, _ interface{}) { var responseGroup slog.Attr if le.verbose >= config.VerboseLevelAccessLogs { responseGroup = slog.Group("response", slog.Int("status", status), slog.Duration("duration", elapsed), slog.Int("content_length", bytes), slog.String("content_type", header.Get("Content-Type")), ) } else { responseGroup = slog.Group("response", slog.Int("status", status), slog.Duration("duration", elapsed), ) } l := le.l.With(responseGroup) switch { case status >= http.StatusInternalServerError: l.Error("Server error") case status >= http.StatusBadRequest: l.Info("Client error") default: l.Info("Request completed") } } func (le *RequestLogEntry) Panic(v interface{}, stack []byte) { if le.printStack { middleware.PrintPrettyStack(v) } else { le.l.Error("Panic", slog.Any("stack", stack)) } }