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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
package logging
import (
"context"
"iter"
"log/slog"
"slices"
)
func attrAll(r slog.Record) iter.Seq[slog.Attr] {
return func(yield func(slog.Attr) bool) {
r.Attrs(func(attr slog.Attr) bool {
return yield(attr)
})
}
}
// GetNormalizedAttrs extracts, resolves, filters, and flattens all attributes
// from a slog.Record into a ready-to-process slice of slog.Attr.
// This helper is essential for any custom slog.Handler implementation
// that needs to process the full set of attributes.
func GetNormalizedAttrs(r slog.Record) []slog.Attr {
attrs := attrAll(r)
return normalizeAttrsSlice(attrs)
}
// normalizeAttrsSlice contains the core logic for resolving, filtering, and flattening.
// This function must be private as it is a utility for GetNormalizedAttrs.
func normalizeAttrsSlice(attrs iter.Seq[slog.Attr]) []slog.Attr {
var out []slog.Attr
for attr := range attrs {
if attr.Equal(slog.Attr{}) {
continue
}
v := attr.Value.Resolve()
switch v.Kind() {
case slog.KindGroup:
// Skip empty group
if len(v.Group()) == 0 {
continue
}
inner := normalizeAttrsSlice(slices.Values(v.Group()))
// Flatten anonymous group (Key == "")
if attr.Key == "" {
out = append(out, inner...)
} else {
// Named group, keep as a GroupValue
out = append(out, slog.Attr{
Key: attr.Key,
Value: slog.GroupValue(inner...),
})
}
default:
// Regular attribute
out = append(out, slog.Attr{
Key: attr.Key,
Value: v,
})
}
}
return out
}
type chainingAttrHandler struct {
parent slog.Handler
attrs []slog.Attr
}
var _ slog.Handler = (*chainingAttrHandler)(nil)
// ChainAttrs stores attributes/groups added via WithAttrs/WithGroup
// and ensures they are applied to the record before passing it to the next handler.
func ChainAttrs(h slog.Handler, attrs []slog.Attr) slog.Handler {
if len(attrs) == 0 {
return h
}
return &chainingAttrHandler{
parent: h,
attrs: attrs,
}
}
func (h *chainingAttrHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.parent.Enabled(ctx, level)
}
func (h *chainingAttrHandler) Handle(ctx context.Context, r slog.Record) error {
newRecord := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
attrs := slices.Collect(attrAll(r))
newRecord.AddAttrs(h.attrs...)
newRecord.AddAttrs(attrs...)
return h.parent.Handle(ctx, newRecord)
}
func (h *chainingAttrHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return ChainAttrs(h, attrs)
}
func (h *chainingAttrHandler) WithGroup(name string) slog.Handler {
return ChainGroup(h, name)
}
type chainingGroupHandler struct {
parent slog.Handler
group string
}
// ChainGroup stores attributes/groups added via WithAttrs/WithGroup
// and ensures they are applied to the record before passing it to the next handler.
func ChainGroup(h slog.Handler, name string) slog.Handler {
if name == "" {
return h
}
return &chainingGroupHandler{
parent: h,
group: name,
}
}
var _ slog.Handler = (*chainingGroupHandler)(nil)
func (h *chainingGroupHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.parent.Enabled(ctx, level)
}
func (h *chainingGroupHandler) Handle(ctx context.Context, r slog.Record) error {
newRecord := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
newRecord.AddAttrs(slog.Attr{
Key: h.group,
Value: slog.GroupValue(GetNormalizedAttrs(r)...),
})
return h.parent.Handle(ctx, newRecord)
}
func (h *chainingGroupHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return ChainAttrs(h, attrs)
}
func (h *chainingGroupHandler) WithGroup(name string) slog.Handler {
return ChainGroup(h, name)
}
|