Skip to content

Commit 317cf8f

Browse files
authored
Add riverlog.LoggerSafely for access outside work body without panic (#1093)
Here, add a "safe" variant of `riverlog.Logger`: `rigerlog.LoggerSafely`. While it'd be rare to not know whether you had a logger or not from inside a `Work` function, `Work` functions might call into other Go modules in your project, those modules might be reused in non-`Work` contexts, and it might not always be clear whether a logger should be available or not. This function lets users do a conditional check before logging: if logger, ok := riverlog.LoggerSafely(ctx); ok { logger.InfoContext(ctx, "Hello from logger") } I went with the "safely" naming according to our conventions elsewhere, but one thing I'm not sure about is that `ClientFromContextSafely` which is a similar function, returns an error instead of `ok` boolean. Here we break with that convention somewhat, but I think the boolean version is more ergonomic. Another option might be to give this a slightly alternative naming convention like `LoggerOK` or `LoggerMaybe`. Fixes #1092.
1 parent 35b5b23 commit 317cf8f

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added `riverlog.LoggerSafely` which provides a non-panic variant of `riverlog.Logger` for use when code may or may not have a context logger available. [PR #1093](https://github.com/riverqueue/river/pull/1093).
13+
1014
## [0.27.1] - 2025-11-21
1115

1216
### Fixed

riverlog/river_log.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,27 @@ type contextKey struct{}
2727
// Logger extracts a logger from context from within the Work body of a worker.
2828
// Middleware must be installed on either the worker or client for this function
2929
// to be usable.
30+
//
31+
// This variant panics if no logger was available in context.
3032
func Logger(ctx context.Context) *slog.Logger {
31-
logger, ok := ctx.Value(contextKey{}).(*slog.Logger)
33+
logger, ok := LoggerSafely(ctx)
3234
if !ok {
3335
panic("no logger in context; do you have riverlog.Middleware configured?")
3436
}
3537
return logger
3638
}
3739

40+
// LoggerSafely extracts a logger from context from within the Work body of a
41+
// worker. Middleware must be installed on either the worker or client for this
42+
// function to be usable.
43+
//
44+
// This variant returns a boolean that's true if a logger was available in
45+
// context and false otherwise.
46+
func LoggerSafely(ctx context.Context) (*slog.Logger, bool) {
47+
logger, ok := ctx.Value(contextKey{}).(*slog.Logger)
48+
return logger, ok
49+
}
50+
3851
// Middleware injects a context logger into the Work function of workers it's
3952
// installed on (or workers of the client it's installed on) which is accessible
4053
// with Logger, and which collates all log output to store it to metadata after
@@ -79,6 +92,12 @@ type MiddlewareConfig struct {
7992
// riverlog.NewMiddleware(func(w io.Writer) slog.Handler {
8093
// return slog.NewJSONHandler(w, nil)
8194
// }, nil)
95+
//
96+
// With the middleware in place, the logger is available in a work function's
97+
// context:
98+
//
99+
// func (w *MyWorker) Work(ctx context.Context, job *river.Job[MyArgs]) error {
100+
// Logger(ctx).InfoContext(ctx, "Hello from work")
82101
func NewMiddleware(newSlogHandler func(w io.Writer) slog.Handler, config *MiddlewareConfig) *Middleware {
83102
return &Middleware{
84103
config: defaultConfig(config),

riverlog/river_log_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,57 @@ import (
1616
"github.com/riverqueue/river/riverdbtest"
1717
"github.com/riverqueue/river/riverdriver"
1818
"github.com/riverqueue/river/riverdriver/riverpgxv5"
19+
"github.com/riverqueue/river/rivershared/riversharedtest"
1920
"github.com/riverqueue/river/rivershared/util/slogutil"
2021
"github.com/riverqueue/river/rivertest"
2122
"github.com/riverqueue/river/rivertype"
2223
)
2324

25+
func TestLogger(t *testing.T) {
26+
t.Parallel()
27+
28+
ctx := context.Background()
29+
30+
t.Run("Success", func(t *testing.T) {
31+
t.Parallel()
32+
33+
ctx := context.WithValue(ctx, contextKey{}, riversharedtest.Logger(t))
34+
35+
Logger(ctx).InfoContext(ctx, "Hello from logger")
36+
})
37+
38+
t.Run("PanicIfNotSet", func(t *testing.T) {
39+
t.Parallel()
40+
41+
require.PanicsWithValue(t, "no logger in context; do you have riverlog.Middleware configured?", func() {
42+
Logger(ctx).InfoContext(ctx, "This will panic")
43+
})
44+
})
45+
}
46+
47+
func TestLoggerSafely(t *testing.T) {
48+
t.Parallel()
49+
50+
ctx := context.Background()
51+
52+
t.Run("Success", func(t *testing.T) {
53+
t.Parallel()
54+
55+
ctx := context.WithValue(ctx, contextKey{}, riversharedtest.Logger(t))
56+
57+
logger, ok := LoggerSafely(ctx)
58+
require.True(t, ok)
59+
logger.InfoContext(ctx, "Hello from logger")
60+
})
61+
62+
t.Run("PanicIfNotSet", func(t *testing.T) {
63+
t.Parallel()
64+
65+
_, ok := LoggerSafely(ctx)
66+
require.False(t, ok)
67+
})
68+
}
69+
2470
var _ rivertype.WorkerMiddleware = &Middleware{}
2571

2672
type loggingArgs struct {

0 commit comments

Comments
 (0)