- Create internal/logger package with Logger wrapper around slog - Logger automatically adds source file, line number, and function name to all log entries - Use golang.org/x/term to properly detect if stdout is a terminal - Replace all slog.Logger usage with logger.Logger throughout the codebase - Remove verbose logging from database GetStats() method - Update all constructors and dependencies to use the new logger
151 lines
3.4 KiB
Go
151 lines
3.4 KiB
Go
// Package logger provides a structured logger with source location tracking
|
|
package logger
|
|
|
|
import (
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// Logger wraps slog.Logger to add source location information
|
|
type Logger struct {
|
|
*slog.Logger
|
|
}
|
|
|
|
// AsSlog returns the underlying slog.Logger
|
|
func (l *Logger) AsSlog() *slog.Logger {
|
|
return l.Logger
|
|
}
|
|
|
|
// New creates a new logger with appropriate handler based on environment
|
|
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)}
|
|
}
|
|
|
|
const sourceSkipLevel = 2 // Skip levels for source location tracking
|
|
|
|
// 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", file),
|
|
slog.Int("line", line),
|
|
}
|
|
|
|
if funcName != "" {
|
|
attrs = append(attrs, slog.String("func", funcName))
|
|
}
|
|
|
|
return attrs
|
|
}
|
|
|
|
// Debug logs at debug level with source location
|
|
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 at info level with source location
|
|
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 at warn level with source location
|
|
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 at error level with source location
|
|
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 attributes
|
|
func (l *Logger) With(args ...any) *Logger {
|
|
return &Logger{Logger: l.Logger.With(args...)}
|
|
}
|
|
|
|
// WithGroup returns a new logger with a group prefix
|
|
func (l *Logger) WithGroup(name string) *Logger {
|
|
return &Logger{Logger: l.Logger.WithGroup(name)}
|
|
}
|