logginghandler/logginghandler.go
Marvin Preuss d5b5c131e7
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat(requestid): the handler is request id aware
if the request has an header `X-Request-ID`, the middleware will extract
  it and use it as uuid on the logs.
2023-02-24 09:07:06 +00:00

108 lines
2.6 KiB
Go

// Package logginghandler is a simple, zerolog based, request logging http middleware.
// It also sets `X-Request-ID` in the request and response headers.
package logginghandler
import (
"context"
"net/http"
"time"
"github.com/justinas/alice"
"github.com/rs/xid"
"github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
"github.com/rs/zerolog/log"
)
const (
UUIDKey = "uuid"
UUIDHeader = "X-Request-ID"
)
func init() { //nolint:gochecknoinits
zerolog.DefaultContextLogger = &log.Logger
}
// GetUUID gets the requests UUID from a request.
func GetUUID(r *http.Request) (string, bool) {
uuid, ok := hlog.IDFromRequest(r)
if !ok {
return "", false
}
return uuid.String(), true
}
// FromRequest returns a logger set from request.
// If no one could be found, it will return the global one.
func FromRequest(r *http.Request) *zerolog.Logger {
return hlog.FromRequest(r)
}
// FromCtx returns a logger set from ctx.
// If no one could be found, it will return the global one.
func FromCtx(ctx context.Context) *zerolog.Logger {
return zerolog.Ctx(ctx)
}
// RequestIDHandler looks in the header for an existing request id. Else it will create one.
func RequestIDHandler() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get(UUIDHeader)
if id != "" {
ctx := r.Context()
log := zerolog.Ctx(ctx)
uuid, err := xid.FromString(id)
if err != nil {
log.Error().Err(err).Msg("couldnt parse uuid")
hlog.RequestIDHandler(UUIDKey, UUIDHeader)(next).ServeHTTP(w, r)
return
}
ctx = hlog.CtxWithID(ctx, uuid)
r = r.WithContext(ctx)
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str(UUIDKey, uuid.String())
})
w.Header().Set(UUIDHeader, uuid.String())
next.ServeHTTP(w, r)
} else {
hlog.RequestIDHandler(UUIDKey, UUIDHeader)(next).ServeHTTP(w, r)
}
})
}
}
func Handler(log zerolog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
chain := alice.New(
hlog.NewHandler(log),
hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
hlog.FromRequest(r).Info().
Str("method", r.Method).
Str("proto", r.Proto).
Stringer("request-url", r.URL).
Int("status", status).
Int("size", size).
Dur("duration", duration).
Msg("")
}),
hlog.RemoteAddrHandler("remote"),
hlog.UserAgentHandler("user-agent"),
hlog.RefererHandler("referer"),
RequestIDHandler(),
).Then(next)
return chain
}
}