mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-28 03:19:38 +00:00
feat(server): add syslog priority prefixes for systemd-journald (#5192)
* fix: add syslog priority prefixes for systemd-journald When running under systemd, all log messages were assigned priority 3 (error) by journald because navidrome wrote plain text to stderr without syslog priority prefixes. Add a journalFormatter that wraps the existing logrus formatter and prepends <N> syslog priority prefixes (RFC 5424) to each log line. The formatter is automatically enabled when the JOURNAL_STREAM environment variable is set (indicating the process is managed by systemd). Priority mapping: - Fatal/Panic → <2>/<0> (crit/emerg) - Error → <3> (err) - Warn → <4> (warning) - Info → <6> (info) - Debug/Trace → <7> (debug) Fixes #5142 * test: refactor journalFormatter tests to use Ginkgo and DescribeTable Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org> Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
c42570446b
commit
aa93911991
4 changed files with 95 additions and 0 deletions
|
|
@ -340,6 +340,10 @@ func Load(noConfigDump bool) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
log.SetOutput(out)
|
log.SetOutput(out)
|
||||||
|
} else if os.Getenv("JOURNAL_STREAM") != "" {
|
||||||
|
// When running under systemd, prepend syslog priority prefixes so
|
||||||
|
// journald assigns the correct severity to each log line.
|
||||||
|
log.EnableJournalFormat()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.SetLevelString(Server.LogLevel)
|
log.SetLevelString(Server.LogLevel)
|
||||||
|
|
|
||||||
41
log/journal.go
Normal file
41
log/journal.go
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// journalFormatter wraps a logrus.Formatter and prepends a syslog priority
|
||||||
|
// prefix (<N>) to each log line. When stderr is captured by systemd-journald,
|
||||||
|
// this prefix tells journald the correct severity for each message.
|
||||||
|
//
|
||||||
|
// See https://www.freedesktop.org/software/systemd/man/sd-daemon.html
|
||||||
|
type journalFormatter struct {
|
||||||
|
inner logrus.Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// levelToPriority maps logrus levels to syslog priority values.
|
||||||
|
// The mapping follows RFC 5424 severity levels.
|
||||||
|
var levelToPriority = map[logrus.Level]int{
|
||||||
|
logrus.PanicLevel: 0, // emerg
|
||||||
|
logrus.FatalLevel: 2, // crit
|
||||||
|
logrus.ErrorLevel: 3, // err
|
||||||
|
logrus.WarnLevel: 4, // warning
|
||||||
|
logrus.InfoLevel: 6, // info
|
||||||
|
logrus.DebugLevel: 7, // debug
|
||||||
|
logrus.TraceLevel: 7, // debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *journalFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||||
|
formatted, err := f.inner.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
return formatted, err
|
||||||
|
}
|
||||||
|
priority, ok := levelToPriority[entry.Level]
|
||||||
|
if !ok {
|
||||||
|
priority = 6 // default to info for unknown levels
|
||||||
|
}
|
||||||
|
prefix := []byte(fmt.Sprintf("<%d>", priority))
|
||||||
|
return append(prefix, formatted...), nil
|
||||||
|
}
|
||||||
41
log/journal_test.go
Normal file
41
log/journal_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("journalFormatter", func() {
|
||||||
|
var formatter *journalFormatter
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
inner := &logrus.TextFormatter{
|
||||||
|
DisableTimestamp: true,
|
||||||
|
DisableColors: true,
|
||||||
|
}
|
||||||
|
formatter = &journalFormatter{inner: inner}
|
||||||
|
})
|
||||||
|
|
||||||
|
DescribeTable("prefixes log lines with syslog priority",
|
||||||
|
func(level logrus.Level, expectedPrefix string) {
|
||||||
|
entry := &logrus.Entry{
|
||||||
|
Logger: logrus.New(),
|
||||||
|
Level: level,
|
||||||
|
Message: "test message",
|
||||||
|
Data: logrus.Fields{},
|
||||||
|
}
|
||||||
|
out, err := formatter.Format(entry)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(string(out)).To(HavePrefix(expectedPrefix))
|
||||||
|
},
|
||||||
|
Entry("error", logrus.ErrorLevel, "<3>"),
|
||||||
|
Entry("warning", logrus.WarnLevel, "<4>"),
|
||||||
|
Entry("info", logrus.InfoLevel, "<6>"),
|
||||||
|
Entry("debug", logrus.DebugLevel, "<7>"),
|
||||||
|
Entry("trace", logrus.TraceLevel, "<7>"),
|
||||||
|
Entry("fatal", logrus.FatalLevel, "<2>"),
|
||||||
|
Entry("panic", logrus.PanicLevel, "<0>"),
|
||||||
|
Entry("unknown level defaults to info", logrus.Level(99), "<6>"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
@ -145,6 +145,15 @@ func SetOutput(w io.Writer) {
|
||||||
defaultLogger.SetOutput(w)
|
defaultLogger.SetOutput(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableJournalFormat wraps the current logger formatter with syslog
|
||||||
|
// priority prefixes for systemd-journald. Only call this when output
|
||||||
|
// goes to stderr and JOURNAL_STREAM is set.
|
||||||
|
func EnableJournalFormat() {
|
||||||
|
loggerMu.Lock()
|
||||||
|
defer loggerMu.Unlock()
|
||||||
|
defaultLogger.Formatter = &journalFormatter{inner: defaultLogger.Formatter}
|
||||||
|
}
|
||||||
|
|
||||||
// Redact applies redaction to a single string
|
// Redact applies redaction to a single string
|
||||||
func Redact(msg string) string {
|
func Redact(msg string) string {
|
||||||
r, _ := redacted.redact(msg)
|
r, _ := redacted.redact(msg)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue