@@ -12,16 +12,38 @@ import (
12
12
"runtime"
13
13
"slices"
14
14
"strconv"
15
+ "sync"
15
16
)
16
17
18
+ // Names of levels corresponding to syslog.Priority values.
17
19
const (
18
- LevelNotice slog.Level = 1
19
-
20
+ LevelNotice slog.Level = slog .LevelInfo + 1
20
21
LevelCritical slog.Level = slog .LevelError + 1
21
22
LevelAlert slog.Level = slog .LevelError + 2
22
23
LevelEmergency slog.Level = slog .LevelError + 3
23
24
)
24
25
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
+
25
47
func levelToPriority (l slog.Level ) syslog.Priority {
26
48
switch l {
27
49
case slog .LevelDebug :
@@ -45,26 +67,25 @@ func levelToPriority(l slog.Level) syslog.Priority {
45
67
}
46
68
}
47
69
70
+ // Options configure the Journal handler.
48
71
type Options struct {
49
72
Level slog.Leveler
50
73
51
74
// ReplaceAttr is called on all non-builtin Attrs before they are written.
52
75
// This can be useful for processing attributes to be in the correct format
53
76
// 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_]*$.
56
78
ReplaceAttr func (groups []string , a slog.Attr ) slog.Attr
57
79
58
80
// ReplaceGroup is called on all group names before they are written. This
59
81
// can be useful for processing group names to be in the correct format for
60
82
// 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_]*$.
63
84
ReplaceGroup func (group string ) string
64
85
}
65
86
66
87
// 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_]*$ .
68
89
type Handler struct {
69
90
opts Options
70
91
// NOTE: We only do single Write() calls. Either the message fits in a
@@ -79,6 +100,12 @@ type Handler struct {
79
100
80
101
const sndBufSize = 8 * 1024 * 1024
81
102
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.
82
109
func NewHandler (opts * Options ) (* Handler , error ) {
83
110
h := & Handler {}
84
111
@@ -87,8 +114,7 @@ func NewHandler(opts *Options) (*Handler, error) {
87
114
}
88
115
89
116
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 {}
92
118
}
93
119
94
120
w , err := newJournalWriter ()
@@ -102,30 +128,19 @@ func NewHandler(opts *Options) (*Handler, error) {
102
128
103
129
}
104
130
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.
106
135
func (h * Handler ) Enabled (_ context.Context , level slog.Level ) bool {
107
136
return level >= h .opts .Level .Level ()
108
137
}
109
138
110
139
var identifier = []byte (path .Base (os .Args [0 ]))
111
140
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.
129
144
func (h * Handler ) Handle (ctx context.Context , r slog.Record ) error {
130
145
buf := make ([]byte , 0 , 1024 )
131
146
buf = h .appendKV (buf , "MESSAGE" , []byte (r .Message ))
@@ -227,7 +242,8 @@ func (h *Handler) appendAttr(b []byte, prefix string, a slog.Attr) []byte {
227
242
return b
228
243
}
229
244
230
- // WithAttrs implements slog.Handler.
245
+ // WithAttrs returns a new Handler whose attributes consist of
246
+ // both the receiver's attributes and the arguments.
231
247
func (h * Handler ) WithAttrs (attrs []slog.Attr ) slog.Handler {
232
248
h2 := * h
233
249
pre := slices .Clone (h2 .preformatted )
@@ -238,7 +254,8 @@ func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
238
254
return & h2
239
255
}
240
256
241
- // WithGroup implements slog.Handler.
257
+ // WithGroup returns a new Handler with the given group appended to
258
+ // the receiver's existing groups.
242
259
func (h * Handler ) WithGroup (name string ) slog.Handler {
243
260
if name == "" {
244
261
return h
0 commit comments