FILE / ScuroNeko/SNekLog
writers.go
Исходный файл и его история в репозитории.
Add Logger.AddReplacer to mask or normalize message text before records reach writers. Document replacement behavior in README and package GoDoc, and update the example. Also fix JSON writer edge cases: - avoid panic on empty message lists - preserve newline semantics when the last message already ends with n - keep original message argument types for custom writers when no replacers are configured Update dependencies and add release notes for the next release.
178 lines
4.9 KiB
Go
178 lines
4.9 KiB
Go
package slog
|
|
|
|
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
|
|
printTraceback bool
|
|
printTime bool
|
|
}
|
|
|
|
// 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, _ []*MethodTraceback, messages ...any) error {
|
|
s := BuildString(level, prefix, w.printTime, w.printTraceback, messages...)
|
|
_, err := w.Write([]byte(s))
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// LoggerJsonMessage is the JSON payload emitted by LoggerJsonWriter.
|
|
type LoggerJsonMessage struct {
|
|
Time time.Time `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")
|
|
}
|
|
|
|
m := LoggerJsonMessage{
|
|
Time: time.Now(),
|
|
Level: level.GetName(),
|
|
Prefix: prefix,
|
|
Message: strings.TrimSpace(strings.Join(msg, " ")),
|
|
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, printTraceback, printTime bool) *LoggerTextWriter {
|
|
writer := &LoggerTextWriter{
|
|
writer: w, printTraceback: printTraceback, printTime: printTime,
|
|
}
|
|
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, printTraceback, printTime bool) (*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, printTraceback, printTime)
|
|
writer.closer = file
|
|
return writer, nil
|
|
}
|
|
|
|
// CreateTextStdoutWriter creates a text writer for os.Stdout without taking
|
|
// ownership of stdout.
|
|
func CreateTextStdoutWriter(printTraceback, printTime bool) *LoggerTextWriter {
|
|
writer := CreateTextWriter(os.Stdout, printTraceback, printTime)
|
|
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
|
|
}
|