Files
Archive/mieru/pkg/log/formatter.go
2025-11-25 19:40:52 +01:00

143 lines
3.5 KiB
Go

package log
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"time"
)
// Default key names for the default fields.
const (
defaultTimestampFormat = time.RFC3339
FieldKeyMsg = "msg"
FieldKeyLevel = "level"
FieldKeyTime = "time"
FieldKeyFunc = "func"
FieldKeyFile = "file"
)
// LogPrefix is a fixed string printed at the beginning of each line
// with DaemonFormatter. Set it as a build time variable to help debug
// the program.
var LogPrefix = ""
// The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones:
//
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
// * `entry.Data["time"]`. The timestamp.
// * `entry.Data["level"]. The level the entry was logged at.
//
// Any additional fields added with `WithField` or `WithFields` are also in
// `entry.Data`. Format is expected to return an array of bytes which are then
// logged to `logger.Out`.
type Formatter interface {
Format(*Entry) ([]byte, error)
}
// CliFormatter is a log formatter that works best for command output.
// It doesn't print log prefix, time, level, or field data.
type CliFormatter struct{}
func (f *CliFormatter) Format(entry *Entry) ([]byte, error) {
var buf *bytes.Buffer
if entry.Buffer != nil {
buf = entry.Buffer
} else {
buf = &bytes.Buffer{}
}
buf.WriteString(entry.Message)
buf.WriteString("\n")
return buf.Bytes(), nil
}
// DaemonFormatter is the a log formatter that is suitable for daemon.
// FATAL log is also printed to stderr.
type DaemonFormatter struct {
NoPrefix bool
NoTimestamp bool
NoLevel bool
}
func (f *DaemonFormatter) Format(entry *Entry) ([]byte, error) {
userData := make(Fields)
for k, v := range entry.Data {
userData[k] = v
}
userKeys := make([]string, 0, len(userData))
for k := range userData {
userKeys = append(userKeys, k)
}
var fileInfo, funcInfo string
orderedKeys := make([]string, 0, 5+len(userData))
if !f.NoTimestamp {
orderedKeys = append(orderedKeys, FieldKeyTime)
}
if !f.NoLevel {
orderedKeys = append(orderedKeys, FieldKeyLevel)
}
orderedKeys = append(orderedKeys, FieldKeyMsg)
if entry.HasCaller() {
fileInfo = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
funcInfo = entry.Caller.Function
orderedKeys = append(orderedKeys, FieldKeyFile)
orderedKeys = append(orderedKeys, FieldKeyFunc)
}
sort.Strings(userKeys)
orderedKeys = append(orderedKeys, userKeys...)
var buf *bytes.Buffer
if entry.Buffer != nil {
buf = entry.Buffer
} else {
buf = &bytes.Buffer{}
}
if !f.NoPrefix {
buf.WriteString(LogPrefix)
}
for _, key := range orderedKeys {
var value string
switch {
case key == FieldKeyTime:
value = entry.Time.Format(defaultTimestampFormat)
case key == FieldKeyLevel:
value = strings.ToUpper(entry.Level.String())
case key == FieldKeyMsg:
value = entry.Message
case key == FieldKeyFile && entry.HasCaller():
value = fileInfo
case key == FieldKeyFunc && entry.HasCaller():
value = funcInfo
default:
value = fmt.Sprintf("%v=%v", key, userData[key])
}
if buf.Len() > 0 {
// Add a space to separate from the previous field.
buf.WriteString(" ")
}
buf.WriteString(value)
}
buf.WriteString("\n")
if entry.Level == FatalLevel {
fmt.Fprint(os.Stderr, buf.String())
}
return buf.Bytes(), nil
}
// NilFormatter prints no log. It disables logging.
type NilFormatter struct{}
func (f *NilFormatter) Format(entry *Entry) ([]byte, error) {
return []byte{}, nil
}