-
Notifications
You must be signed in to change notification settings - Fork 13
/
slogbuf.go
133 lines (105 loc) · 2.98 KB
/
slogbuf.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
// Package slogbuf implements a Buffer that stores log records
// that can later be replayed on a slog.Handler. This is particularly
// useful for bootstrap.
//
// Example: let's say log configuration is stored in an external config source,
// but the config load mechanism itself wants a logger. A slogbuf "boot logger"
// can be passed to the config load mechanism. On successful boot, the "real"
// logger can be created, and slogbuf's records can be replayed on that logger.
// If boot fails, slogbuf's records can be replayed to stderr.
package slogbuf
import (
"context"
"log/slog"
"sync"
"github.com/neilotoole/sq/libsq/core/errz"
)
// New returns a logger, and a buffer that can be used for replay.
func New() (*slog.Logger, *Buffer) {
h := &handler{
buf: &Buffer{},
}
return slog.New(h), h.buf
}
// Buffer stores slog records that can be replayed via Buffer.Flush.
type Buffer struct {
entries []entry
mu sync.Mutex
}
type entry struct {
handler *handler
record slog.Record
}
func (b *Buffer) append(h *handler, record slog.Record) {
b.mu.Lock()
defer b.mu.Unlock()
b.entries = append(b.entries, entry{
handler: h,
record: record,
})
}
// Flush replays the buffer on dest. If an error occurs writing the
// log records to dest, Flush returns immediately (and does not write
// any remaining records). The buffer drains, even if an error occurs.
func (b *Buffer) Flush(ctx context.Context, dest slog.Handler) error {
if dest == nil {
return errz.New("flush log buffer: dest is nil")
}
b.mu.Lock()
defer b.mu.Unlock()
defer func() { b.entries = nil }()
for i := range b.entries {
d, h, rec := dest, b.entries[i].handler, b.entries[i].record
if !d.Enabled(ctx, rec.Level) {
continue
}
d = d.WithAttrs(h.attrs)
for _, g := range h.groups {
d = d.WithGroup(g)
}
if err := d.Handle(ctx, rec); err != nil {
return err
}
}
return nil
}
var _ slog.Handler = (*handler)(nil)
// handler implements slog.Handler.
type handler struct {
buf *Buffer
attrs []slog.Attr
groups []string
}
// Enabled implements slog.Handler.
func (h *handler) Enabled(_ context.Context, _ slog.Level) bool {
return true
}
// WithAttrs implements slog.Handler.
func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler {
h2 := &handler{buf: h.buf}
if h.groups != nil {
h2.groups = make([]string, len(h.groups))
copy(h2.groups, h.groups)
}
h2.attrs = make([]slog.Attr, len(h.attrs), len(h.attrs)+len(attrs))
copy(h2.attrs, h.attrs)
h2.attrs = append(h2.attrs, attrs...)
return h2
}
// WithGroup implements slog.Handler.
func (h *handler) WithGroup(name string) slog.Handler {
h2 := &handler{buf: h.buf}
if h.attrs != nil {
h2.attrs = make([]slog.Attr, len(h.attrs))
copy(h2.attrs, h.attrs)
}
h2.groups = make([]string, len(h.groups)+1)
copy(h2.groups, h.groups)
h2.groups[len(h2.groups)-1] = name
return h2
}
// Handle implements slog.Handler.
func (h *handler) Handle(_ context.Context, record slog.Record) error {
h.buf.append(h, record)
return nil
}