124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
package splinter
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// Logger fans out log records to one or more Streams. Streams are fixed at
|
|
// construction time; child loggers from With share the same streams.
|
|
type Logger struct {
|
|
streams []Stream
|
|
baseAttrs map[string]any
|
|
onError func(stream string, rec Record, err error)
|
|
}
|
|
|
|
// Option configures a Logger.
|
|
type Option func(*Logger)
|
|
|
|
// WithStream adds a Stream to the logger.
|
|
func WithStream(s Stream) Option {
|
|
return func(l *Logger) { l.streams = append(l.streams, s) }
|
|
}
|
|
|
|
// WithAttrs pre-populates attributes on every record produced by this logger.
|
|
func WithAttrs(attrs map[string]any) Option {
|
|
return func(l *Logger) { maps.Copy(l.baseAttrs, attrs) }
|
|
}
|
|
|
|
// WithErrorHandler sets a callback invoked when a stream's Write fails.
|
|
// The default handler prints to stderr.
|
|
func WithErrorHandler(fn func(stream string, rec Record, err error)) Option {
|
|
return func(l *Logger) { l.onError = fn }
|
|
}
|
|
|
|
// New creates a Logger. With no streams supplied, a JSON ConsoleStream at
|
|
// LevelInfo writing to stderr is used.
|
|
func New(opts ...Option) *Logger {
|
|
l := &Logger{
|
|
baseAttrs: make(map[string]any),
|
|
onError: defaultOnError,
|
|
}
|
|
for _, o := range opts {
|
|
o(l)
|
|
}
|
|
if len(l.streams) == 0 {
|
|
l.streams = append(l.streams, NewConsoleStream(ConsoleJSON, LevelInfo))
|
|
}
|
|
return l
|
|
}
|
|
|
|
// With returns a child Logger with additional attributes merged onto the
|
|
// base. The child shares streams and the error handler with the parent.
|
|
func (l *Logger) With(attrs map[string]any) *Logger {
|
|
merged := make(map[string]any, len(l.baseAttrs)+len(attrs))
|
|
maps.Copy(merged, l.baseAttrs)
|
|
maps.Copy(merged, attrs)
|
|
return &Logger{
|
|
streams: l.streams,
|
|
baseAttrs: merged,
|
|
onError: l.onError,
|
|
}
|
|
}
|
|
|
|
// Debug logs at LevelDebug.
|
|
func (l *Logger) Debug(msg string, args ...any) { l.log(LevelDebug, msg, args...) }
|
|
|
|
// Info logs at LevelInfo.
|
|
func (l *Logger) Info(msg string, args ...any) { l.log(LevelInfo, msg, args...) }
|
|
|
|
// Warn logs at LevelWarn.
|
|
func (l *Logger) Warn(msg string, args ...any) { l.log(LevelWarn, msg, args...) }
|
|
|
|
// Error logs at LevelError.
|
|
func (l *Logger) Error(msg string, args ...any) { l.log(LevelError, msg, args...) }
|
|
|
|
// Close shuts down all streams. Returns the first error encountered, but
|
|
// always attempts to close every stream.
|
|
func (l *Logger) Close() error {
|
|
var firstErr error
|
|
for _, s := range l.streams {
|
|
if err := s.Close(); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
return firstErr
|
|
}
|
|
|
|
func (l *Logger) log(level Level, msg string, args ...any) {
|
|
rec := Record{
|
|
Time: time.Now(),
|
|
Level: level,
|
|
Message: msg,
|
|
Attrs: l.buildAttrs(args),
|
|
}
|
|
ctx := context.Background()
|
|
for _, s := range l.streams {
|
|
if !s.Enabled(level) {
|
|
continue
|
|
}
|
|
if err := s.Write(ctx, rec); err != nil {
|
|
l.onError(s.Name(), rec, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *Logger) buildAttrs(args []any) map[string]any {
|
|
attrs := make(map[string]any, len(l.baseAttrs)+len(args)/2)
|
|
maps.Copy(attrs, l.baseAttrs)
|
|
for i := 0; i+1 < len(args); i += 2 {
|
|
key, ok := args[i].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
attrs[key] = args[i+1]
|
|
}
|
|
return attrs
|
|
}
|
|
|
|
func defaultOnError(stream string, _ Record, err error) {
|
|
fmt.Fprintf(os.Stderr, "splinter: stream %q write failed: %v\n", stream, err)
|
|
}
|