Add comprehensive godoc comments to all exported types, functions, and constants throughout the codebase. Create README.md documenting the project architecture, execution flow, database schema, and component relationships.
176 lines
5.2 KiB
Go
176 lines
5.2 KiB
Go
// Package logger provides a structured logger with source location tracking.
|
|
// It wraps the standard library's log/slog package and automatically enriches
|
|
// log messages with the file name, line number, and function name of the caller.
|
|
// The output format is automatically selected based on the runtime environment:
|
|
// human-readable text for terminals, JSON for non-terminal output.
|
|
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// Logger wraps slog.Logger to add automatic source location information
|
|
// to all log messages. It embeds slog.Logger and provides the same logging
|
|
// methods (Debug, Info, Warn, Error) but enriches each message with the
|
|
// file name, line number, and function name of the caller.
|
|
type Logger struct {
|
|
*slog.Logger
|
|
}
|
|
|
|
// AsSlog returns the underlying slog.Logger for use with APIs that require
|
|
// a standard slog.Logger instance rather than the custom Logger type.
|
|
func (l *Logger) AsSlog() *slog.Logger {
|
|
return l.Logger
|
|
}
|
|
|
|
// New creates a new Logger with an appropriate handler based on the runtime
|
|
// environment. If stdout is a terminal, it uses a human-readable text format;
|
|
// otherwise, it outputs JSON for structured log aggregation. The log level
|
|
// defaults to Info, but can be set to Debug by including "routewatch" in the
|
|
// DEBUG environment variable.
|
|
func New() *Logger {
|
|
level := slog.LevelInfo
|
|
if debug := os.Getenv("DEBUG"); strings.Contains(debug, "routewatch") {
|
|
level = slog.LevelDebug
|
|
}
|
|
|
|
opts := &slog.HandlerOptions{
|
|
Level: level,
|
|
}
|
|
|
|
var handler slog.Handler
|
|
if term.IsTerminal(int(os.Stdout.Fd())) {
|
|
// Terminal, use text
|
|
handler = slog.NewTextHandler(os.Stdout, opts)
|
|
} else {
|
|
// Not a terminal, use JSON
|
|
handler = slog.NewJSONHandler(os.Stdout, opts)
|
|
}
|
|
|
|
return &Logger{Logger: slog.New(handler)}
|
|
}
|
|
|
|
// sourceSkipLevel defines the number of call stack frames to skip when
|
|
// determining the caller's source location. This accounts for the logger
|
|
// method itself and the getSourceAttrs helper function.
|
|
const sourceSkipLevel = 2
|
|
|
|
// getSourceAttrs returns attributes for the calling source location
|
|
func getSourceAttrs() []slog.Attr {
|
|
pc, file, line, ok := runtime.Caller(sourceSkipLevel)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Get just the filename without the full path
|
|
file = filepath.Base(file)
|
|
|
|
// Get the function name
|
|
fn := runtime.FuncForPC(pc)
|
|
var funcName string
|
|
if fn != nil {
|
|
funcName = filepath.Base(fn.Name())
|
|
}
|
|
|
|
attrs := []slog.Attr{
|
|
slog.String("source", fmt.Sprintf("%s:%d", file, line)),
|
|
}
|
|
|
|
if funcName != "" {
|
|
attrs = append(attrs, slog.String("func", funcName))
|
|
}
|
|
|
|
return attrs
|
|
}
|
|
|
|
// Debug logs a message at debug level with automatic source location tracking.
|
|
// Additional structured attributes can be passed as key-value pairs in args.
|
|
// Debug messages are only output when the DEBUG environment variable contains
|
|
// "routewatch".
|
|
func (l *Logger) Debug(msg string, args ...any) {
|
|
sourceAttrs := getSourceAttrs()
|
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
|
|
|
// Add source attributes first
|
|
for _, attr := range sourceAttrs {
|
|
allArgs = append(allArgs, attr)
|
|
}
|
|
|
|
// Add user args
|
|
allArgs = append(allArgs, args...)
|
|
|
|
l.Logger.Debug(msg, allArgs...)
|
|
}
|
|
|
|
// Info logs a message at info level with automatic source location tracking.
|
|
// Additional structured attributes can be passed as key-value pairs in args.
|
|
func (l *Logger) Info(msg string, args ...any) {
|
|
sourceAttrs := getSourceAttrs()
|
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
|
|
|
// Add source attributes first
|
|
for _, attr := range sourceAttrs {
|
|
allArgs = append(allArgs, attr)
|
|
}
|
|
|
|
// Add user args
|
|
allArgs = append(allArgs, args...)
|
|
|
|
l.Logger.Info(msg, allArgs...)
|
|
}
|
|
|
|
// Warn logs a message at warn level with automatic source location tracking.
|
|
// Additional structured attributes can be passed as key-value pairs in args.
|
|
func (l *Logger) Warn(msg string, args ...any) {
|
|
sourceAttrs := getSourceAttrs()
|
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
|
|
|
// Add source attributes first
|
|
for _, attr := range sourceAttrs {
|
|
allArgs = append(allArgs, attr)
|
|
}
|
|
|
|
// Add user args
|
|
allArgs = append(allArgs, args...)
|
|
|
|
l.Logger.Warn(msg, allArgs...)
|
|
}
|
|
|
|
// Error logs a message at error level with automatic source location tracking.
|
|
// Additional structured attributes can be passed as key-value pairs in args.
|
|
func (l *Logger) Error(msg string, args ...any) {
|
|
sourceAttrs := getSourceAttrs()
|
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
|
|
|
// Add source attributes first
|
|
for _, attr := range sourceAttrs {
|
|
allArgs = append(allArgs, attr)
|
|
}
|
|
|
|
// Add user args
|
|
allArgs = append(allArgs, args...)
|
|
|
|
l.Logger.Error(msg, allArgs...)
|
|
}
|
|
|
|
// With returns a new Logger with additional structured attributes that will
|
|
// be included in all subsequent log messages. The args parameter accepts
|
|
// key-value pairs in the same format as the logging methods.
|
|
func (l *Logger) With(args ...any) *Logger {
|
|
return &Logger{Logger: l.Logger.With(args...)}
|
|
}
|
|
|
|
// WithGroup returns a new Logger that adds the specified group name as a
|
|
// prefix to all attribute keys in subsequent log messages. This is useful
|
|
// for organizing related attributes under a common namespace.
|
|
func (l *Logger) WithGroup(name string) *Logger {
|
|
return &Logger{Logger: l.Logger.WithGroup(name)}
|
|
}
|