12 // DaemonLogHandler writes short event lines with ordered attributes. Copies
13 // share the writer lock. The err attribute follows all other attributes as a
14 // cause. Journal lines carry a syslog priority instead of a time and level.
15 type DaemonLogHandler struct {
24 func (h *DaemonLogHandler) Enabled(_ context.Context, _ slog.Level) bool {
28 func (h *DaemonLogHandler) Handle(_ context.Context, record slog.Record) error {
29 var line strings.Builder
34 case record.Level >= slog.LevelError:
36 case record.Level >= slog.LevelWarn:
38 case record.Level >= slog.LevelInfo:
42 fmt.Fprintf(&line, "<%d>", priority)
44 line.WriteString(record.Time.Format("2006/01/02 15:04:05"))
46 line.WriteString(record.Level.String())
50 line.WriteString(h.prefix)
51 line.WriteString(record.Message)
55 for _, attr := range h.attrs {
56 daemonLogAttr(&line, "", attr, &cause)
59 record.Attrs(func(attr slog.Attr) bool {
60 daemonLogAttr(&line, h.group, attr, &cause)
66 line.WriteString(": ")
67 line.WriteString(cause)
72 defer h.mutex.Unlock()
74 _, err := io.WriteString(h.out, line.String())
76 return fmt.Errorf("write daemon log: %w", err)
82 func (h *DaemonLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
84 clone.attrs = append([]slog.Attr(nil), h.attrs...)
85 for _, attr := range attrs {
87 attr = slog.Group(strings.TrimSuffix(h.group, "."), attr)
90 clone.attrs = append(clone.attrs, attr)
96 func (h *DaemonLogHandler) WithGroup(name string) slog.Handler {
99 clone.group += name + "."
105 // daemonLogAttr resolves and writes one attribute, or keeps its cause for the
106 // end of the line. Groups flatten into dotted keys and empty attributes produce
108 func daemonLogAttr(line *strings.Builder, group string, attr slog.Attr, cause *string) {
109 attr.Value = attr.Value.Resolve()
110 if attr.Equal(slog.Attr{}) {
114 if attr.Value.Kind() == slog.KindGroup {
116 group += attr.Key + "."
119 for _, child := range attr.Value.Group() {
120 daemonLogAttr(line, group, child, cause)
126 if attr.Key == "err" {
127 if attr.Value.Any() != nil {
128 *cause = fmt.Sprint(attr.Value.Any())
135 line.WriteString(group + attr.Key)
138 if attr.Value.Kind() == slog.KindDuration {
139 line.WriteString(durationText(attr.Value.Duration()))
141 fmt.Fprint(line, attr.Value.Any())