Implement structured logging and enhance middleware with context-aware logging
This commit is contained in:
85
internal/logging/logging.go
Normal file
85
internal/logging/logging.go
Normal file
@ -0,0 +1,85 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// loggerKey is the context key for logger values
|
||||
type loggerKey struct{}
|
||||
|
||||
// AppLogger is the application-wide logger instance
|
||||
var AppLogger *slog.Logger
|
||||
|
||||
// SetupLogger initializes the structured logger based on environment settings
|
||||
func SetupLogger(env string) *slog.Logger {
|
||||
// Determine log level
|
||||
logLevel := slog.LevelInfo
|
||||
if levelStr := os.Getenv("LOG_LEVEL"); levelStr != "" {
|
||||
if err := logLevel.UnmarshalText([]byte(levelStr)); err != nil {
|
||||
panic(fmt.Sprintf("invalid log level: %s", levelStr))
|
||||
}
|
||||
}
|
||||
|
||||
var handler slog.Handler
|
||||
|
||||
// Configure handler based on environment
|
||||
if env == "development" {
|
||||
// In development, use text output with source information if in debug mode
|
||||
opts := &slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
AddSource: logLevel == slog.LevelDebug,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
// Format time for better readability in dev mode
|
||||
if a.Key == slog.TimeKey {
|
||||
if t, ok := a.Value.Any().(time.Time); ok {
|
||||
return slog.String(slog.TimeKey, t.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
return a
|
||||
},
|
||||
}
|
||||
handler = slog.NewTextHandler(os.Stdout, opts)
|
||||
} else {
|
||||
// In production, use JSON output
|
||||
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
})
|
||||
}
|
||||
|
||||
// Create and set the default logger
|
||||
logger := slog.New(handler)
|
||||
slog.SetDefault(logger)
|
||||
AppLogger = logger
|
||||
|
||||
logger.Info("logger initialized",
|
||||
slog.String("environment", env),
|
||||
slog.String("level", logLevel.String()))
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// FromContext retrieves a logger from the given context
|
||||
// If no logger exists in the context, returns the default logger
|
||||
func FromContext(ctx context.Context) *slog.Logger {
|
||||
if ctx == nil {
|
||||
return AppLogger
|
||||
}
|
||||
if logger, ok := ctx.Value(loggerKey{}).(*slog.Logger); ok {
|
||||
return logger
|
||||
}
|
||||
return AppLogger
|
||||
}
|
||||
|
||||
// WithContext stores a logger in the given context
|
||||
func WithContext(ctx context.Context, logger *slog.Logger) context.Context {
|
||||
return context.WithValue(ctx, loggerKey{}, logger)
|
||||
}
|
||||
|
||||
// WithValues returns a new logger with additional context values
|
||||
func WithValues(logger *slog.Logger, attrs ...any) *slog.Logger {
|
||||
return logger.With(attrs...)
|
||||
}
|
Reference in New Issue
Block a user