mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-20 14:41:31 +03:00
7c56377b40
* Field alignment
134 lines
3.0 KiB
Go
134 lines
3.0 KiB
Go
// 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
|
|
}
|