Skip to content

Commit 42ee180

Browse files
authored
feat: allow adding attribute via context function (#25)
This is useful if you only have access to the context values via functions.
1 parent d0ef590 commit 42ee180

File tree

4 files changed

+73
-4
lines changed

4 files changed

+73
-4
lines changed

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,15 @@ customLogger := sloggorm.New(
125125

126126
slogGorm.WithErrorField("err"), // instead of "error" (by default)
127127

128-
slogGorm.WithContextValue("slogAttrName", "ctxKey"), // adds an slog.Attr if a value if found for this key in the Gorm's query context
128+
slogGorm.WithContextValue("slogAttrName1", "ctxKey"), // adds an slog.Attr if a value is found for this key in the Gorm's query context
129+
130+
slogGorm.WithContextFunc("slogAttrName2", func(ctx context.Context) (slog.Value, bool) {
131+
v, ok := ctx.Value(ctxKey1).(time.Duration)
132+
if !ok {
133+
return slog.Value{}, false
134+
}
135+
return slog.DurationValue(v), true
136+
}), // adds an slog.Attr if the given function returns an slog.Value and true
129137
)
130138
```
131139

logger.go

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type logger struct {
6868
logLevel map[LogType]slog.Level
6969
gormLevel gormlogger.LogLevel
7070
contextKeys map[string]string
71+
contextFuncs map[string]func(context.Context) (slog.Value, bool)
7172

7273
sourceField string
7374
errorField string
@@ -201,5 +202,10 @@ func (l logger) appendContextAttributes(ctx context.Context, args []any) []any {
201202
args = append(args, slog.Any(k, value))
202203
}
203204
}
205+
for k, f := range l.contextFuncs {
206+
if value, ok := f(ctx); ok {
207+
args = append(args, slog.Any(k, value))
208+
}
209+
}
204210
return args
205211
}

logger_test.go

+44-3
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,23 @@ func Test_logger_LogMode(t *testing.T) {
9292
}
9393

9494
func Test_logger(t *testing.T) {
95-
receiver, gormLogger := getReceiverAndLogger([]Option{WithContextValue("attrKey", "ctxKey")})
95+
receiver, gormLogger := getReceiverAndLogger([]Option{
96+
WithContextValue("attrKeyViaValue", "ctxKey"),
97+
WithContextFunc("attrKeyViaFunc1", func(ctx context.Context) (slog.Value, bool) {
98+
v, ok := ctx.Value(ctxKey1).(string)
99+
if !ok {
100+
return slog.Value{}, false
101+
}
102+
return slog.StringValue(v), true
103+
}),
104+
WithContextFunc("attrKeyViaFunc2", func(ctx context.Context) (slog.Value, bool) {
105+
v, ok := ctx.Value(ctxKey2).(time.Duration)
106+
if !ok {
107+
return slog.Value{}, false
108+
}
109+
return slog.DurationValue(v), true
110+
}),
111+
})
96112
expectedMsg := "awesome message"
97113

98114
tests := []struct {
@@ -111,6 +127,24 @@ func Test_logger(t *testing.T) {
111127
wantMsg: expectedMsg,
112128
wantLevel: slog.LevelInfo,
113129
},
130+
{
131+
name: "with context value and func",
132+
ctx: context.WithValue(
133+
context.WithValue(
134+
context.WithValue(context.Background(), "ctxKey", "ctxVal"),
135+
ctxKey1, "ctxValFunc1",
136+
),
137+
ctxKey2, time.Second,
138+
),
139+
function: gormLogger.Info,
140+
wantMsg: expectedMsg,
141+
wantAttributes: map[string]slog.Attr{
142+
"attrKeyViaValue": slog.Any("attrKeyViaValue", "ctxVal"),
143+
"attrKeyViaFunc1": slog.String("attrKeyViaFunc1", "ctxValFunc1"),
144+
"attrKeyViaFunc2": slog.Duration("attrKeyViaFunc2", time.Second),
145+
},
146+
wantLevel: slog.LevelInfo,
147+
},
114148
{
115149
name: "Warn",
116150
ctx: context.Background(),
@@ -130,7 +164,7 @@ func Test_logger(t *testing.T) {
130164
ctx: context.WithValue(context.Background(), "ctxKey", "ctxVal"),
131165
function: gormLogger.Error,
132166
wantMsg: expectedMsg,
133-
wantAttributes: map[string]slog.Attr{"attrKey": slog.Any("attrKey", "ctxVal")},
167+
wantAttributes: map[string]slog.Attr{"attrKeyViaValue": slog.Any("attrKeyViaValue", "ctxVal")},
134168
wantLevel: slog.LevelError,
135169
},
136170
}
@@ -389,7 +423,7 @@ func Test_logger_Trace(t *testing.T) {
389423
}
390424
}
391425

392-
// private functions
426+
// private helpers
393427

394428
func getReceiverAndLogger(options []Option) (*DummyHandler, *logger) {
395429
receiver := NewDummyHandler()
@@ -398,6 +432,13 @@ func getReceiverAndLogger(options []Option) (*DummyHandler, *logger) {
398432
return receiver, New(options...)
399433
}
400434

435+
type ctxKey int
436+
437+
const (
438+
ctxKey1 ctxKey = iota
439+
ctxKey2
440+
)
441+
401442
// Mock
402443

403444
func NewDummyHandler() *DummyHandler {

options.go

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package slogGorm
22

33
import (
4+
"context"
45
"log/slog"
56
"time"
67
)
@@ -83,3 +84,16 @@ func WithContextValue(slogAttrName, contextKey string) Option {
8384
l.contextKeys[slogAttrName] = contextKey
8485
}
8586
}
87+
88+
// WithContextFunc adds an attribute with the given name and slog.Value returned by the given
89+
// function if the function returns true. No attribute will be added if the function returns false.
90+
// Use this over WithContextValue if your context keys are not strings or only accessible via
91+
// functions.
92+
func WithContextFunc(slogAttrName string, slogValueFunc func(ctx context.Context) (slog.Value, bool)) Option {
93+
return func(l *logger) {
94+
if l.contextFuncs == nil {
95+
l.contextFuncs = make(map[string]func(context.Context) (slog.Value, bool))
96+
}
97+
l.contextFuncs[slogAttrName] = slogValueFunc
98+
}
99+
}

0 commit comments

Comments
 (0)