package sneklog import ( "encoding/json" "fmt" "io" "os" "path/filepath" "strings" "time" ) // LoggerWriter is the common sink interface used by Logger. type LoggerWriter interface { Close() error Write(p []byte) (n int, err error) Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error } // LoggerTextWriter writes human-readable log records to an io.Writer. type LoggerTextWriter struct { LoggerWriter writer io.Writer closer io.Closer formatter *Formatter } func (w *LoggerTextWriter) SetFormatter(formatter *Formatter) *LoggerTextWriter { w.formatter = formatter return w } func (w *LoggerTextWriter) Formatter() *Formatter { if w.formatter == nil { return DefaultTextFormatter } return w.formatter } // Write forwards raw bytes to the underlying writer. func (w *LoggerTextWriter) Write(p []byte) (n int, err error) { return w.writer.Write(p) } // Print formats the provided record as text and writes it to the underlying writer. func (w *LoggerTextWriter) Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error { msg := Map(messages, func(el any) string { return fmt.Sprint(el) }) newline := false if len(msg) > 0 && strings.HasSuffix(msg[len(msg)-1], "\n") { newline = true msg[len(msg)-1] = strings.TrimSuffix(msg[len(msg)-1], "\n") } messages = Map(msg, func(el string) any { return any(el) }) f := w.Formatter() s := f.FormatMessage(level, prefix, traceback, messages...) if f.ColorOutput && (!f.ColorOnlyStdout || w.writer == os.Stdout || w.writer == os.Stderr) { s = f.ColorizeString(s, level) } _, err := w.Write([]byte(s)) if err != nil { return err } if newline { _, err = w.Write([]byte("\n")) if err != nil { return err } } return nil } // Close closes the owned writer, if any. func (w *LoggerTextWriter) Close() error { if w.closer == nil { return nil } return w.closer.Close() } // LoggerJsonWriter writes structured log records as JSON. type LoggerJsonWriter struct { LoggerWriter writer io.Writer closer io.Closer pretty bool formatter *Formatter } func (w *LoggerJsonWriter) Formatter() *Formatter { if w.formatter == nil { return DefaultJsonFormatter } return w.formatter } func (w *LoggerJsonWriter) SetFormatter(f *Formatter) *LoggerJsonWriter { w.formatter = f return w } // LoggerJsonMessage is the JSON payload emitted by LoggerJsonWriter. type LoggerJsonMessage struct { Time string `json:"time"` Level string `json:"level"` Prefix string `json:"prefix"` Message string `json:"message"` Traceback []*MethodTraceback `json:"traceback"` } // Write forwards raw bytes to the underlying writer. func (w *LoggerJsonWriter) Write(data []byte) (int, error) { return w.writer.Write(data) } // Print encodes the provided record as JSON and writes it to the underlying writer. func (w *LoggerJsonWriter) Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error { msg := Map(messages, func(el any) string { return fmt.Sprint(el) }) newline := false if len(msg) > 0 && strings.HasSuffix(msg[len(msg)-1], "\n") { newline = true msg[len(msg)-1] = strings.TrimSuffix(msg[len(msg)-1], "\n") } messages = Map(msg, func(el string) any { return any(el) }) f := w.Formatter() s := f.FormatMessage(level, prefix, traceback, messages...) if f.ColorOutput && (!f.ColorOnlyStdout || w.writer == os.Stdout || w.writer == os.Stderr) { s = f.ColorizeString(s, level) } m := LoggerJsonMessage{ Time: f.FormatTime(time.Now()), Level: level.GetName(), Prefix: prefix, Message: s, Traceback: traceback, } var data []byte var err error if w.pretty { data, err = json.MarshalIndent(m, "", " ") } else { data, err = json.Marshal(m) } if err != nil { return err } if newline { data = append(data, '\n') } _, err = w.Write(data) return err } // Close closes the owned writer, if any. func (w *LoggerJsonWriter) Close() error { if w.closer == nil { return nil } return w.closer.Close() } // CreateTextWriter wraps an external writer for text output without taking // ownership of it. func CreateTextWriter(w io.Writer) *LoggerTextWriter { writer := &LoggerTextWriter{ writer: w, } return writer } // CreateTextFileWriter creates a text writer for path, creating parent // directories as needed. The returned writer owns the opened file. func CreateTextFileWriter(path string) (*LoggerTextWriter, error) { err := os.MkdirAll(filepath.Dir(path), os.ModePerm) if err != nil { return nil, err } file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, err } writer := CreateTextWriter(file) writer.closer = file return writer, nil } // CreateTextStdoutWriter creates a text writer for os.Stdout without taking // ownership of stdout. func CreateTextStdoutWriter() *LoggerTextWriter { writer := CreateTextWriter(os.Stdout) writer.closer = nil return writer } // CreateJsonWriter wraps an external writer for JSON output without taking // ownership of it. func CreateJsonWriter(w io.Writer, pretty bool) *LoggerJsonWriter { writer := &LoggerJsonWriter{ writer: w, pretty: pretty, } return writer } // CreateJsonStdoutWriter creates a JSON writer for os.Stdout without taking // ownership of stdout. func CreateJsonStdoutWriter(pretty bool) *LoggerJsonWriter { writer := CreateJsonWriter(os.Stdout, pretty) writer.closer = nil return writer } // CreateJsonFileWriter creates a JSON writer for path, creating parent // directories as needed. The returned writer owns the opened file. func CreateJsonFileWriter(path string, pretty bool) (*LoggerJsonWriter, error) { err := os.MkdirAll(filepath.Dir(path), os.ModePerm) if err != nil { return nil, err } file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, err } writer := CreateJsonWriter(file, pretty) writer.closer = file return writer, nil }