goshort/internal/server/middleware/logging.go
Gustavo Maronato 8ac3eef33a
Some checks failed
Build / build (push) Has been cancelled
implemented tracing
2023-09-21 16:19:17 -03:00

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))
}
}