@@ -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 l's level.
38+ func (l * LevelVar ) Level () slog.Level {
39+ sync .OnceFunc (func () {
40+ if os .Getenv ("DEBUG_INVOCATION" ) != "" {
41+ l .Set (slog .LevelDebug )
42+ }
43+ })()
44+ return l .LevelVar .Level ()
45+ }
46+
2547func levelToPriority (l slog.Level ) syslog.Priority {
2648 switch l {
2749 case slog .LevelDebug :
@@ -51,20 +73,18 @@ type Options struct {
5173 // ReplaceAttr is called on all non-builtin Attrs before they are written.
5274 // This can be useful for processing attributes to be in the correct format
5375 // 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.
76+ // keys of the form ^[A-Z_][A-Z0-9_]*$.
5677 ReplaceAttr func (groups []string , a slog.Attr ) slog.Attr
5778
5879 // ReplaceGroup is called on all group names before they are written. This
5980 // can be useful for processing group names to be in the correct format for
6081 // 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.
82+ // keys of the form ^[A-Z_][A-Z0-9_]*$.
6383 ReplaceGroup func (group string ) string
6484}
6585
6686// 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 .
87+ // The journal only accepts keys of the form ^[A-Z_][A-Z0-9_]*$ .
6888type Handler struct {
6989 opts Options
7090 // NOTE: We only do single Write() calls. Either the message fits in a
@@ -79,6 +99,12 @@ type Handler struct {
7999
80100const sndBufSize = 8 * 1024 * 1024
81101
102+ // NewHandler returns a new Handler that writes to the systemd journal.
103+ // The journal only accepts keys of the form ^[A-Z_][A-Z0-9_]*$.
104+ // If opts is nil, the default options are used.
105+ // If opts.Level is nil, the default level is a [LevelVar] which is equivalent to
106+ // slog.LevelInfo unless the environment variable DEBUG_INVOCATION is set, in
107+ // which case it is slog.LevelDebug.
82108func NewHandler (opts * Options ) (* Handler , error ) {
83109 h := & Handler {}
84110
@@ -87,8 +113,7 @@ func NewHandler(opts *Options) (*Handler, error) {
87113 }
88114
89115 if h .opts .Level == nil {
90- // TODO: Implement a leveler that checks DEBUG_INVOCATION=1
91- h .opts .Level = slog .LevelInfo
116+ h .opts .Level = & LevelVar {}
92117 }
93118
94119 w , err := newJournalWriter ()
@@ -102,30 +127,19 @@ func NewHandler(opts *Options) (*Handler, error) {
102127
103128}
104129
105- // Enabled implements slog.Handler.
130+ // Enabled reports whether the handler handles records at the given level.
131+ // The handler ignores records whose level is lower.
132+ // It is called early, before any arguments are processed,
133+ // to save effort if the log event should be discarded.
106134func (h * Handler ) Enabled (_ context.Context , level slog.Level ) bool {
107135 return level >= h .opts .Level .Level ()
108136}
109137
110138var identifier = []byte (path .Base (os .Args [0 ]))
111139
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.
140+ // Handle handles the Record and formats it as a [journal message](https://systemd.io/JOURNAL_NATIVE_PROTOCOL/).
141+ // Journal only supports keys of the form ^[A-Z_][A-Z0-9_]*$.
142+ // Any other keys will be silently dropped.
129143func (h * Handler ) Handle (ctx context.Context , r slog.Record ) error {
130144 buf := make ([]byte , 0 , 1024 )
131145 buf = h .appendKV (buf , "MESSAGE" , []byte (r .Message ))
@@ -227,7 +241,8 @@ func (h *Handler) appendAttr(b []byte, prefix string, a slog.Attr) []byte {
227241 return b
228242}
229243
230- // WithAttrs implements slog.Handler.
244+ // WithAttrs returns a new Handler whose attributes consist of
245+ // both the receiver's attributes and the arguments.
231246func (h * Handler ) WithAttrs (attrs []slog.Attr ) slog.Handler {
232247 h2 := * h
233248 pre := slices .Clone (h2 .preformatted )
@@ -238,7 +253,8 @@ func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
238253 return & h2
239254}
240255
241- // WithGroup implements slog.Handler.
256+ // WithGroup returns a new Handler with the given group appended to
257+ // the receiver's existing groups.
242258func (h * Handler ) WithGroup (name string ) slog.Handler {
243259 if name == "" {
244260 return h
0 commit comments