Skip to content

Commit c3b89c2

Browse files
committed
slog: add an slog backed implementation of Logger
1 parent aab9422 commit c3b89c2

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed

attrs.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package btclog
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"log/slog"
7+
)
8+
9+
// Hex is a convenience function for a hex-encoded log attributes.
10+
func Hex(key string, value []byte) slog.Attr {
11+
h := hex.EncodeToString(value)
12+
13+
return slog.String(key, h)
14+
}
15+
16+
type attrsKey struct{}
17+
18+
// WithCtx returns a copy of the context with which the logging attributes are
19+
// associated.
20+
//
21+
// Usage:
22+
//
23+
// ctx := log.WithCtx(ctx, "height", 1234)
24+
// ...
25+
// log.Info(ctx, "Height processed") // Will contain attribute: height=1234
26+
func WithCtx(ctx context.Context, attrs ...any) context.Context {
27+
return context.WithValue(ctx, attrsKey{}, mergeAttrs(ctx, attrs))
28+
}
29+
30+
// mergeAttrs returns the attributes from the context merged with the provided attributes.
31+
func mergeAttrs(ctx context.Context, attrs []any) []any {
32+
resp, _ := ctx.Value(attrsKey{}).([]any) // We know the type.
33+
resp = append(resp, attrs...)
34+
35+
return resp
36+
}

slog.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package btclog
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
)
8+
9+
// Handler wraps the slog.Handler interface with a few more methods that we
10+
// need in order to satisfy the Logger interface.
11+
type Handler interface {
12+
slog.Handler
13+
14+
// Level returns the current logging level of the Handler.
15+
Level() Level
16+
17+
// SetLevel changes the logging level of the Handler to the passed
18+
// level.
19+
SetLevel(level Level)
20+
}
21+
22+
// sLogger is an implementation of Logger backed by a structured sLogger.
23+
type sLogger struct {
24+
Handler
25+
logger *slog.Logger
26+
}
27+
28+
// NewSLogger constructs a new structured logger from the given Handler.
29+
func NewSLogger(handler Handler) Logger {
30+
return &sLogger{
31+
Handler: handler,
32+
logger: slog.New(handler),
33+
}
34+
}
35+
36+
// Tracef formats message according to format specifier, prepends the prefix as
37+
// necessary, and writes to log with LevelTrace.
38+
//
39+
// This is part of the Logger interface implementation.
40+
func (l *sLogger) Tracef(format string, params ...any) {
41+
l.toSlogf(LevelTrace, format, params...)
42+
}
43+
44+
// Debugf formats message according to format specifier, prepends the prefix as
45+
// necessary, and writes to log with LevelDebug.
46+
//
47+
// This is part of the Logger interface implementation.
48+
func (l *sLogger) Debugf(format string, params ...any) {
49+
l.toSlogf(LevelDebug, format, params...)
50+
}
51+
52+
// Infof formats message according to format specifier, prepends the prefix as
53+
// necessary, and writes to log with LevelInfo.
54+
//
55+
// This is part of the Logger interface implementation.
56+
func (l *sLogger) Infof(format string, params ...any) {
57+
l.toSlogf(LevelInfo, format, params...)
58+
}
59+
60+
// Warnf formats message according to format specifier, prepends the prefix as
61+
// necessary, and writes to log with LevelWarn.
62+
//
63+
// This is part of the Logger interface implementation.
64+
func (l *sLogger) Warnf(format string, params ...any) {
65+
l.toSlogf(LevelWarn, format, params...)
66+
}
67+
68+
// Errorf formats message according to format specifier, prepends the prefix as
69+
// necessary, and writes to log with LevelError.
70+
//
71+
// This is part of the Logger interface implementation.
72+
func (l *sLogger) Errorf(format string, params ...any) {
73+
l.toSlogf(LevelError, format, params...)
74+
}
75+
76+
// Criticalf formats message according to format specifier, prepends the prefix as
77+
// necessary, and writes to log with LevelCritical.
78+
//
79+
// This is part of the Logger interface implementation.
80+
func (l *sLogger) Criticalf(format string, params ...any) {
81+
l.toSlogf(LevelCritical, format, params...)
82+
}
83+
84+
// Trace formats message using the default formats for its operands, prepends
85+
// the prefix as necessary, and writes to log with LevelTrace.
86+
//
87+
// This is part of the Logger interface implementation.
88+
func (l *sLogger) Trace(v ...any) {
89+
l.toSlog(LevelTrace, v...)
90+
}
91+
92+
// Debug formats message using the default formats for its operands, prepends
93+
// the prefix as necessary, and writes to log with LevelDebug.
94+
//
95+
// This is part of the Logger interface implementation.
96+
func (l *sLogger) Debug(v ...any) {
97+
l.toSlog(LevelDebug, v...)
98+
}
99+
100+
// Info formats message using the default formats for its operands, prepends
101+
// the prefix as necessary, and writes to log with LevelInfo.
102+
//
103+
// This is part of the Logger interface implementation.
104+
func (l *sLogger) Info(v ...any) {
105+
l.toSlog(LevelInfo, v...)
106+
}
107+
108+
// Warn formats message using the default formats for its operands, prepends
109+
// the prefix as necessary, and writes to log with LevelWarn.
110+
//
111+
// This is part of the Logger interface implementation.
112+
func (l *sLogger) Warn(v ...any) {
113+
l.toSlog(LevelWarn, v...)
114+
}
115+
116+
// Error formats message using the default formats for its operands, prepends
117+
// the prefix as necessary, and writes to log with LevelError.
118+
//
119+
// This is part of the Logger interface implementation.
120+
func (l *sLogger) Error(v ...any) {
121+
l.toSlog(LevelError, v...)
122+
}
123+
124+
// Critical formats message using the default formats for its operands, prepends
125+
// the prefix as necessary, and writes to log with LevelCritical.
126+
//
127+
// This is part of the Logger interface implementation.
128+
func (l *sLogger) Critical(v ...any) {
129+
l.toSlog(LevelCritical, v...)
130+
}
131+
132+
// TraceS writes a structured log with the given message and key-value pair
133+
// attributes with LevelTrace to the log.
134+
//
135+
// This is part of the Logger interface implementation.
136+
func (l *sLogger) TraceS(ctx context.Context, msg string, attrs ...any) {
137+
l.toSlogS(ctx, LevelTrace, msg, attrs...)
138+
}
139+
140+
// DebugS writes a structured log with the given message and key-value pair
141+
// attributes with LevelDebug to the log.
142+
//
143+
// This is part of the Logger interface implementation.
144+
func (l *sLogger) DebugS(ctx context.Context, msg string, attrs ...any) {
145+
l.toSlogS(ctx, LevelDebug, msg, attrs...)
146+
}
147+
148+
// InfoS writes a structured log with the given message and key-value pair
149+
// attributes with LevelInfo to the log.
150+
//
151+
// This is part of the Logger interface implementation.
152+
func (l *sLogger) InfoS(ctx context.Context, msg string, attrs ...any) {
153+
l.toSlogS(ctx, LevelInfo, msg, attrs...)
154+
}
155+
156+
// WarnS writes a structured log with the given message and key-value pair
157+
// attributes with LevelWarn to the log.
158+
//
159+
// This is part of the Logger interface implementation.
160+
func (l *sLogger) WarnS(ctx context.Context, msg string, err error,
161+
attrs ...any) {
162+
163+
if err != nil {
164+
attrs = append([]any{slog.String("err", err.Error())}, attrs...)
165+
}
166+
167+
l.toSlogS(ctx, LevelWarn, msg, attrs...)
168+
}
169+
170+
// ErrorS writes a structured log with the given message and key-value pair
171+
// attributes with LevelError to the log.
172+
//
173+
// This is part of the Logger interface implementation.
174+
func (l *sLogger) ErrorS(ctx context.Context, msg string, err error,
175+
attrs ...any) {
176+
177+
if err != nil {
178+
attrs = append([]any{slog.String("err", err.Error())}, attrs...)
179+
}
180+
181+
l.toSlogS(ctx, LevelError, msg, attrs...)
182+
}
183+
184+
// CriticalS writes a structured log with the given message and key-value pair
185+
// attributes with LevelCritical to the log.
186+
//
187+
// This is part of the Logger interface implementation.
188+
func (l *sLogger) CriticalS(ctx context.Context, msg string, err error,
189+
attrs ...any) {
190+
if err != nil {
191+
attrs = append([]any{slog.String("err", err.Error())}, attrs...)
192+
}
193+
194+
l.toSlogS(ctx, LevelCritical, msg, attrs...)
195+
}
196+
197+
// toSlogf is a helper method that converts an unstructured log call that
198+
// contains a format string and parameters for the string into the appropriate
199+
// form expected by the structured logger.
200+
func (l *sLogger) toSlogf(level Level, format string, params ...any) {
201+
l.logger.Log(context.Background(), slog.Level(level),
202+
fmt.Sprintf(format, params...))
203+
}
204+
205+
// toSlog is a helper method that converts an unstructured log call that
206+
// contains a number of parameters into the appropriate form expected by the
207+
// structured logger.
208+
func (l *sLogger) toSlog(level Level, v ...any) {
209+
l.logger.Log(context.Background(), slog.Level(level), fmt.Sprint(v...))
210+
}
211+
212+
// toSlogS is a helper method that can be used by all the structured log calls
213+
// to access the underlying logger.
214+
func (l *sLogger) toSlogS(ctx context.Context, level Level, msg string,
215+
attrs ...any) {
216+
217+
l.logger.Log(ctx, slog.Level(level), msg, mergeAttrs(ctx, attrs)...)
218+
}
219+
220+
var _ Logger = (*sLogger)(nil)

0 commit comments

Comments
 (0)