mirror of
https://github.com/caddyserver/caddy.git
synced 2025-11-13 18:16:54 -05:00
core: custom slog handlers for modules (log contextual data) (#7346)
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 50s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 15s
Lint / lint (ubuntu-latest, linux) (push) Failing after 15s
Lint / govulncheck (push) Successful in 1m47s
Lint / dependency-review (push) Failing after 16s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 18s
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 50s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 15s
Lint / lint (ubuntu-latest, linux) (push) Failing after 15s
Lint / govulncheck (push) Successful in 1m47s
Lint / dependency-review (push) Failing after 16s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 18s
This commit is contained in:
parent
07d2aaf22e
commit
b3f2db233b
49
context.go
49
context.go
@ -21,12 +21,14 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/exp/zapslog"
|
"go.uber.org/zap/exp/zapslog"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/internal/filesystems"
|
"github.com/caddyserver/caddy/v2/internal/filesystems"
|
||||||
)
|
)
|
||||||
@ -583,24 +585,57 @@ func (ctx Context) Logger(module ...Module) *zap.Logger {
|
|||||||
return ctx.cfg.Logging.Logger(mod)
|
return ctx.cfg.Logging.Logger(mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type slogHandlerFactory func(handler slog.Handler, core zapcore.Core, moduleID string) slog.Handler
|
||||||
|
|
||||||
|
var (
|
||||||
|
slogHandlerFactories []slogHandlerFactory
|
||||||
|
slogHandlerFactoriesMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterSlogHandlerFactory allows modules to register custom log/slog.Handler,
|
||||||
|
// for instance, to add contextual data to the logs.
|
||||||
|
func RegisterSlogHandlerFactory(factory slogHandlerFactory) {
|
||||||
|
slogHandlerFactoriesMu.Lock()
|
||||||
|
slogHandlerFactories = append(slogHandlerFactories, factory)
|
||||||
|
slogHandlerFactoriesMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Slogger returns a slog logger that is intended for use by
|
// Slogger returns a slog logger that is intended for use by
|
||||||
// the most recent module associated with the context.
|
// the most recent module associated with the context.
|
||||||
func (ctx Context) Slogger() *slog.Logger {
|
func (ctx Context) Slogger() *slog.Logger {
|
||||||
|
var (
|
||||||
|
handler slog.Handler
|
||||||
|
core zapcore.Core
|
||||||
|
moduleID string
|
||||||
|
)
|
||||||
if ctx.cfg == nil {
|
if ctx.cfg == nil {
|
||||||
// often the case in tests; just use a dev logger
|
// often the case in tests; just use a dev logger
|
||||||
l, err := zap.NewDevelopment()
|
l, err := zap.NewDevelopment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("config missing, unable to create dev logger: " + err.Error())
|
panic("config missing, unable to create dev logger: " + err.Error())
|
||||||
}
|
}
|
||||||
return slog.New(zapslog.NewHandler(l.Core()))
|
|
||||||
|
core = l.Core()
|
||||||
|
handler = zapslog.NewHandler(core)
|
||||||
|
} else {
|
||||||
|
mod := ctx.Module()
|
||||||
|
if mod == nil {
|
||||||
|
core = Log().Core()
|
||||||
|
handler = zapslog.NewHandler(core)
|
||||||
|
} else {
|
||||||
|
moduleID = string(mod.CaddyModule().ID)
|
||||||
|
core = ctx.cfg.Logging.Logger(mod).Core()
|
||||||
|
handler = zapslog.NewHandler(core, zapslog.WithName(moduleID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mod := ctx.Module()
|
|
||||||
if mod == nil {
|
slogHandlerFactoriesMu.RLock()
|
||||||
return slog.New(zapslog.NewHandler(Log().Core()))
|
for _, f := range slogHandlerFactories {
|
||||||
|
handler = f(handler, core, moduleID)
|
||||||
}
|
}
|
||||||
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
|
slogHandlerFactoriesMu.RUnlock()
|
||||||
zapslog.WithName(string(mod.CaddyModule().ID)),
|
|
||||||
))
|
return slog.New(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modules returns the lineage of modules that this context provisioned,
|
// Modules returns the lineage of modules that this context provisioned,
|
||||||
|
|||||||
@ -15,18 +15,28 @@
|
|||||||
package caddyhttp
|
package caddyhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/exp/zapslog"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterSlogHandlerFactory(func(handler slog.Handler, core zapcore.Core, moduleID string) slog.Handler {
|
||||||
|
return &extraFieldsSlogHandler{defaultHandler: handler, core: core, moduleID: moduleID}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ServerLogConfig describes a server's logging configuration. If
|
// ServerLogConfig describes a server's logging configuration. If
|
||||||
// enabled without customization, all requests to this server are
|
// enabled without customization, all requests to this server are
|
||||||
// logged to the default logger; logger destinations may be
|
// logged to the default logger; logger destinations may be
|
||||||
@ -223,17 +233,21 @@ func errLogValues(err error) (status int, msg string, fields func() []zapcore.Fi
|
|||||||
|
|
||||||
// ExtraLogFields is a list of extra fields to log with every request.
|
// ExtraLogFields is a list of extra fields to log with every request.
|
||||||
type ExtraLogFields struct {
|
type ExtraLogFields struct {
|
||||||
fields []zapcore.Field
|
fields []zapcore.Field
|
||||||
|
handlers sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a field to the list of extra fields to log.
|
// Add adds a field to the list of extra fields to log.
|
||||||
func (e *ExtraLogFields) Add(field zap.Field) {
|
func (e *ExtraLogFields) Add(field zap.Field) {
|
||||||
|
e.handlers.Clear()
|
||||||
e.fields = append(e.fields, field)
|
e.fields = append(e.fields, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets a field in the list of extra fields to log.
|
// Set sets a field in the list of extra fields to log.
|
||||||
// If the field already exists, it is replaced.
|
// If the field already exists, it is replaced.
|
||||||
func (e *ExtraLogFields) Set(field zap.Field) {
|
func (e *ExtraLogFields) Set(field zap.Field) {
|
||||||
|
e.handlers.Clear()
|
||||||
|
|
||||||
for i := range e.fields {
|
for i := range e.fields {
|
||||||
if e.fields[i].Key == field.Key {
|
if e.fields[i].Key == field.Key {
|
||||||
e.fields[i] = field
|
e.fields[i] = field
|
||||||
@ -243,6 +257,29 @@ func (e *ExtraLogFields) Set(field zap.Field) {
|
|||||||
e.fields = append(e.fields, field)
|
e.fields = append(e.fields, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ExtraLogFields) getSloggerHandler(handler *extraFieldsSlogHandler) (h slog.Handler) {
|
||||||
|
if existing, ok := e.handlers.Load(handler); ok {
|
||||||
|
return existing.(slog.Handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler.moduleID == "" {
|
||||||
|
h = zapslog.NewHandler(handler.core.With(e.fields))
|
||||||
|
} else {
|
||||||
|
h = zapslog.NewHandler(handler.core.With(e.fields), zapslog.WithName(handler.moduleID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler.group != "" {
|
||||||
|
h = h.WithGroup(handler.group)
|
||||||
|
}
|
||||||
|
if handler.attrs != nil {
|
||||||
|
h = h.WithAttrs(handler.attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.handlers.Store(handler, h)
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Variable name used to indicate that this request
|
// Variable name used to indicate that this request
|
||||||
// should be omitted from the access logs
|
// should be omitted from the access logs
|
||||||
@ -254,3 +291,43 @@ const (
|
|||||||
// Variable name used to indicate the logger to be used
|
// Variable name used to indicate the logger to be used
|
||||||
AccessLoggerNameVarKey string = "access_logger_names"
|
AccessLoggerNameVarKey string = "access_logger_names"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type extraFieldsSlogHandler struct {
|
||||||
|
defaultHandler slog.Handler
|
||||||
|
core zapcore.Core
|
||||||
|
moduleID string
|
||||||
|
group string
|
||||||
|
attrs []slog.Attr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extraFieldsSlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
return e.defaultHandler.Enabled(ctx, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extraFieldsSlogHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||||
|
if elf, ok := ctx.Value(ExtraLogFieldsCtxKey).(*ExtraLogFields); ok {
|
||||||
|
return elf.getSloggerHandler(e).Handle(ctx, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.defaultHandler.Handle(ctx, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extraFieldsSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return &extraFieldsSlogHandler{
|
||||||
|
e.defaultHandler.WithAttrs(attrs),
|
||||||
|
e.core,
|
||||||
|
e.moduleID,
|
||||||
|
e.group,
|
||||||
|
append(e.attrs, attrs...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extraFieldsSlogHandler) WithGroup(name string) slog.Handler {
|
||||||
|
return &extraFieldsSlogHandler{
|
||||||
|
e.defaultHandler.WithGroup(name),
|
||||||
|
e.core,
|
||||||
|
e.moduleID,
|
||||||
|
name,
|
||||||
|
e.attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -793,8 +793,10 @@ func (s *Server) logRequest(
|
|||||||
accLog *zap.Logger, r *http.Request, wrec ResponseRecorder, duration *time.Duration,
|
accLog *zap.Logger, r *http.Request, wrec ResponseRecorder, duration *time.Duration,
|
||||||
repl *caddy.Replacer, bodyReader *lengthReader, shouldLogCredentials bool,
|
repl *caddy.Replacer, bodyReader *lengthReader, shouldLogCredentials bool,
|
||||||
) {
|
) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
// this request may be flagged as omitted from the logs
|
// this request may be flagged as omitted from the logs
|
||||||
if skip, ok := GetVar(r.Context(), LogSkipVar).(bool); ok && skip {
|
if skip, ok := GetVar(ctx, LogSkipVar).(bool); ok && skip {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,7 +814,7 @@ func (s *Server) logRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
message := "handled request"
|
message := "handled request"
|
||||||
if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop {
|
if nop, ok := GetVar(ctx, "unhandled").(bool); ok && nop {
|
||||||
message = "NOP"
|
message = "NOP"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,7 +838,7 @@ func (s *Server) logRequest(
|
|||||||
reqBodyLength = bodyReader.Length
|
reqBodyLength = bodyReader.Length
|
||||||
}
|
}
|
||||||
|
|
||||||
extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
|
extra := ctx.Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
|
||||||
|
|
||||||
fieldCount := 6
|
fieldCount := 6
|
||||||
fields = make([]zapcore.Field, 0, fieldCount+len(extra.fields))
|
fields = make([]zapcore.Field, 0, fieldCount+len(extra.fields))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user