package slog import ( "errors" "fmt" "io" "strings" "time" "github.com/fatih/color" ) // Logger routes log records to one or more configured writers. type Logger struct { prefix string level LogLevel writers []LoggerWriter printTraceback bool printTime bool jsonPretty bool } // LogLevel describes a logging severity. type LogLevel struct { n uint8 t string c color.Attribute } // GetName returns the lowercase textual representation of the level. func (l *LogLevel) GetName() string { return l.t } // 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", c: color.FgWhite} WARN = LogLevel{n: 1, t: "warn", c: color.FgHiYellow} ERROR = LogLevel{n: 2, t: "error", c: color.FgHiRed} FATAL = LogLevel{n: 3, t: "fatal", c: color.FgRed} DEBUG = LogLevel{n: 4, t: "debug", c: color.FgGreen} ) // CreateLogger creates a logger with default settings. func CreateLogger() *Logger { return &Logger{ prefix: "LOG", level: FATAL, printTraceback: false, printTime: true, } } // 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 } // PrintTraceback enables traceback output for text writers. func (l *Logger) PrintTraceback(b bool) *Logger { l.printTraceback = b return l } // PrintTime enables timestamps for text writers. func (l *Logger) PrintTime(b bool) *Logger { l.printTime = b 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 } // 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, l.printTraceback, l.printTime) } // CreateTextStdoutWriter creates a non-owning text writer for os.Stdout. func (l *Logger) CreateTextStdoutWriter() *LoggerTextWriter { return CreateTextStdoutWriter(l.printTraceback, l.printTime) } // CreateTextFileWriter creates an owning text writer for a file. func (l *Logger) CreateTextFileWriter(filename string) (*LoggerTextWriter, error) { return CreateTextFileWriter(filename, l.printTraceback, l.printTime) } // 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) } // FormatTime converts time to the package text log timestamp format. func FormatTime(t time.Time) string { return fmt.Sprintf("%02d.%02d.%02d %02d:%02d:%02d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second()) } // FormatTraceback converts a traceback frame to a compact string. func FormatTraceback(mt *MethodTraceback) string { return fmt.Sprintf("%s:%s:%d", mt.Filename, mt.Method, mt.Line) } // FormatFullTraceback joins multiple traceback frames into one string. func FormatFullTraceback(tracebacks []*MethodTraceback) string { formatted := make([]string, 0) for _, tb := range tracebacks { formatted = append(formatted, FormatTraceback(tb)) } return strings.Join(formatted, "->") } // BuildString renders a text log record using the provided settings. func BuildString(level LogLevel, prefix string, printTime, printTraceback bool, m ...any) string { args := []string{ fmt.Sprintf("[%s]", prefix), fmt.Sprintf("[%s]", strings.ToUpper(level.t)), } if printTraceback { args = append(args, fmt.Sprintf("[%s]", FormatTraceback(getTraceback()))) } if printTime { args = append(args, fmt.Sprintf("[%s]", FormatTime(time.Now()))) } msg := Map(m, func(el any) string { return fmt.Sprintf("%v", el) }) s := fmt.Sprintf("%s %s", strings.Join(args, " "), strings.Join(msg, " ")) return s }