package sneklog import ( "errors" "fmt" "io" "strings" ) type replacer struct { old string new string } func (r replacer) replace(s string) string { return strings.ReplaceAll(s, r.old, r.new) } // Logger routes log records to one or more configured writers. type Logger struct { prefix string level LogLevel writers []LoggerWriter replacers []replacer jsonPretty bool } // LogLevel describes a logging severity. type LogLevel struct { n uint8 t string fg FgColor bg BgColor } // GetName returns the lowercase textual representation of the level. func (l *LogLevel) GetName() string { return l.t } func (l *LogLevel) GetFgColor() FgColor { return l.fg } func (l *LogLevel) SetFgColor(color FgColor) *LogLevel { l.fg = color return l } func (l *LogLevel) GetBgColor() BgColor { return l.bg } func (l *LogLevel) SetBgColor(color BgColor) *LogLevel { l.bg = color return l } // MethodTraceback describes a single stack frame attached to a log entry. type MethodTraceback struct { Method string `json:"method"` Filename string `json:"filename"` Line int `json:"line"` Signature string `json:"signature"` FullPath string `json:"fullPath"` } // Predefined log levels. var ( INFO = LogLevel{n: 0, t: "info", fg: FgWhite} WARN = LogLevel{n: 1, t: "warn", fg: FgHiYellow} ERROR = LogLevel{n: 2, t: "error", fg: FgHiRed} FATAL = LogLevel{n: 3, t: "fatal", fg: FgRed} DEBUG = LogLevel{n: 4, t: "debug", fg: FgGreen} ) // CreateLogger creates a logger with default settings. func CreateLogger() *Logger { return &Logger{ prefix: "LOG", level: FATAL, } } // Prefix sets the record prefix and returns the logger for chaining. func (l *Logger) Prefix(prefix string) *Logger { l.prefix = prefix return l } // Level sets the maximum enabled level and returns the logger for chaining. func (l *Logger) Level(level LogLevel) *Logger { l.level = level return l } // JsonPretty enables indented JSON output for JSON writers. func (l *Logger) JsonPretty(b bool) *Logger { l.jsonPretty = b return l } // AddWriters appends multiple writers to the logger. func (l *Logger) AddWriters(writers ...LoggerWriter) *Logger { l.writers = append(l.writers, writers...) return l } // AddWriter appends a single writer to the logger. func (l *Logger) AddWriter(writer LoggerWriter) *Logger { l.writers = append(l.writers, writer) return l } // AddReplacer appends a message replacer and returns the logger for chaining. // // Replacement rules are applied before records are passed to writers. Empty old // values are ignored because replacing an empty string would insert the // replacement between every UTF-8 sequence. func (l *Logger) AddReplacer(old, new string) *Logger { if old == "" { return l } r := replacer{old: old, new: new} l.replacers = append(l.replacers, r) return l } // Close closes all owned writers and returns a joined error, if any. func (l *Logger) Close() error { var errs []error for _, writer := range l.writers { if writer == nil { continue } err := writer.Close() if err != nil { errs = append(errs, err) } } l.writers = nil return errors.Join(errs...) } // CreateTextWriter wraps an external writer with the logger text settings. func (l *Logger) CreateTextWriter(w io.Writer) *LoggerTextWriter { return CreateTextWriter(w) } // CreateTextStdoutWriter creates a non-owning text writer for os.Stdout. func (l *Logger) CreateTextStdoutWriter() *LoggerTextWriter { return CreateTextStdoutWriter() } // CreateTextFileWriter creates an owning text writer for a file. func (l *Logger) CreateTextFileWriter(filename string) (*LoggerTextWriter, error) { return CreateTextFileWriter(filename) } // CreateJsonWriter wraps an external writer with the logger JSON settings. func (l *Logger) CreateJsonWriter(w io.Writer) *LoggerJsonWriter { return CreateJsonWriter(w, l.jsonPretty) } // CreateJsonStdoutWriter creates a non-owning JSON writer for os.Stdout. func (l *Logger) CreateJsonStdoutWriter() *LoggerJsonWriter { return CreateJsonStdoutWriter(l.jsonPretty) } // CreateJsonFileWriter creates an owning JSON writer for a file. func (l *Logger) CreateJsonFileWriter(filename string) (*LoggerJsonWriter, error) { return CreateJsonFileWriter(filename, l.jsonPretty) } func (l *Logger) replace(s string) string { out := s for _, repl := range l.replacers { out = repl.replace(out) } return out } func (l *Logger) replaceAll(messages ...any) []any { if len(l.replacers) == 0 { return messages } out := make([]any, len(messages)) for i, msg := range messages { out[i] = l.replace(fmt.Sprint(msg)) } return out }