Skip to content

Commit

Permalink
slog: add an slog backed implementation of Logger
Browse files Browse the repository at this point in the history
  • Loading branch information
ellemouton committed Sep 16, 2024
1 parent aab9422 commit d81e157
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 0 deletions.
36 changes: 36 additions & 0 deletions attrs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package btclog

import (
"context"
"encoding/hex"
"log/slog"
)

// Hex is a convenience function for a hex-encoded log attributes.
func Hex(key string, value []byte) slog.Attr {
h := hex.EncodeToString(value)

return slog.String(key, h)
}

type attrsKey struct{}

// WithCtx returns a copy of the context with which the logging attributes are
// associated.
//
// Usage:
//
// ctx := log.WithCtx(ctx, "height", 1234)
// ...
// log.Info(ctx, "Height processed") // Will contain attribute: height=1234
func WithCtx(ctx context.Context, attrs ...any) context.Context {
return context.WithValue(ctx, attrsKey{}, mergeAttrs(ctx, attrs))
}

// mergeAttrs returns the attributes from the context merged with the provided attributes.
func mergeAttrs(ctx context.Context, attrs []any) []any {
resp, _ := ctx.Value(attrsKey{}).([]any) // We know the type.
resp = append(resp, attrs...)

return resp
}
217 changes: 217 additions & 0 deletions slog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package btclog

import (
"context"
"fmt"
"log/slog"
)

// Handler wraps the slog.Handler interface with a few more methods that we
// need in order to satisfy the Logger interface.
type Handler interface {
slog.Handler

// Level returns the current logging level of the Handler.
Level() Level

// SetLevel changes the logging level of the Handler to the passed
// level.
SetLevel(level Level)
}

// sLogger is an implementation of Logger backed by a structured sLogger.
type sLogger struct {
Handler
logger *slog.Logger
}

// NewSLogger constructs a new structured logger from the given Handler.
func NewSLogger(handler Handler) Logger {
return &sLogger{
Handler: handler,
logger: slog.New(handler),
}
}

// Tracef formats message according to format specifier, prepends the prefix as
// necessary, and writes to log with LevelTrace.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Tracef(format string, params ...any) {
l.toSlogf(LevelTrace, format, params...)
}

// Debugf formats message according to format specifier, prepends the prefix as
// necessary, and writes to log with LevelDebug.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Debugf(format string, params ...any) {
l.toSlogf(LevelDebug, format, params...)
}

// Infof formats message according to format specifier, prepends the prefix as
// necessary, and writes to log with LevelInfo.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Infof(format string, params ...any) {
l.toSlogf(LevelInfo, format, params...)
}

// Warnf formats message according to format specifier, prepends the prefix as
// necessary, and writes to log with LevelWarn.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Warnf(format string, params ...any) {
l.toSlogf(LevelWarn, format, params...)
}

// Errorf formats message according to format specifier, prepends the prefix as
// necessary, and writes to log with LevelError.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Errorf(format string, params ...any) {
l.toSlogf(LevelError, format, params...)
}

// Criticalf formats message according to format specifier, prepends the prefix as
// necessary, and writes to log with LevelCritical.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Criticalf(format string, params ...any) {
l.toSlogf(LevelCritical, format, params...)
}

// Trace formats message using the default formats for its operands, prepends
// the prefix as necessary, and writes to log with LevelTrace.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Trace(v ...any) {
l.toSlog(LevelTrace, v...)
}

// Debug formats message using the default formats for its operands, prepends
// the prefix as necessary, and writes to log with LevelDebug.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Debug(v ...any) {
l.toSlog(LevelDebug, v...)
}

// Info formats message using the default formats for its operands, prepends
// the prefix as necessary, and writes to log with LevelInfo.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Info(v ...any) {
l.toSlog(LevelInfo, v...)
}

// Warn formats message using the default formats for its operands, prepends
// the prefix as necessary, and writes to log with LevelWarn.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Warn(v ...any) {
l.toSlog(LevelWarn, v...)
}

// Error formats message using the default formats for its operands, prepends
// the prefix as necessary, and writes to log with LevelError.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Error(v ...any) {
l.toSlog(LevelError, v...)
}

// Critical formats message using the default formats for its operands, prepends
// the prefix as necessary, and writes to log with LevelCritical.
//
// This is part of the Logger interface implementation.
func (l *sLogger) Critical(v ...any) {
l.toSlog(LevelCritical, v...)
}

// TraceS writes a structured log with the given message and key-value pair
// attributes with LevelTrace to the log.
//
// This is part of the Logger interface implementation.
func (l *sLogger) TraceS(ctx context.Context, msg string, attrs ...any) {
l.logger.Log(ctx, slog.Level(LevelTrace), msg,
mergeAttrs(ctx, attrs)...)
}

// DebugS writes a structured log with the given message and key-value pair
// attributes with LevelDebug to the log.
//
// This is part of the Logger interface implementation.
func (l *sLogger) DebugS(ctx context.Context, msg string, attrs ...any) {
l.logger.Log(ctx, slog.Level(LevelDebug), msg,
mergeAttrs(ctx, attrs)...)
}

// InfoS writes a structured log with the given message and key-value pair
// attributes with LevelInfo to the log.
//
// This is part of the Logger interface implementation.
func (l *sLogger) InfoS(ctx context.Context, msg string, attrs ...any) {
l.logger.Log(ctx, slog.Level(LevelInfo), msg,
mergeAttrs(ctx, attrs)...)
}

// WarnS writes a structured log with the given message and key-value pair
// attributes with LevelWarn to the log.
//
// This is part of the Logger interface implementation.
func (l *sLogger) WarnS(ctx context.Context, msg string, err error,
attrs ...any) {

if err != nil {
attrs = append([]any{slog.String("err", err.Error())}, attrs...)
}

l.logger.Log(ctx, slog.Level(LevelWarn), msg, mergeAttrs(ctx, attrs)...)
}

// ErrorS writes a structured log with the given message and key-value pair
// attributes with LevelError to the log.
//
// This is part of the Logger interface implementation.
func (l *sLogger) ErrorS(ctx context.Context, msg string, err error,
attrs ...any) {

if err != nil {
attrs = append([]any{slog.String("err", err.Error())}, attrs...)
}

l.logger.Log(ctx, slog.Level(LevelError), msg,
mergeAttrs(ctx, attrs)...)
}

// CriticalS writes a structured log with the given message and key-value pair
// attributes with LevelCritical to the log.
//
// This is part of the Logger interface implementation.
func (l *sLogger) CriticalS(ctx context.Context, msg string, err error,
attrs ...any) {
if err != nil {
attrs = append([]any{slog.String("err", err.Error())}, attrs...)
}

l.logger.Log(ctx, slog.Level(LevelCritical), msg,
mergeAttrs(ctx, attrs)...)
}

// toSlogf is a helper method that converts an unstructured log call that
// contains a format string and parameters for the string into the appropriate
// form expected by the structured logger.
func (l *sLogger) toSlogf(level Level, format string, params ...any) {
l.logger.Log(context.Background(), slog.Level(level),
fmt.Sprintf(format, params...))
}

// toSlog is a helper method that converts an unstructured log call that
// contains a number of parameters into the appropriate form expected by the
// structured logger.
func (l *sLogger) toSlog(level Level, v ...any) {
l.logger.Log(context.Background(), slog.Level(level), fmt.Sprint(v...))
}

var _ Logger = (*sLogger)(nil)

0 comments on commit d81e157

Please sign in to comment.