Skip to content

Commit 9ce4728

Browse files
committed
feat: custom slog handlers for modules (log contextual data)
1 parent 07d2aaf commit 9ce4728

File tree

3 files changed

+126
-11
lines changed

3 files changed

+126
-11
lines changed

context.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import (
2121
"log"
2222
"log/slog"
2323
"reflect"
24+
"sync"
2425

2526
"github.com/caddyserver/certmagic"
2627
"github.com/prometheus/client_golang/prometheus"
2728
"github.com/prometheus/client_golang/prometheus/collectors"
2829
"go.uber.org/zap"
2930
"go.uber.org/zap/exp/zapslog"
31+
"go.uber.org/zap/zapcore"
3032

3133
"github.com/caddyserver/caddy/v2/internal/filesystems"
3234
)
@@ -583,24 +585,57 @@ func (ctx Context) Logger(module ...Module) *zap.Logger {
583585
return ctx.cfg.Logging.Logger(mod)
584586
}
585587

588+
type slogHandlerFactory func(handler slog.Handler, core zapcore.Core, moduleID string) slog.Handler
589+
590+
var (
591+
slogHandlerFactories []slogHandlerFactory
592+
slogHandlerFactoriesMu sync.RWMutex
593+
)
594+
595+
// RegisterSlogHandlerFactory allows modules to register custom log/slog.Handler,
596+
// for instance, to add contextual data to the logs.
597+
func RegisterSlogHandlerFactory(factory slogHandlerFactory) {
598+
slogHandlerFactoriesMu.Lock()
599+
slogHandlerFactories = append(slogHandlerFactories, factory)
600+
slogHandlerFactoriesMu.Unlock()
601+
}
602+
586603
// Slogger returns a slog logger that is intended for use by
587604
// the most recent module associated with the context.
588605
func (ctx Context) Slogger() *slog.Logger {
606+
var (
607+
handler slog.Handler
608+
core zapcore.Core
609+
moduleID string
610+
)
589611
if ctx.cfg == nil {
590612
// often the case in tests; just use a dev logger
591613
l, err := zap.NewDevelopment()
592614
if err != nil {
593615
panic("config missing, unable to create dev logger: " + err.Error())
594616
}
595-
return slog.New(zapslog.NewHandler(l.Core()))
617+
618+
core = l.Core()
619+
handler = zapslog.NewHandler(core)
620+
} else {
621+
mod := ctx.Module()
622+
if mod == nil {
623+
core = Log().Core()
624+
handler = zapslog.NewHandler(core)
625+
} else {
626+
moduleID = string(mod.CaddyModule().ID)
627+
core = ctx.cfg.Logging.Logger(mod).Core()
628+
handler = zapslog.NewHandler(core, zapslog.WithName(moduleID))
629+
}
596630
}
597-
mod := ctx.Module()
598-
if mod == nil {
599-
return slog.New(zapslog.NewHandler(Log().Core()))
631+
632+
slogHandlerFactoriesMu.RLock()
633+
for _, f := range slogHandlerFactories {
634+
handler = f(handler, core, moduleID)
600635
}
601-
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
602-
zapslog.WithName(string(mod.CaddyModule().ID)),
603-
))
636+
slogHandlerFactoriesMu.RUnlock()
637+
638+
return slog.New(handler)
604639
}
605640

606641
// Modules returns the lineage of modules that this context provisioned,

modules/caddyhttp/logging.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,28 @@
1515
package caddyhttp
1616

1717
import (
18+
"context"
1819
"encoding/json"
1920
"errors"
21+
"log/slog"
2022
"net"
2123
"net/http"
2224
"strings"
25+
"sync"
2326

2427
"go.uber.org/zap"
28+
"go.uber.org/zap/exp/zapslog"
2529
"go.uber.org/zap/zapcore"
2630

2731
"github.com/caddyserver/caddy/v2"
2832
)
2933

34+
func init() {
35+
caddy.RegisterSlogHandlerFactory(func(handler slog.Handler, core zapcore.Core, moduleID string) slog.Handler {
36+
return &extraFieldsSlogHandler{defaultHandler: handler, core: core, moduleID: moduleID}
37+
})
38+
}
39+
3040
// ServerLogConfig describes a server's logging configuration. If
3141
// enabled without customization, all requests to this server are
3242
// 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
223233

224234
// ExtraLogFields is a list of extra fields to log with every request.
225235
type ExtraLogFields struct {
226-
fields []zapcore.Field
236+
fields []zapcore.Field
237+
handlers sync.Map
227238
}
228239

229240
// Add adds a field to the list of extra fields to log.
230241
func (e *ExtraLogFields) Add(field zap.Field) {
242+
e.handlers.Clear()
231243
e.fields = append(e.fields, field)
232244
}
233245

234246
// Set sets a field in the list of extra fields to log.
235247
// If the field already exists, it is replaced.
236248
func (e *ExtraLogFields) Set(field zap.Field) {
249+
e.handlers.Clear()
250+
237251
for i := range e.fields {
238252
if e.fields[i].Key == field.Key {
239253
e.fields[i] = field
@@ -243,6 +257,30 @@ func (e *ExtraLogFields) Set(field zap.Field) {
243257
e.fields = append(e.fields, field)
244258
}
245259

260+
// INTERNAL
261+
func (e *ExtraLogFields) getSloggerHandler(handler *extraFieldsSlogHandler) (h slog.Handler) {
262+
if existing, ok := e.handlers.Load(handler); ok {
263+
return existing.(slog.Handler)
264+
}
265+
266+
if handler.moduleID == "" {
267+
h = zapslog.NewHandler(handler.core.With(e.fields))
268+
} else {
269+
h = zapslog.NewHandler(handler.core.With(e.fields), zapslog.WithName(handler.moduleID))
270+
}
271+
272+
if handler.group != "" {
273+
h = h.WithGroup(handler.group)
274+
}
275+
if handler.attrs != nil {
276+
h = h.WithAttrs(handler.attrs)
277+
}
278+
279+
e.handlers.Store(handler, h)
280+
281+
return h
282+
}
283+
246284
const (
247285
// Variable name used to indicate that this request
248286
// should be omitted from the access logs
@@ -254,3 +292,43 @@ const (
254292
// Variable name used to indicate the logger to be used
255293
AccessLoggerNameVarKey string = "access_logger_names"
256294
)
295+
296+
type extraFieldsSlogHandler struct {
297+
defaultHandler slog.Handler
298+
core zapcore.Core
299+
moduleID string
300+
group string
301+
attrs []slog.Attr
302+
}
303+
304+
func (e *extraFieldsSlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
305+
return e.defaultHandler.Enabled(ctx, level)
306+
}
307+
308+
func (e *extraFieldsSlogHandler) Handle(ctx context.Context, record slog.Record) error {
309+
if elf, ok := ctx.Value(ExtraLogFieldsCtxKey).(*ExtraLogFields); ok {
310+
return elf.getSloggerHandler(e).Handle(ctx, record)
311+
}
312+
313+
return e.defaultHandler.Handle(ctx, record)
314+
}
315+
316+
func (e *extraFieldsSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
317+
return &extraFieldsSlogHandler{
318+
e.defaultHandler.WithAttrs(attrs),
319+
e.core,
320+
e.moduleID,
321+
e.group,
322+
append(e.attrs, attrs...),
323+
}
324+
}
325+
326+
func (e *extraFieldsSlogHandler) WithGroup(name string) slog.Handler {
327+
return &extraFieldsSlogHandler{
328+
e.defaultHandler.WithGroup(name),
329+
e.core,
330+
e.moduleID,
331+
name,
332+
e.attrs,
333+
}
334+
}

modules/caddyhttp/server.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,10 @@ func (s *Server) logRequest(
793793
accLog *zap.Logger, r *http.Request, wrec ResponseRecorder, duration *time.Duration,
794794
repl *caddy.Replacer, bodyReader *lengthReader, shouldLogCredentials bool,
795795
) {
796+
ctx := r.Context()
797+
796798
// this request may be flagged as omitted from the logs
797-
if skip, ok := GetVar(r.Context(), LogSkipVar).(bool); ok && skip {
799+
if skip, ok := GetVar(ctx, LogSkipVar).(bool); ok && skip {
798800
return
799801
}
800802

@@ -812,7 +814,7 @@ func (s *Server) logRequest(
812814
}
813815

814816
message := "handled request"
815-
if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop {
817+
if nop, ok := GetVar(ctx, "unhandled").(bool); ok && nop {
816818
message = "NOP"
817819
}
818820

@@ -836,7 +838,7 @@ func (s *Server) logRequest(
836838
reqBodyLength = bodyReader.Length
837839
}
838840

839-
extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
841+
extra := ctx.Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
840842

841843
fieldCount := 6
842844
fields = make([]zapcore.Field, 0, fieldCount+len(extra.fields))

0 commit comments

Comments
 (0)