Documentation ¶
Overview ¶
Package slogctx provides tools for working with contexts with the proposed log/slog package.
The package provides alternatives to slog logging functions that require a context with a logs: slogctx.Info replaces slog.Info, slogctx.Error replaces slog.Error, etc. The struct *slogctx.Logger replaces *slog.Logger. This is useful to ensure a context is always included. Usage:
slogctx.Info(ctx, "got a request") logger := slogctx.Default() // or logger := slogctx.NewLogger(slog.Default()) logger.Info(ctx, "found something special")
The package supports storing extra attributes in a context. All logs using the context created by slogctx.WithAttrs will include the extra attributes. This is useful to include a requestID with all logs. Usage:
ctx = slogctx.WithAttrs(ctx, "requestID", 1234) slogctx.Info(ctx, "processing request") slog.Default().WithContext(ctx).Info("processing more") // also works with plain slog
The package supports overriding the minimum log level. All logs using the context created by slogctx.WithMinimumLevel will use the supplied level. This is useful to debug specific requests. Usage:
ctx = slogctx.WithMinimumLevel(ctx, slog.LevelDebug) slogctx.Debug(ctx, "low-level information")
Using WithAttrs and WithMinimumLevel requires wrapping the underlying slog.Handler using slogctx.CtxHandler. This can be done globally for the default logger using slogctx.WrapDefaultLoggerWithCtxHandler.
Example ¶
package main import ( "context" "os" "time" "github.com/jellevandenhooff/slogctx" "golang.org/x/exp/slog" ) func SetupSlogForExample() func() { original := slog.Default() slog.SetDefault(slog.New(slog.HandlerOptions{ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == "time" { return slog.Time("time", time.Date(2022, 1, 29, 15, 10, 0, 0, time.UTC)) } return a }, }.NewTextHandler(os.Stdout))) return func() { slog.SetDefault(original) } } func main() { cleanup := SetupSlogForExample() defer cleanup() ctx := context.Background() // Setup slogctx for slog.Default(). This is required to make slog.WithAttrs // and slog.WithMinimumLevel work. slogctx.WrapDefaultLoggerWithCtxHandler() // The log functions slogctx.Info, slogctx.Error, etc. are alternatives to // slog.Info, slog.Error, etc. that take a context as first argument. // This can be useful to ensure all logger calls will supply a context. slogctx.Info(ctx, "doing a small log") // The slogctx.Logger is an alternative to slog.Logger where the log // functions take a context as first argument. // This can be useful to ensure all logger calls will supply a context. logger := slogctx.NewLogger(slog.Default()) logger.Info(ctx, "from the logger") logger = logger.With("loggerAttr", "extra attr") logger.Info(ctx, "extra logger attr") // Using WithAttrs extra attributes can be included in a context. // This can be useful to include eg. a request ID with all future logs. ctx = slogctx.WithAttrs(ctx, "requestID", "1234") slogctx.Info(ctx, "extra context attr") // WithAttrs also works with the standard slog log functions. slog.Default().WithContext(ctx).Info("from default logger") // Using WithMinimumLevel the default log level can be overriden. // This can be useful to trace in detail what happens with a specific request. slogctx.Debug(ctx, "log ignored") ctx = slogctx.WithMinimumLevel(ctx, slog.LevelDebug) slogctx.Debug(ctx, "log not ignored") }
Output: time=2022-01-29T15:10:00.000Z level=INFO msg="doing a small log" time=2022-01-29T15:10:00.000Z level=INFO msg="from the logger" time=2022-01-29T15:10:00.000Z level=INFO msg="extra logger attr" loggerAttr="extra attr" time=2022-01-29T15:10:00.000Z level=INFO msg="extra context attr" requestID=1234 time=2022-01-29T15:10:00.000Z level=INFO msg="from default logger" requestID=1234 time=2022-01-29T15:10:00.000Z level=DEBUG msg="log not ignored" requestID=1234
Example (Whole_program) ¶
package main import ( "context" "encoding/json" "net/http" "os" "time" "github.com/google/uuid" "github.com/jellevandenhooff/slogctx" "golang.org/x/exp/slog" ) func NewRequestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestID := uuid.NewString() ctx := r.Context() ctx = slogctx.WithAttrs(ctx, "requestID", requestID) r = r.WithContext(ctx) next.ServeHTTP(w, r) }) } type WebServer struct { // WebServer always has a context when logging. Use a *slogctx.Logger for convenience. logger *slogctx.Logger db *DB } func (s *WebServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() s.logger.Info(ctx, "got messages request", "url", r.URL.String(), "remoteAddr", r.RemoteAddr) response, err := s.db.QueryMessages(ctx) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } json.NewEncoder(w).Encode(response) } type DB struct { // DB sometimes has a context when logging, sometimes does not. Use a *slog.Logger. logger *slog.Logger } func (db *DB) QueryMessages(ctx context.Context) ([]string, error) { db.logger.WithContext(ctx).Debug("querying db") return []string{"hello world", "another message"}, nil } func (db *DB) PrintStats() { t := time.NewTicker(10 * time.Second) for range t.C { db.logger.Info("stats", "numDBConnections", 10) } } func main() { // This example shows a way of passing around *slog(ctx).Loggers as well and // extra attributes (or trace IDs) with a context. // // At program start time, the main function sets up the logger(s), DB // clients, server structs, and glues them altogether. // // At runtime, handlers pass around context.Context which gets passed down // to the loggers. // Setup slog slog.SetDefault(slog.New(slog.HandlerOptions{ AddSource: true, }.NewTextHandler(os.Stderr))) slogctx.WrapDefaultLoggerWithCtxHandler() // Build DB client. db := &DB{ logger: slog.Default().With("dbURL", "sqlite://foo"), } go db.PrintStats() // Build web server. webServer := &WebServer{ logger: slogctx.NewLogger(slog.Default()), db: db, } // Wrap web server with middleware. handler := NewRequestIDMiddleware(webServer) // Run the server. http.ListenAndServe(":8080", handler) }
Output:
Index ¶
- func Debug(ctx context.Context, msg string, args ...any)
- func Error(ctx context.Context, msg string, err error, args ...any)
- func Info(ctx context.Context, msg string, args ...any)
- func Warn(ctx context.Context, msg string, args ...any)
- func WithAttrs(ctx context.Context, args ...any) context.Context
- func WithMinimumLevel(ctx context.Context, level slog.Level) context.Context
- func WrapDefaultLoggerWithCtxHandler()
- func WrapWithCtxHandler(inner slog.Handler) slog.Handler
- type Logger
- func (l *Logger) Debug(ctx context.Context, msg string, args ...any)
- func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool
- func (l *Logger) Error(ctx context.Context, msg string, err error, args ...any)
- func (l *Logger) Info(ctx context.Context, msg string, args ...any)
- func (l *Logger) Log(ctx context.Context, level slog.Level, msg string, args ...any)
- func (l *Logger) Warn(ctx context.Context, msg string, args ...any)
- func (l *Logger) With(args ...any) *Logger
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func WithAttrs ¶
WithAttrs attaches the given attributes (as in slog.Logger.With) to the context.
Requires a slog.Handler wrapped with WrapWithCtxHandler.
func WithMinimumLevel ¶
WithMinimumLevel overrides the minimum logging level for all log calls using this context.
Requires a slog.Handler wrapped with WrapWithCtxHandler.
func WrapDefaultLoggerWithCtxHandler ¶
func WrapDefaultLoggerWithCtxHandler()
WrapDefaultLoggerWithCtxHandler wraps the handler used by slog.Default() with WrapWithCtxHandler.
Types ¶
type Logger ¶
Logger is a slog.Logger wrapper with a mandatory context argument.
func (*Logger) Error ¶
Error logs at LevelError. If err is non-nil, Error appends Any(ErrorKey, err) to the list of attributes.