//go:build !windows // +build !windows // This package consists of parts of github.com/rs/zerolog i copied from. // I removed the JSON argument part and added some more relevant data (The position of the caller and the goroutine) package logging import ( "bytes" "encoding/json" "fmt" "io" "runtime" "strings" "github.com/coreos/go-systemd/v22/journal" "github.com/fxamacker/cbor/v2" "github.com/rs/zerolog" ) var replacements = map[string]string{ "-": "_", " ": "_", } const defaultJournalDPrio = journal.PriNotice func binaryFmt(p []byte) bool { if len(p) > 0 && p[0] > 0x7F { return true } return false } // NewJournalDWriter returns a zerolog log destination // to be used as parameter to New() calls. Writing logs // to this writer will send the log messages to journalD // running in this system. func NewJournalDWriter() io.Writer { return journalWriter{} } type journalWriter struct { } // levelToJPrio converts zerolog Level string into // journalD's priority values. JournalD has more // priorities than zerolog. func levelToJPrio(zLevel string) journal.Priority { lvl, _ := zerolog.ParseLevel(zLevel) switch lvl { case zerolog.TraceLevel: return journal.PriDebug case zerolog.DebugLevel: return journal.PriDebug case zerolog.InfoLevel: return journal.PriInfo case zerolog.WarnLevel: return journal.PriWarning case zerolog.ErrorLevel: return journal.PriErr case zerolog.FatalLevel: return journal.PriCrit case zerolog.PanicLevel: return journal.PriEmerg case zerolog.NoLevel: return journal.PriNotice } return defaultJournalDPrio } func replace(input string) (output string) { output = input for key, value := range replacements { output = strings.ReplaceAll(output, key, value) } return } func (w journalWriter) Write(p []byte) (n int, err error) { var event map[string]interface{} origPLen := len(p) if binaryFmt(p) { cbord := cbor.NewDecoder(bytes.NewReader(p)) err = cbord.Decode(&event) } else { jsond := json.NewDecoder(bytes.NewReader(p)) err = jsond.Decode(&event) } jPrio := defaultJournalDPrio args := make(map[string]string) if err != nil { return } if l, ok := event[zerolog.LevelFieldName].(string); ok { jPrio = levelToJPrio(l) } msg := "" for key, value := range event { jKey := replace(strings.ToUpper(key)) switch key { case zerolog.LevelFieldName, zerolog.TimestampFieldName: continue case zerolog.MessageFieldName: msg, _ = value.(string) continue } switch v := value.(type) { case string: args[jKey] = v case json.Number: args[jKey] = fmt.Sprint(value) default: b, err := zerolog.InterfaceMarshalFunc(value) if err != nil { args[jKey] = fmt.Sprintf("[error: %v]", err) } else { args[jKey] = string(b) } } } args["TID"] = fmt.Sprint(runtime.NumGoroutine()) var callers = make([]uintptr, 10) // skipping the function itself runtime.Callers(2, callers) var frames = runtime.CallersFrames(callers) var frame, next = frames.Next() for next { if !strings.Contains(frame.File, "github.com/rs/zerolog") && !strings.Contains(frame.File, "github.com/jwreagor/grpc-zerolog") { args["CODE_FILE"] = frame.File args["CODE_LINE"] = fmt.Sprint(frame.Line) args["CODE_FUNC"] = frame.Function break } frame, next = frames.Next() } err = journal.Send(msg, jPrio, args) if err == nil { n = origPLen } return }