package logger import ( "fmt" "log" "os" "strings" ) type Level int const ( LevelDebug Level = iota LevelInfo LevelWarn LevelError ) type Logger struct { base *log.Logger level Level component string } func New(level string) (*Logger, error) { parsed, err := ParseLevel(level) if err != nil { return nil, err } return &Logger{ base: log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds), level: parsed, component: "app", }, nil } func ParseLevel(raw string) (Level, error) { switch strings.ToLower(strings.TrimSpace(raw)) { case "debug": return LevelDebug, nil case "info", "": return LevelInfo, nil case "warn", "warning": return LevelWarn, nil case "error": return LevelError, nil default: return LevelInfo, fmt.Errorf("invalid LOG_LEVEL=%q, expected debug|info|warn|error", raw) } } func (l *Logger) WithComponent(component string) *Logger { if component == "" { component = "app" } return &Logger{ base: l.base, level: l.level, component: component, } } func (l *Logger) Level() Level { return l.level } func (l *Logger) Debugf(format string, args ...any) { l.logf(LevelDebug, "DEBUG", format, args...) } func (l *Logger) Infof(format string, args ...any) { l.logf(LevelInfo, "INFO", format, args...) } func (l *Logger) Warnf(format string, args ...any) { l.logf(LevelWarn, "WARN", format, args...) } func (l *Logger) Errorf(format string, args ...any) { l.logf(LevelError, "ERROR", format, args...) } func (l *Logger) logf(level Level, label, format string, args ...any) { if level < l.level { return } msg := fmt.Sprintf(format, args...) l.base.Printf("[%s] [%s] %s", label, l.component, msg) }