aboutsummaryrefslogtreecommitdiffstats
path: root/logging/chain.go
blob: d6ccb8a7403d19f495ee93e982d0b58204a4cdca (plain) (blame)
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)
}