Skip to content

Commit 0cf63db

Browse files
committed
add support for RestartMode=debug
This adds a variant of slog.LevelVar that checks DEBUG_INVOCATION=1
1 parent 344aba3 commit 0cf63db

File tree

1 file changed

+45
-29
lines changed

1 file changed

+45
-29
lines changed

journal.go

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,38 @@ import (
1212
"runtime"
1313
"slices"
1414
"strconv"
15+
"sync"
1516
)
1617

18+
// Names of levels corresponding to syslog.Priority values.
1719
const (
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+
2547
func 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_]*$.
6888
type 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

80100
const 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.
82108
func 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.
106134
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool {
107135
return level >= h.opts.Level.Level()
108136
}
109137

110138
var 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.
129143
func (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.
231246
func (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.
242258
func (h *Handler) WithGroup(name string) slog.Handler {
243259
if name == "" {
244260
return h

0 commit comments

Comments
 (0)