246 lines
5.3 KiB
Go
246 lines
5.3 KiB
Go
// Package logging provides slog-based structured logging with context integration.
|
|
//
|
|
// This package standardizes logging across all services with:
|
|
// - Environment-aware formatting (JSON for production, text for development)
|
|
// - Request-scoped loggers with context propagation
|
|
// - Convenience methods for common logging patterns
|
|
//
|
|
// Usage:
|
|
//
|
|
// // Create a logger based on environment
|
|
// logger := logging.New(logging.Config{
|
|
// Level: logging.LevelInfo,
|
|
// Format: logging.FormatJSON,
|
|
// Environment: "production",
|
|
// })
|
|
//
|
|
// // Or use convenience constructors
|
|
// logger := logging.NewDevelopment() // text format, debug level
|
|
// logger := logging.NewProduction() // JSON format, info level
|
|
package logging
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Level represents the logging level.
|
|
type Level int
|
|
|
|
const (
|
|
LevelDebug Level = iota
|
|
LevelInfo
|
|
LevelWarn
|
|
LevelError
|
|
)
|
|
|
|
// String returns the string representation of the level.
|
|
func (l Level) String() string {
|
|
switch l {
|
|
case LevelDebug:
|
|
return "debug"
|
|
case LevelInfo:
|
|
return "info"
|
|
case LevelWarn:
|
|
return "warn"
|
|
case LevelError:
|
|
return "error"
|
|
default:
|
|
return "info"
|
|
}
|
|
}
|
|
|
|
// ParseLevel parses a string into a Level.
|
|
// Returns LevelInfo if the string is not recognized.
|
|
func ParseLevel(s string) Level {
|
|
switch strings.ToLower(strings.TrimSpace(s)) {
|
|
case "debug":
|
|
return LevelDebug
|
|
case "info":
|
|
return LevelInfo
|
|
case "warn", "warning":
|
|
return LevelWarn
|
|
case "error":
|
|
return LevelError
|
|
default:
|
|
return LevelInfo
|
|
}
|
|
}
|
|
|
|
func (l Level) toSlog() slog.Level {
|
|
switch l {
|
|
case LevelDebug:
|
|
return slog.LevelDebug
|
|
case LevelInfo:
|
|
return slog.LevelInfo
|
|
case LevelWarn:
|
|
return slog.LevelWarn
|
|
case LevelError:
|
|
return slog.LevelError
|
|
default:
|
|
return slog.LevelInfo
|
|
}
|
|
}
|
|
|
|
// Format represents the output format.
|
|
type Format int
|
|
|
|
const (
|
|
FormatJSON Format = iota
|
|
FormatText
|
|
)
|
|
|
|
// String returns the string representation of the format.
|
|
func (f Format) String() string {
|
|
switch f {
|
|
case FormatJSON:
|
|
return "json"
|
|
case FormatText:
|
|
return "text"
|
|
default:
|
|
return "json"
|
|
}
|
|
}
|
|
|
|
// ParseFormat parses a string into a Format.
|
|
// Returns FormatJSON if the string is not recognized.
|
|
func ParseFormat(s string) Format {
|
|
switch strings.ToLower(strings.TrimSpace(s)) {
|
|
case "text", "console":
|
|
return FormatText
|
|
case "json":
|
|
return FormatJSON
|
|
default:
|
|
return FormatJSON
|
|
}
|
|
}
|
|
|
|
// Config holds the logger configuration.
|
|
type Config struct {
|
|
// Level sets the minimum log level.
|
|
// Default: LevelInfo
|
|
Level Level
|
|
|
|
// Format sets the output format.
|
|
// Default: FormatJSON
|
|
Format Format
|
|
|
|
// Output sets the output writer.
|
|
// Default: os.Stdout
|
|
Output io.Writer
|
|
|
|
// AddSource adds source file and line number to log entries.
|
|
// Default: false
|
|
AddSource bool
|
|
|
|
// Environment determines default format if not specified.
|
|
// "development" uses text format, others use JSON.
|
|
Environment string
|
|
}
|
|
|
|
// Logger wraps slog.Logger with additional convenience methods.
|
|
type Logger struct {
|
|
*slog.Logger
|
|
}
|
|
|
|
// New creates a new Logger with the given configuration.
|
|
func New(cfg Config) *Logger {
|
|
if cfg.Output == nil {
|
|
cfg.Output = os.Stdout
|
|
}
|
|
|
|
// Auto-detect format based on environment if not explicitly set
|
|
format := cfg.Format
|
|
if cfg.Environment == "development" && format == FormatJSON {
|
|
format = FormatText
|
|
}
|
|
|
|
opts := &slog.HandlerOptions{
|
|
Level: cfg.Level.toSlog(),
|
|
AddSource: cfg.AddSource,
|
|
}
|
|
|
|
var handler slog.Handler
|
|
switch format {
|
|
case FormatText:
|
|
handler = slog.NewTextHandler(cfg.Output, opts)
|
|
default:
|
|
handler = slog.NewJSONHandler(cfg.Output, opts)
|
|
}
|
|
|
|
return &Logger{
|
|
Logger: slog.New(handler),
|
|
}
|
|
}
|
|
|
|
// NewDevelopment creates a logger configured for development.
|
|
// Uses text format, debug level, and includes source location.
|
|
func NewDevelopment() *Logger {
|
|
return New(Config{
|
|
Level: LevelDebug,
|
|
Format: FormatText,
|
|
AddSource: true,
|
|
})
|
|
}
|
|
|
|
// NewProduction creates a logger configured for production.
|
|
// Uses JSON format and info level.
|
|
func NewProduction() *Logger {
|
|
return New(Config{
|
|
Level: LevelInfo,
|
|
Format: FormatJSON,
|
|
})
|
|
}
|
|
|
|
// With returns a new Logger with the given attributes.
|
|
func (l *Logger) With(args ...any) *Logger {
|
|
return &Logger{
|
|
Logger: l.Logger.With(args...),
|
|
}
|
|
}
|
|
|
|
// WithGroup returns a new Logger with the given group name.
|
|
func (l *Logger) WithGroup(name string) *Logger {
|
|
return &Logger{
|
|
Logger: l.Logger.WithGroup(name),
|
|
}
|
|
}
|
|
|
|
// WithError returns a new Logger with the error attribute.
|
|
func (l *Logger) WithError(err error) *Logger {
|
|
if err == nil {
|
|
return l
|
|
}
|
|
return l.With("error", err.Error())
|
|
}
|
|
|
|
// WithComponent returns a new Logger with the component attribute.
|
|
func (l *Logger) WithComponent(name string) *Logger {
|
|
return l.With("component", name)
|
|
}
|
|
|
|
// WithService returns a new Logger with the service attribute.
|
|
func (l *Logger) WithService(name string) *Logger {
|
|
return l.With("service", name)
|
|
}
|
|
|
|
// Nop returns a logger that discards all output.
|
|
func Nop() *Logger {
|
|
return New(Config{
|
|
Output: io.Discard,
|
|
Level: LevelError,
|
|
})
|
|
}
|
|
|
|
// Default returns the default logger configured for the current environment.
|
|
// Uses APP_ENVIRONMENT env var to determine format.
|
|
func Default() *Logger {
|
|
env := os.Getenv("APP_ENVIRONMENT")
|
|
if env == "development" || env == "" {
|
|
return NewDevelopment()
|
|
}
|
|
return NewProduction()
|
|
}
|