feat: add message replacement for log records
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.
This commit is contained in:
@@ -10,11 +10,21 @@ import (
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
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
|
||||
prefix string
|
||||
level LogLevel
|
||||
writers []LoggerWriter
|
||||
replacers []replacer
|
||||
|
||||
printTraceback bool
|
||||
printTime bool
|
||||
@@ -66,36 +76,57 @@ 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
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -116,35 +147,61 @@ func (l *Logger) Close() error {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -153,8 +210,9 @@ func FormatFullTraceback(tracebacks []*MethodTraceback) string {
|
||||
}
|
||||
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 {
|
||||
func BuildString(level LogLevel, prefix string, printTime, printTraceback bool, messages ...any) string {
|
||||
args := []string{
|
||||
fmt.Sprintf("[%s]", prefix),
|
||||
fmt.Sprintf("[%s]", strings.ToUpper(level.t)),
|
||||
@@ -168,9 +226,9 @@ func BuildString(level LogLevel, prefix string, printTime, printTraceback bool,
|
||||
args = append(args, fmt.Sprintf("[%s]", FormatTime(time.Now())))
|
||||
}
|
||||
|
||||
msg := Map(m, func(el any) string {
|
||||
return fmt.Sprintf("%v", el)
|
||||
m := Map(messages, func(t any) string {
|
||||
return fmt.Sprint(t)
|
||||
})
|
||||
s := fmt.Sprintf("%s %s", strings.Join(args, " "), strings.Join(msg, " "))
|
||||
s := fmt.Sprintf("%s %s", strings.Join(args, " "), strings.Join(m, " "))
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user