-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
145 lines (126 loc) · 3.99 KB
/
main.go
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
package slogpfx
import (
"context"
"log/slog"
"slices"
"strings"
)
const (
ansiCyan = "\x1b[36m"
ansiReset = "\x1b[0m"
)
type HandlerOptions struct {
PrefixKeys []string // A list of keys used to fetch prefix values from the log record.
// PrefixFormatter is a function to format the prefix of the log record.
// If it's not set, the DefaultPrefixFormatter with ColorizePrefix wrapper is used.
PrefixFormatter func(prefixes []slog.Value) string
}
// Handler is a custom slog handler that wraps another slog.Handler to prepend a prefix to the log
// messages. The prefix is sourced from the log record's attributes using the keys specified
// in PrefixKeys.
type Handler struct {
Next slog.Handler // The next log handler in the chain.
opts HandlerOptions // Configuration options for this handler.
prefixes []slog.Value // Cached list of prefix values.
}
var _ slog.Handler = (*Handler)(nil)
// NewHandler creates a new prefix logging handler.
// The new handler will prepend a prefix sourced from the log record's attributes to each log
// message before passing the record to the next handler.
func NewHandler(next slog.Handler, opts *HandlerOptions) *Handler {
if opts == nil {
opts = &HandlerOptions{}
}
return &Handler{
Next: next,
opts: *opts,
prefixes: make([]slog.Value, len(opts.PrefixKeys)),
}
}
func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool {
return h.Next.Enabled(ctx, level)
}
// Handle processes a log record, prepending a prefix to its message if needed, and then passes the
// record to the next handler.
func (h *Handler) Handle(ctx context.Context, r slog.Record) error {
prefixes := h.prefixes
if r.NumAttrs() > 0 {
nr := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
attrs := make([]slog.Attr, 0, r.NumAttrs())
r.Attrs(func(a slog.Attr) bool {
attrs = append(attrs, a)
return true
})
if p, changed := h.extractPrefixes(attrs); changed {
nr.AddAttrs(attrs...)
r = nr
prefixes = p
}
}
f := h.opts.PrefixFormatter
if f == nil {
f = ColorizePrefix(DefaultPrefixFormatter)
}
r.Message = f(prefixes) + r.Message
return h.Next.Handle(ctx, r)
}
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
p, _ := h.extractPrefixes(attrs)
return &Handler{
Next: h.Next.WithAttrs(attrs),
opts: h.opts,
prefixes: p,
}
}
func (h *Handler) WithGroup(name string) slog.Handler {
return &Handler{
Next: h.Next.WithGroup(name),
opts: h.opts,
prefixes: h.prefixes,
}
}
// extractPrefixes scans the attributes for keys specified in PrefixKeys.
// If found, their values are saved in a new prefix list.
// The original attribute list will be modified to remove the extracted prefix attributes.
func (h *Handler) extractPrefixes(attrs []slog.Attr) (prefixes []slog.Value, changed bool) {
prefixes = h.prefixes
for i, attr := range attrs {
idx := slices.IndexFunc(h.opts.PrefixKeys, func(s string) bool { return s == attr.Key })
if idx >= 0 {
if !changed {
// make a copy of prefixes:
prefixes = make([]slog.Value, len(h.prefixes))
copy(prefixes, h.prefixes)
}
prefixes[idx] = attr.Value
attrs[i] = slog.Attr{} // remove the prefix attribute
changed = true
}
}
return
}
// DefaultPrefixFormatter constructs a prefix string by joining all detected prefix values using ":".
// A " > " suffix is added at the end of the prefix string.
func DefaultPrefixFormatter(prefixes []slog.Value) string {
p := make([]string, 0, len(prefixes))
for _, prefix := range prefixes {
if prefix.Any() == nil || prefix.String() == "" {
continue // skip empty prefixes
}
p = append(p, prefix.String())
}
if len(p) == 0 {
return ""
}
return strings.Join(p, ":") + " > "
}
// ColorizePrefix wraps a prefix formatter function to colorize its output with cyan ANSI codes.
func ColorizePrefix(f func(prefixes []slog.Value) string) func(prefixes []slog.Value) string {
return func(prefixes []slog.Value) string {
p := f(prefixes)
if p == "" {
return ""
}
return ansiCyan + p + ansiReset
}
}