121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
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))
|
|
}
|
|
}
|