package sneklog import ( "errors" "fmt" "io" "strings" ) // LogLevel describes a logging severity. type LogLevel struct { n uint8 t string fg FgColor fg256 FgColor256 fgRgb FgColorRGB bg BgColor bg256 BgColor256 bgRgb BgColorRGB attrs []Attribute } // NewLogLevel creates a log level without predefined colors. func NewLogLevel(index uint8, name string) LogLevel { return LogLevel{n: index, t: name, attrs: []Attribute{}} } // NewLogLevelWithColors creates a log level with ANSI foreground and background colors. func NewLogLevelWithColors(index uint8, name string, fg FgColor, bg BgColor) LogLevel { return LogLevel{n: index, t: name, fg: fg, bg: bg, attrs: []Attribute{}} } // GetName returns the lowercase textual representation of the level. func (l *LogLevel) GetName() string { return l.t } // SetFgColor sets the ANSI foreground color and clears other foreground color modes. // Deprecated: use SetForegroundColor. This method will be removed in v3. func (l *LogLevel) SetFgColor(color FgColor) *LogLevel { return l.SetForegroundColor(color) } // GetFgColor returns the ANSI foreground color for the level. // Deprecated: use GetForegroundColor. This method will be removed in v3. func (l *LogLevel) GetFgColor() FgColor { return l.GetForegroundColor() } // SetForegroundColor sets the ANSI foreground color and clears other foreground color modes. func (l *LogLevel) SetForegroundColor(color FgColor) *LogLevel { l.fg = color l.fg256 = 0 l.fgRgb = nil return l } // GetForegroundColor returns the ANSI foreground color for the level. func (l *LogLevel) GetForegroundColor() FgColor { return l.fg } // SetForeground256Color sets the 256-color foreground and clears other foreground color modes. func (l *LogLevel) SetForeground256Color(color FgColor256) *LogLevel { l.fg = 0 l.fg256 = color l.fgRgb = nil return l } // GetForeground256Color returns the configured 256-color foreground value. func (l *LogLevel) GetForeground256Color() FgColor256 { return l.fg256 } // SetForegroundRGB sets the RGB foreground color and clears other foreground color modes. func (l *LogLevel) SetForegroundRGB(r, g, b uint8) *LogLevel { l.fg = 0 l.fg256 = 0 l.fgRgb = FgColorRGB{r, g, b} return l } // GetForegroundRGB returns the configured RGB foreground value. func (l *LogLevel) GetForegroundRGB() FgColorRGB { return l.fgRgb } // SetBgColor sets the ANSI background color and clears other background color modes. // Deprecated: use SetBackgroundColor. This method will be removed in v3. func (l *LogLevel) SetBgColor(color BgColor) *LogLevel { return l.SetBackgroundColor(color) } // GetBgColor returns the ANSI background color for the level. // Deprecated: use GetBackgroundColor. This method will be removed in v3. func (l *LogLevel) GetBgColor() BgColor { return l.GetBackgroundColor() } // SetBackgroundColor sets the ANSI background color and clears other background color modes. func (l *LogLevel) SetBackgroundColor(color BgColor) *LogLevel { l.bg = color l.bg256 = 0 l.bgRgb = nil return l } // GetBackgroundColor returns the ANSI background color for the level. func (l *LogLevel) GetBackgroundColor() BgColor { return l.bg } // SetBackground256Color sets the 256-color background and clears other background color modes. func (l *LogLevel) SetBackground256Color(color BgColor256) *LogLevel { l.bg = 0 l.bg256 = color l.bgRgb = nil return l } // GetBackground256Color returns the configured 256-color background value. func (l *LogLevel) GetBackground256Color() BgColor256 { return l.bg256 } // SetBackgroundRGB sets the RGB background color and clears other background color modes. func (l *LogLevel) SetBackgroundRGB(r, g, b uint8) *LogLevel { l.bg = 0 l.bg256 = 0 l.bgRgb = BgColorRGB{r, g, b} return l } // GetBackgroundRGB returns the configured RGB background value. func (l *LogLevel) GetBackgroundRGB() BgColorRGB { return l.bgRgb } // AddAttribute appends an ANSI text attribute to the level. func (l *LogLevel) AddAttribute(a Attribute) *LogLevel { l.attrs = append(l.attrs, a) return l } // RemoveAttribute removes all matching ANSI text attributes from the level. func (l *LogLevel) RemoveAttribute(a Attribute) *LogLevel { attrs := make([]Attribute, 0) for _, attr := range l.attrs { if attr != a { attrs = append(attrs, attr) } } l.attrs = attrs return l } // SetAttributes replaces the level attributes with a copy of the provided slice. func (l *LogLevel) SetAttributes(a []Attribute) *LogLevel { attrs := make([]Attribute, len(a)) copy(attrs, a) l.attrs = attrs return l } // GetAttributes returns a copy of the configured ANSI text attributes. func (l *LogLevel) GetAttributes() []Attribute { attrs := make([]Attribute, len(l.attrs)) copy(attrs, l.attrs) return attrs } // 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 = NewLogLevelWithColors(0, "info", FgWhite, BgNone) WARN = NewLogLevelWithColors(1, "warn", FgHiYellow, BgNone) ERROR = NewLogLevelWithColors(2, "error", FgHiRed, BgNone) FATAL = NewLogLevelWithColors(3, "fatal", FgRed, BgNone) DEBUG = NewLogLevelWithColors(4, "debug", FgGreen, BgNone) ) // Logger routes log records to one or more configured writers. type Logger struct { prefix string level LogLevel writers []LoggerWriter replacers []replacer jsonPretty bool } // CreateLogger creates a logger with default settings. // Deprecated: use NewLogger. This method will be removed in v3. func CreateLogger() *Logger { return &Logger{ prefix: "LOG", level: FATAL, } } // NewLogger creates a logger with default settings. func NewLogger() *Logger { return &Logger{ prefix: "LOG", level: FATAL, } } // Prefix sets the record prefix and returns the logger for chaining. // Deprecated: use SetName. This method will be removed in v3. func (l *Logger) Prefix(prefix string) *Logger { l.prefix = prefix return l } // SetName sets the record prefix and returns the logger for chaining. func (l *Logger) SetName(name string) *Logger { l.prefix = name return l } // Level sets the maximum enabled level and returns the logger for chaining. // Deprecated: use SetLevel. This method will be removed in v3. func (l *Logger) Level(level LogLevel) *Logger { l.level = level return l } // SetLevel sets the maximum enabled level and returns the logger for chaining. func (l *Logger) SetLevel(level LogLevel) *Logger { l.level = level return l } // JsonPretty enables or disables indented JSON output for JSON writers created by the logger. // Deprecated: use SetJSONPretty. This method will be removed in v3. func (l *Logger) JsonPretty(b bool) *Logger { l.jsonPretty = b return l } // SetJSONPretty enables or disables indented output for JSON writers created by the logger. func (l *Logger) SetJSONPretty(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. // Deprecated: use AddWriters. This method will be removed in v3. 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) } type replacer struct { old string new string } func (r replacer) replace(s string) string { return strings.ReplaceAll(s, r.old, r.new) } 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 }