package logger
import (
"fmt"
"io"
"os"
"sync"
"time"
)
// Level represents log severity levels.
type Level int
const (
LevelDebug Level = iota
LevelInfo
LevelWarn
LevelError
)
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 "UNKNOWN"
}
}
// Logger defines the logging interface used throughout shep.
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
WithPrefix(prefix string) Logger
}
// StandardLogger implements Logger writing to an io.Writer.
type StandardLogger struct {
out io.Writer
prefix string
level Level
mu sync.Mutex
}
// Option configures a StandardLogger.
type Option func(*StandardLogger)
// WithOutput sets the output writer.
func WithOutput(w io.Writer) Option {
return func(l *StandardLogger) {
l.out = w
}
}
// WithLevel sets the minimum log level.
func WithLevel(level Level) Option {
return func(l *StandardLogger) {
l.level = level
}
}
// WithLogPrefix sets a prefix for log messages.
func WithLogPrefix(prefix string) Option {
return func(l *StandardLogger) {
l.prefix = prefix
}
}
// New creates a new StandardLogger with the given options.
func New(opts ...Option) *StandardLogger {
l := &StandardLogger{
out: os.Stderr,
level: LevelInfo,
}
for _, opt := range opts {
opt(l)
}
return l
}
func (l *StandardLogger) log(level Level, msg string, args ...any) {
if level < l.level {
return
}
l.mu.Lock()
defer l.mu.Unlock()
ts := time.Now().Format("15:04:05")
prefix := ""
if l.prefix != "" {
prefix = "[" + l.prefix + "] "
}
formatted := msg
if len(args) > 0 {
formatted = fmt.Sprintf(msg, args...)
}
fmt.Fprintf(l.out, "%s %s %s%s\n", ts, level.String(), prefix, formatted)
}
func (l *StandardLogger) Debug(msg string, args ...any) {
l.log(LevelDebug, msg, args...)
}
func (l *StandardLogger) Info(msg string, args ...any) {
l.log(LevelInfo, msg, args...)
}
func (l *StandardLogger) Warn(msg string, args ...any) {
l.log(LevelWarn, msg, args...)
}
func (l *StandardLogger) Error(msg string, args ...any) {
l.log(LevelError, msg, args...)
}
func (l *StandardLogger) WithPrefix(prefix string) Logger {
newPrefix := prefix
if l.prefix != "" {
newPrefix = l.prefix + "/" + prefix
}
return &StandardLogger{
out: l.out,
prefix: newPrefix,
level: l.level,
}
}
// NopLogger is a logger that does nothing.
type NopLogger struct{}
func (NopLogger) Debug(msg string, args ...any) {}
func (NopLogger) Info(msg string, args ...any) {}
func (NopLogger) Warn(msg string, args ...any) {}
func (NopLogger) Error(msg string, args ...any) {}
func (NopLogger) WithPrefix(prefix string) Logger {
return NopLogger{}
}