Repository navigation
Expand file tree
/
Copy pathslog.go
More file actions
123 lines (101 loc) · 2.91 KB
/
Copy pathslog.go
File metadata and controls
123 lines (101 loc) · 2.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package logg
import (
"context"
"io"
"log/slog"
"strings"
)
// SlogHandler returns a slog.Handler that emits records through this logger,
// honoring its configured format (pretty/json), time flags, color and minimum
// level. slog attributes become structured fields; groups are flattened into
// dotted key prefixes (e.g. "req.id").
//
// The logger's file flags (Lshortfile/Llongfile) are ignored for slog records
// because logg's caller lookup cannot see through slog's call frames.
func (l *Logg) SlogHandler() slog.Handler {
return &slogHandler{l: l}
}
// NewSlog is a convenience constructor that returns a *slog.Logger backed by a
// new logg writing to w.
func NewSlog(w io.Writer) *slog.Logger {
return slog.New(New(w).SlogHandler())
}
type slogHandler struct {
l *Logg
groups []string
attrs []Field
}
func (h *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
return h.l.enabled(fromSlogLevel(level))
}
func (h *slogHandler) Handle(_ context.Context, r slog.Record) error {
fields := make([]Field, 0, len(h.attrs)+r.NumAttrs())
fields = append(fields, h.attrs...)
r.Attrs(func(a slog.Attr) bool {
fields = appendAttr(fields, h.groups, a)
return true
})
h.l.writeRecord(fromSlogLevel(r.Level), fields, r.Message)
return nil
}
func (h *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if len(attrs) == 0 {
return h
}
merged := make([]Field, len(h.attrs), len(h.attrs)+len(attrs))
copy(merged, h.attrs)
for _, a := range attrs {
merged = appendAttr(merged, h.groups, a)
}
return &slogHandler{l: h.l, groups: h.groups, attrs: merged}
}
func (h *slogHandler) WithGroup(name string) slog.Handler {
if name == "" {
return h
}
groups := make([]string, len(h.groups), len(h.groups)+1)
copy(groups, h.groups)
groups = append(groups, name)
return &slogHandler{l: h.l, groups: groups, attrs: h.attrs}
}
// fromSlogLevel maps a slog.Level onto a logg level.
func fromSlogLevel(level slog.Level) level {
switch {
case level < slog.LevelInfo:
return DebugLevel
case level < slog.LevelWarn:
return InfoLevel
case level < slog.LevelError:
return WarningLevel
default:
return ErrorLevel
}
}
// appendAttr flattens a slog.Attr (resolving values and recursing into groups)
// into dst, prefixing keys with the current group path joined by '.'.
func appendAttr(dst []Field, groups []string, a slog.Attr) []Field {
a.Value = a.Value.Resolve()
if a.Value.Kind() == slog.KindGroup {
attrs := a.Value.Group()
if len(attrs) == 0 {
return dst
}
// A group with an empty key is inlined without adding to the prefix.
next := groups
if a.Key != "" {
next = append(append(make([]string, 0, len(groups)+1), groups...), a.Key)
}
for _, ga := range attrs {
dst = appendAttr(dst, next, ga)
}
return dst
}
if a.Key == "" {
return dst
}
key := a.Key
if len(groups) > 0 {
key = strings.Join(groups, ".") + "." + a.Key
}
return append(dst, F(key, a.Value.Any()))
}