Skip to content

Commit 360ff98

Browse files
committed
Update readme and docs, add examples
1 parent f6f3945 commit 360ff98

File tree

5 files changed

+337
-3
lines changed

5 files changed

+337
-3
lines changed

README.md

+181-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,182 @@
11
# slog-context
2-
Add keys and values to golang slog log lines. Have slog automatically read from context to add details.
2+
[![tag](https://img.shields.io/github/tag/veqryn/slog-context.svg)](https://github.com/veqryn/slog-context/releases)
3+
![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-%23007d9c)
4+
[![GoDoc](https://godoc.org/github.com/veqryn/slog-context?status.svg)](https://pkg.go.dev/github.com/veqryn/slog-context)
5+
![Build Status](https://github.com/veqryn/slog-context/actions/workflows/build_and_test.yml/badge.svg)
6+
[![Go report](https://goreportcard.com/badge/github.com/veqryn/slog-context)](https://goreportcard.com/report/github.com/veqryn/slog-context)
7+
[![Coverage](https://img.shields.io/codecov/c/github/veqryn/slog-context)](https://codecov.io/gh/veqryn/slog-context)
8+
[![Contributors](https://img.shields.io/github/contributors/veqryn/slog-context)](https://github.com/veqryn/slog-context/graphs/contributors)
9+
[![License](https://img.shields.io/github/license/veqryn/slog-context)](./LICENSE)
10+
11+
Use golang structured logging (slog) with context.
12+
Add attributes to context. Add and retrieve logger to and from context.
13+
14+
This library supports two different workflows for using slog and context.
15+
These workflows can be used separately or together.
16+
17+
Using the `slogcontext.Handler` lets us `Prepend` and `Append` attributes to
18+
log lines, even when a logger is not passed into a function or in code we don't
19+
control. This is done without storing the logger in the context; instead the
20+
attributes are stored in the context and the Handler picks them up later
21+
whenever a new log line is written.
22+
23+
Using `ToCtx` and `Logger` lets us store the logger itself within a context,
24+
and get it back out again. Wrapper methods `With`/`WithGroup`/`Debug`/`Info`/
25+
`Warn`/`Error`/`Log`/`LogAttrs` let us work directly with a logger residing
26+
with the context (or the default logger if no logger is stored in the context).
27+
28+
## Install
29+
`go get github.com/veqryn/slog-context`
30+
31+
```go
32+
import (
33+
slogcontext "github.com/veqryn/slog-context"
34+
)
35+
```
36+
37+
## Usage
38+
### Add attributes to context workflow
39+
```go
40+
package main
41+
42+
import (
43+
"context"
44+
"log/slog"
45+
"os"
46+
47+
slogcontext "github.com/veqryn/slog-context"
48+
)
49+
50+
// This workflow lets us use slog as normal, while adding the ability to put
51+
// slog attributes into the context which will then show up at the start or end
52+
// of log lines.
53+
//
54+
// This is useful when you are not passing a *slog.Logger around to different
55+
// functions (because you are making use of the default package-level slog),
56+
// but you are passing a context.Context around.
57+
//
58+
// This can also be used when a library or vendor code you don't control is
59+
// using the default log methods, default logger, or doesn't accept a slog
60+
// Logger to all functions you wish to add attributes to.
61+
//
62+
// Attributes and key-value pairs like request-id, trace-id, user-id, etc, can
63+
// be added to the context, and the *slogcontext.Handler will make sure they
64+
// are prepended to the start, or appended to the end, of any log lines using
65+
// that context.
66+
func main() {
67+
// Create the *slogcontext.Handler middleware
68+
h := slogcontext.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil)
69+
slog.SetDefault(slog.New(h))
70+
71+
ctx := context.Background()
72+
73+
// Prepend some slog attributes to the start of future log lines:
74+
ctx = slogcontext.Prepend(ctx, "prependKey", "prependValue")
75+
76+
// Append some slog attributes to the end of future log lines:
77+
// Prepend and Append have the same args signature as slog methods,
78+
// and can take a mix of slog.Attr and key-value pairs.
79+
ctx = slogcontext.Append(ctx, slog.String("appendKey", "appendValue"))
80+
81+
// Use the logger like normal:
82+
slog.WarnContext(ctx, "main message", "mainKey", "mainValue")
83+
/*
84+
{
85+
"time": "2023-11-15T18:43:23.290798-07:00",
86+
"level": "WARN",
87+
"msg": "main message",
88+
"prependKey": "prependValue",
89+
"mainKey": "mainValue",
90+
"appendKey": "appendValue"
91+
}
92+
*/
93+
94+
// Use the logger like normal; add attributes, create groups, pass it around:
95+
log := slog.With("rootKey", "rootValue")
96+
log = log.WithGroup("someGroup")
97+
log = log.With("subKey", "subValue")
98+
99+
// The prepended/appended attributes end up in all log lines that use that context
100+
log.InfoContext(ctx, "main message", "mainKey", "mainValue")
101+
/*
102+
{
103+
"time": "2023-11-14T00:37:03.805196-07:00",
104+
"level": "INFO",
105+
"msg": "main message",
106+
"prependKey": "prependValue",
107+
"rootKey": "rootValue",
108+
"someGroup": {
109+
"subKey": "subValue",
110+
"mainKey": "mainValue",
111+
"appendKey": "appendValue"
112+
}
113+
}
114+
*/
115+
}
116+
```
117+
118+
### Add logger to context workflow
119+
```go
120+
package main
121+
122+
import (
123+
"context"
124+
"log/slog"
125+
"os"
126+
127+
slogcontext "github.com/veqryn/slog-context"
128+
)
129+
130+
// This workflow has us pass the *slog.Logger around inside a context.Context.
131+
// This lets us add attributes and groups to the logger, while naturally
132+
// keeping the logger scoped just like the context itself is scoped.
133+
//
134+
// This eliminates the need to use the default package-level slog, and also
135+
// eliminates the need to add a *slog.Logger as yet another argument to all
136+
// functions.
137+
//
138+
// You can still get the Logger out of the context at any time, and pass it
139+
// around manually if needed, but since contexts are already passed to most
140+
// functions, passing the logger explicitly is now optional.
141+
//
142+
// Attributes and key-value pairs like request-id, trace-id, user-id, etc, can
143+
// be added to the logger in the context, and as the context propagates the
144+
// logger and its attributes will propagate with it, adding these to any log
145+
// lines using that context.
146+
func main() {
147+
h := slog.NewJSONHandler(os.Stdout, nil)
148+
slog.SetDefault(slog.New(h))
149+
150+
// Store the logger inside the context:
151+
ctx := slogcontext.ToCtx(context.Background(), slog.Default())
152+
153+
// Get the logger back out again at any time, for manual usage:
154+
log := slogcontext.Logger(ctx)
155+
log.Warn("warning")
156+
157+
// Add attributes directly to the logger in the context:
158+
ctx = slogcontext.With(ctx, "rootKey", "rootValue")
159+
160+
// Create a group directly on the logger in the context:
161+
ctx = slogcontext.WithGroup(ctx, "someGroup")
162+
163+
// With and wrapper methods have the same args signature as slog methods,
164+
// and can take a mix of slog.Attr and key-value pairs.
165+
ctx = slogcontext.With(ctx, slog.String("subKey", "subValue"))
166+
167+
// Access the logger in the context directly with handy wrappers for Debug/Info/Warn/Error/Log/LogAttrs:
168+
slogcontext.Info(ctx, "main message", "mainKey", "mainValue")
169+
/*
170+
{
171+
"time":"2023-11-14T00:53:46.363072-07:00",
172+
"level":"INFO",
173+
"msg":"main message",
174+
"rootKey":"rootValue",
175+
"someGroup":{
176+
"subKey":"subValue",
177+
"mainKey":"mainValue"
178+
}
179+
}
180+
*/
181+
}
182+
```

ctx.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ func Logger(ctx context.Context) *slog.Logger {
2626
if ctx == nil {
2727
return slog.Default()
2828
}
29-
if log, ok := ctx.Value(ctxKey{}).(*slog.Logger); ok && log != nil {
30-
return log
29+
if l, ok := ctx.Value(ctxKey{}).(*slog.Logger); ok && l != nil {
30+
return l
3131
}
3232
return slog.Default()
3333
}

doc.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
Package slogcontext lets you use golang structured logging (slog) with context.
3+
Add attributes to context. Add and retrieve logger to and from context.
4+
5+
This library supports two different workflows for using slog and context.
6+
These workflows can be used separately or together.
7+
8+
Using the Handler lets us Prepend and Append attributes to
9+
log lines, even when a logger is not passed into a function or in code we don't
10+
control. This is done without storing the logger in the context; instead the
11+
attributes are stored in the context and the Handler picks them up later
12+
whenever a new log line is written.
13+
14+
Using ToCtx and Logger lets us store the logger itself within a context,
15+
and get it back out again. Wrapper methods With / WithGroup / Debug / Info /
16+
Warn / Error / Log / LogAttrs let us work directly with a logger residing
17+
with the context (or the default logger if no logger is stored in the context).
18+
*/
19+
package slogcontext

examples/attributes-in-ctx-workflow/attributes-in-ctx-workflow-main.go

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import (
1616
// functions (because you are making use of the default package-level slog),
1717
// but you are passing a context.Context around.
1818
//
19+
// This can also be used when a library or vendor code you don't control is
20+
// using the default log methods, default logger, or doesn't accept a slog
21+
// Logger to all functions you wish to add attributes to.
22+
//
1923
// Attributes and key-value pairs like request-id, trace-id, user-id, etc, can
2024
// be added to the context, and the *slogcontext.Handler will make sure they
2125
// are prepended to the start, or appended to the end, of any log lines using

examples_test.go

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package slogcontext_test
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"os"
7+
8+
slogcontext "github.com/veqryn/slog-context"
9+
)
10+
11+
func ExampleNewHandler() {
12+
// This workflow lets us use slog as normal, while adding the ability to put
13+
// slog attributes into the context which will then show up at the start or end
14+
// of log lines.
15+
//
16+
// This is useful when you are not passing a *slog.Logger around to different
17+
// functions (because you are making use of the default package-level slog),
18+
// but you are passing a context.Context around.
19+
//
20+
// This can also be used when a library or vendor code you don't control is
21+
// using the default log methods, default logger, or doesn't accept a slog
22+
// Logger to all functions you wish to add attributes to.
23+
//
24+
// Attributes and key-value pairs like request-id, trace-id, user-id, etc, can
25+
// be added to the context, and the *slogcontext.Handler will make sure they
26+
// are prepended to the start, or appended to the end, of any log lines using
27+
// that context.
28+
29+
// Create the *slogcontext.Handler middleware
30+
h := slogcontext.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil)
31+
slog.SetDefault(slog.New(h))
32+
33+
ctx := context.Background()
34+
35+
// Prepend some slog attributes to the start of future log lines:
36+
ctx = slogcontext.Prepend(ctx, "prependKey", "prependValue")
37+
38+
// Append some slog attributes to the end of future log lines:
39+
// Prepend and Append have the same args signature as slog methods,
40+
// and can take a mix of slog.Attr and key-value pairs.
41+
ctx = slogcontext.Append(ctx, slog.String("appendKey", "appendValue"))
42+
43+
// Use the logger like normal:
44+
slog.WarnContext(ctx, "main message", "mainKey", "mainValue")
45+
/*
46+
{
47+
"time": "2023-11-15T18:43:23.290798-07:00",
48+
"level": "WARN",
49+
"msg": "main message",
50+
"prependKey": "prependValue",
51+
"mainKey": "mainValue",
52+
"appendKey": "appendValue"
53+
}
54+
*/
55+
56+
// Use the logger like normal; add attributes, create groups, pass it around:
57+
log := slog.With("rootKey", "rootValue")
58+
log = log.WithGroup("someGroup")
59+
log = log.With("subKey", "subValue8")
60+
61+
// The prepended/appended attributes end up in all log lines that use that context
62+
log.InfoContext(ctx, "main message", "mainKey", "mainValue")
63+
/*
64+
{
65+
"time": "2023-11-14T00:37:03.805196-07:00",
66+
"level": "INFO",
67+
"msg": "main message",
68+
"prependKey": "prependValue",
69+
"rootKey": "rootValue",
70+
"someGroup": {
71+
"subKey": "subValue",
72+
"mainKey": "mainValue",
73+
"appendKey": "appendValue"
74+
}
75+
}
76+
*/
77+
}
78+
79+
func ExampleToCtx() {
80+
// This workflow has us pass the *slog.Logger around inside a context.Context.
81+
// This lets us add attributes and groups to the logger, while naturally
82+
// keeping the logger scoped just like the context itself is scoped.
83+
//
84+
// This eliminates the need to use the default package-level slog, and also
85+
// eliminates the need to add a *slog.Logger as yet another argument to all
86+
// functions.
87+
//
88+
// You can still get the Logger out of the context at any time, and pass it
89+
// around manually if needed, but since contexts are already passed to most
90+
// functions, passing the logger explicitly is now optional.
91+
//
92+
// Attributes and key-value pairs like request-id, trace-id, user-id, etc, can
93+
// be added to the logger in the context, and as the context propagates the
94+
// logger and its attributes will propagate with it, adding these to any log
95+
// lines using that context.
96+
97+
h := slog.NewJSONHandler(os.Stdout, nil)
98+
slog.SetDefault(slog.New(h))
99+
100+
// Store the logger inside the context:
101+
ctx := slogcontext.ToCtx(context.Background(), slog.Default())
102+
103+
// Get the logger back out again at any time, for manual usage:
104+
log := slogcontext.Logger(ctx)
105+
log.Warn("warning")
106+
107+
// Add attributes directly to the logger in the context:
108+
ctx = slogcontext.With(ctx, "rootKey", "rootValue")
109+
110+
// Create a group directly on the logger in the context:
111+
ctx = slogcontext.WithGroup(ctx, "someGroup")
112+
113+
// With and wrapper methods have the same args signature as slog methods,
114+
// and can take a mix of slog.Attr and key-value pairs.
115+
ctx = slogcontext.With(ctx, slog.String("subKey", "subValue"))
116+
117+
// Access the logger in the context directly with handy wrappers for Debug/Info/Warn/Error/Log/LogAttrs:
118+
slogcontext.Info(ctx, "main message", "mainKey", "mainValue")
119+
/*
120+
{
121+
"time":"2023-11-14T00:53:46.363072-07:00",
122+
"level":"INFO",
123+
"msg":"main message",
124+
"rootKey":"rootValue",
125+
"someGroup":{
126+
"subKey":"subValue",
127+
"mainKey":"mainValue"
128+
}
129+
}
130+
*/
131+
}

0 commit comments

Comments
 (0)