@@ -12,16 +12,38 @@ import (
1212 "runtime"
1313 "slices"
1414 "strconv"
15+ "sync"
1516)
1617
18+ // Names of levels corresponding to syslog.Priority values.
1719const (
18- LevelNotice slog.Level = 1
19-
20+ LevelNotice slog.Level = slog .LevelInfo + 1
2021 LevelCritical slog.Level = slog .LevelError + 1
2122 LevelAlert slog.Level = slog .LevelError + 2
2223 LevelEmergency slog.Level = slog .LevelError + 3
2324)
2425
26+ // LevelVar is similar to [slog.LevelVar] but also implements the service side of [RestartMode=debug].
27+ // It looks if the environment variable DEBUG_INVOCATION is set and if so, sets the level to slog.LevelDebug.
28+ // The zero value of LevelVar is equivalent to slog.LevelInfo.
29+ // In the future, we might extend the behaviour of LevelVar to implement [org.freedesktop.LogControl1].
30+ //
31+ // [RestartMode=debug]: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#RestartMode=
32+ // [org.freedesktop.LogControl1]: https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.LogControl1.html
33+ type LevelVar struct {
34+ slog.LevelVar
35+ }
36+
37+ // Return v's level.
38+ func (v * LevelVar ) Level () slog.Level {
39+ sync .OnceFunc (func () {
40+ if os .Getenv ("DEBUG_INVOCATION" ) != "" {
41+ v .Set (slog .LevelDebug )
42+ }
43+ })()
44+ return v .LevelVar .Level ()
45+ }
46+
2547func levelToPriority (l slog.Level ) syslog.Priority {
2648 switch l {
2749 case slog .LevelDebug :
@@ -45,26 +67,25 @@ func levelToPriority(l slog.Level) syslog.Priority {
4567 }
4668}
4769
70+ // Options configure the Journal handler.
4871type Options struct {
4972 Level slog.Leveler
5073
5174 // ReplaceAttr is called on all non-builtin Attrs before they are written.
5275 // This can be useful for processing attributes to be in the correct format
5376 // for log statements outside of your own code as the journal only accepts
54- // variables that are uppercase and consist only of characters, numbers and
55- // underscores, and may not begin with an underscore.
77+ // keys of the form ^[A-Z_][A-Z0-9_]*$.
5678 ReplaceAttr func (groups []string , a slog.Attr ) slog.Attr
5779
5880 // ReplaceGroup is called on all group names before they are written. This
5981 // can be useful for processing group names to be in the correct format for
6082 // log statements outside of your own code as the journal only accepts
61- // variables that are uppercase and consist only of characters, numbers and
62- // underscores, and may not begin with an underscore.
83+ // keys of the form ^[A-Z_][A-Z0-9_]*$.
6384 ReplaceGroup func (group string ) string
6485}
6586
6687// Handler sends logs to the systemd journal.
67- // variable names must be in uppercase and consist only of characters, numbers and underscores, and may not begin with an underscore .
88+ // The journal only accepts keys of the form ^[A-Z_][A-Z0-9_]*$ .
6889type Handler struct {
6990 opts Options
7091 // NOTE: We only do single Write() calls. Either the message fits in a
@@ -79,6 +100,12 @@ type Handler struct {
79100
80101const sndBufSize = 8 * 1024 * 1024
81102
103+ // NewHandler returns a new Handler that writes to the systemd journal.
104+ // The journal only accepts keys of the form ^[A-Z_][A-Z0-9_]*$.
105+ // If opts is nil, the default options are used.
106+ // If opts.Level is nil, the default level is a [LevelVar] which is equivalent to
107+ // slog.LevelInfo unless the environment variable DEBUG_INVOCATION is set, in
108+ // which case it is slog.LevelDebug.
82109func NewHandler (opts * Options ) (* Handler , error ) {
83110 h := & Handler {}
84111
@@ -87,8 +114,7 @@ func NewHandler(opts *Options) (*Handler, error) {
87114 }
88115
89116 if h .opts .Level == nil {
90- // TODO: Implement a leveler that checks DEBUG_INVOCATION=1
91- h .opts .Level = slog .LevelInfo
117+ h .opts .Level = & LevelVar {}
92118 }
93119
94120 w , err := newJournalWriter ()
@@ -102,30 +128,19 @@ func NewHandler(opts *Options) (*Handler, error) {
102128
103129}
104130
105- // Enabled implements slog.Handler.
131+ // Enabled reports whether the handler handles records at the given level.
132+ // The handler ignores records whose level is lower.
133+ // It is called early, before any arguments are processed,
134+ // to save effort if the log event should be discarded.
106135func (h * Handler ) Enabled (_ context.Context , level slog.Level ) bool {
107136 return level >= h .opts .Level .Level ()
108137}
109138
110139var identifier = []byte (path .Base (os .Args [0 ]))
111140
112- // Handle handles the Record.
113- // It will only be called when Enabled returns true.
114- // The Context argument is as for Enabled.
115- // It is present solely to provide Handlers access to the context's values.
116- // Canceling the context should not affect record processing.
117- // (Among other things, log messages may be necessary to debug a
118- // cancellation-related problem.)
119- //
120- // Handle methods that produce output should observe the following rules:
121- // - If r.Time is the zero time, ignore the time.
122- // - If r.PC is zero, ignore it.
123- // - Attr's values should be resolved.
124- // - If an Attr's key and value are both the zero value, ignore the Attr.
125- // This can be tested with attr.Equal(Attr{}).
126- // - If a group's key is empty, inline the group's Attrs.
127- // - If a group has no Attrs (even if it has a non-empty key),
128- // ignore it.
141+ // Handle handles the Record and formats it as a [journal message](https://systemd.io/JOURNAL_NATIVE_PROTOCOL/).
142+ // Journal only supports keys of the form ^[A-Z_][A-Z0-9_]*$.
143+ // Any other keys will be silently dropped.
129144func (h * Handler ) Handle (ctx context.Context , r slog.Record ) error {
130145 buf := make ([]byte , 0 , 1024 )
131146 buf = h .appendKV (buf , "MESSAGE" , []byte (r .Message ))
@@ -227,7 +242,8 @@ func (h *Handler) appendAttr(b []byte, prefix string, a slog.Attr) []byte {
227242 return b
228243}
229244
230- // WithAttrs implements slog.Handler.
245+ // WithAttrs returns a new Handler whose attributes consist of
246+ // both the receiver's attributes and the arguments.
231247func (h * Handler ) WithAttrs (attrs []slog.Attr ) slog.Handler {
232248 h2 := * h
233249 pre := slices .Clone (h2 .preformatted )
@@ -238,7 +254,8 @@ func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
238254 return & h2
239255}
240256
241- // WithGroup implements slog.Handler.
257+ // WithGroup returns a new Handler with the given group appended to
258+ // the receiver's existing groups.
242259func (h * Handler ) WithGroup (name string ) slog.Handler {
243260 if name == "" {
244261 return h
0 commit comments