diff options
author | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2025-08-02 20:55:11 +0530 |
---|---|---|
committer | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2025-08-02 20:55:11 +0530 |
commit | ce6cf13c2d67c3368251d1eea5593198f5021330 (patch) | |
tree | d4c2347fd45fce395bf22a30e423697494d4284f /logging/log.go | |
download | kit-ce6cf13c2d67c3368251d1eea5593198f5021330.tar kit-ce6cf13c2d67c3368251d1eea5593198f5021330.tar.gz kit-ce6cf13c2d67c3368251d1eea5593198f5021330.tar.bz2 kit-ce6cf13c2d67c3368251d1eea5593198f5021330.tar.lz kit-ce6cf13c2d67c3368251d1eea5593198f5021330.tar.xz kit-ce6cf13c2d67c3368251d1eea5593198f5021330.tar.zst kit-ce6cf13c2d67c3368251d1eea5593198f5021330.zip |
Initial Commitv0.1.0
Diffstat (limited to 'logging/log.go')
-rw-r--r-- | logging/log.go | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/logging/log.go b/logging/log.go new file mode 100644 index 0000000..8dc8017 --- /dev/null +++ b/logging/log.go @@ -0,0 +1,136 @@ +// Package logging provides helpers for context-aware logging using Go's slog package. +// +// It allows attaching loggers to context.Context for structured logging, +// recovering and logging panics with stack traces, +// and provides a simple package-level logger setup function. +package logging + +import ( + "context" + "fmt" + "io" + "log/slog" + "os" + "runtime" + "strings" +) + +type ctxLoggerKeyType struct{} + +var ctxLoggerKey = ctxLoggerKeyType{} + +// WithLogger returns a new context derived from ctx that carries the provided slog.Logger. +// +// Typical usage: +// +// ctx = logging.WithLogger(ctx, logger) +func WithLogger(ctx context.Context, logger *slog.Logger) context.Context { + return context.WithValue(ctx, ctxLoggerKey, logger) +} + +// FromContext retrieves the slog.Logger stored in ctx if any. +// If no logger is attached to the context, returns slog.Default(). +func FromContext(ctx context.Context) *slog.Logger { + if logger, ok := ctx.Value(ctxLoggerKey).(*slog.Logger); ok { + return logger + } + return slog.Default() +} + +// With returns a new slog.Logger augmented with the passed key-value pairs, +// and a new context holding this logger. +// +// Typical usage: +// +// logger, ctx := logging.With(ctx, "user_id", userID) +func With(ctx context.Context, args ...any) (*slog.Logger, context.Context) { + logger := FromContext(ctx) + logger = logger.With(args...) + ctx = WithLogger(ctx, logger) + return logger, ctx +} + +// WithGroup returns a new slog.Logger grouped under name, +// and a new context holding this logger. +// +// Typical usage: +// +// logger, ctx := logging.WithGroup(ctx, "http") +func WithGroup(ctx context.Context, name string) (*slog.Logger, context.Context) { + logger := FromContext(ctx) + logger = logger.WithGroup(name) + ctx = WithLogger(ctx, logger) + return logger, ctx +} + +// RecoverAndLog logs a recovered panic or error with a stack trace using the logger from ctx. +// +// Typically used in a deferred recover block: +// +// defer func() { +// if err := recover(); err != nil { +// logging.RecoverAndLog(ctx, "Panic recovered", err) +// os.Exit(1) +// } +// }() +func RecoverAndLog(ctx context.Context, msg string, err any) { + const size = 64 << 10 // 64 KB stack trace buffer + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logger := FromContext(ctx) + logger.ErrorContext(ctx, msg, "Error", err, "stack", string(buf)) +} + +type LogSink string + +const ( + SinkStdout LogSink = "stdout" + SinkStderr LogSink = "stderr" + SinkFile LogSink = "file" +) + +type LogConfig struct { + Level slog.Level `toml:"level"` + Sink LogSink `toml:"sink"` + Format string `toml:"format"` + File string `toml:"file"` +} + +func Setup(cfg LogConfig) error { + var w io.Writer + + switch cfg.Sink { + case SinkStdout, "": + w = os.Stdout + case SinkStderr: + w = os.Stderr + case SinkFile: + if cfg.File == "" { + return fmt.Errorf("log sink set to 'file' but no file path provided") + } + f, err := os.OpenFile(cfg.File, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open log file: %w", err) + } + w = f + default: + return fmt.Errorf("unsupported log sink: %s", cfg.Sink) + } + + var handler slog.Handler + opts := &slog.HandlerOptions{ + Level: cfg.Level, + } + + switch strings.ToLower(cfg.Format) { + case "text": + handler = slog.NewTextHandler(w, opts) + case "json", "": + handler = slog.NewJSONHandler(w, opts) + default: + return fmt.Errorf("unsupported log format: %s", cfg.Format) + } + + slog.SetDefault(slog.New(handler)) + return nil +} |