Skip to content

Commit 29ffb74

Browse files
committed
slog-handler-guide: handler example: Handle method
Pull the discussion of the Handle method up. It makes more sense to talk about it before WithGroup and WithAttrs. Change-Id: I01a1b4b98131079cf74d2d965742a48acf5c3407 Reviewed-on: https://go-review.googlesource.com/c/example/+/509957 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com> Run-TryBot: Jonathan Amsterdam <jba@google.com>
1 parent 7137c6b commit 29ffb74

File tree

2 files changed

+179
-133
lines changed

2 files changed

+179
-133
lines changed

slog-handler-guide/README.md

Lines changed: 103 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ This document is maintained by Jonathan Amsterdam `jba@google.com`.
1111
1. [Loggers and their handlers](#loggers-and-their-handlers)
1212
1. [Implementing `Handler` methods](#implementing-`handler`-methods)
1313
1. [The `Enabled` method](#the-`enabled`-method)
14+
1. [The `Handle` method](#the-`handle`-method)
1415
1. [The `WithAttrs` method](#the-`withattrs`-method)
1516
1. [The `WithGroup` method](#the-`withgroup`-method)
16-
1. [The `Handle` method](#the-`handle`-method)
17+
1. [Testing](#testing)
1718
1. [General considerations](#general-considerations)
1819
1. [Concurrency safety](#concurrency-safety)
1920
1. [Robustness](#robustness)
@@ -149,7 +150,7 @@ Changes to `LevelVar`s are goroutine-safe.
149150
The mutex will be used to ensure that writes to the `io.Writer` happen atomically.
150151
Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a
151152
`sync.Mutex` directly.
152-
But there is a good reason for that, which we'll explain later.
153+
There is a good reason for that, which we'll explain later.
153154

154155
TODO(jba): add link to that later explanation.
155156

@@ -175,6 +176,101 @@ of the work done by each request to be controlled independently.
175176

176177
TODO(jba): include Enabled example
177178

179+
## The `Handle` method
180+
181+
The `Handle` method is passed a `Record` containing all the information to be
182+
logged for a single call to a `Logger` output method.
183+
The `Handle` method should deal with it in some way.
184+
One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
185+
But other options are to modify the `Record` and pass it on to another handler,
186+
enqueue the `Record` for later processing, or ignore it.
187+
188+
The signature of `Handle` is
189+
190+
Handle(context.Context, Record) error
191+
192+
The context is provided to support applications that provide logging information
193+
along the call chain. In a break with usual Go practice, the `Handle` method
194+
should not treat a canceled context as a signal to stop work.
195+
196+
If `Handle` processes its `Record`, it should follow the rules in the
197+
[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
198+
For example, a zero `Time` field should be ignored, as should zero `Attr`s.
199+
200+
A `Handle` method that is going to produce output should carry out the following steps:
201+
202+
1. Allocate a buffer, typically a `[]byte`, to hold the output.
203+
It's best to construct the output in memory first,
204+
then write it with a single call to `io.Writer.Write`,
205+
to minimize interleaving with other goroutines using the same writer.
206+
207+
2. Format the special fields: time, level, message, and source location (PC).
208+
As a general rule, these fields should appear first and are not nested in
209+
groups established by `WithGroup`.
210+
211+
3. Format the result of `WithGroup` and `WithAttrs` calls.
212+
213+
4. Format the attributes in the `Record`.
214+
215+
5. Output the buffer.
216+
217+
That is how our `IndentHandler`'s `Handle` method is structured:
218+
219+
```
220+
func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
221+
buf := make([]byte, 0, 1024)
222+
if !r.Time.IsZero() {
223+
buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0)
224+
}
225+
buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0)
226+
if r.PC != 0 {
227+
fs := runtime.CallersFrames([]uintptr{r.PC})
228+
f, _ := fs.Next()
229+
buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0)
230+
}
231+
buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0)
232+
indentLevel := 0
233+
// TODO: output the Attrs and groups from WithAttrs and WithGroup.
234+
r.Attrs(func(a slog.Attr) bool {
235+
buf = h.appendAttr(buf, a, indentLevel)
236+
return true
237+
})
238+
buf = append(buf, "---\n"...)
239+
h.mu.Lock()
240+
defer h.mu.Unlock()
241+
_, err := h.out.Write(buf)
242+
return err
243+
}
244+
```
245+
246+
The first line allocates a `[]byte` that should be large enough for most log
247+
output.
248+
Allocating a buffer with some initial, fairly large capacity is a simple but
249+
significant optimization: it avoids the repeated copying and allocation that
250+
happen when the initial slice is empty or small.
251+
We'll return to this line in the section on [speed](#speed)
252+
and show how we can do even better.
253+
254+
The next part of our `Handle` method formats the special attributes,
255+
observing the rules to ignore a zero time and a zero PC.
256+
257+
Next, the method processes the result of `WithAttrs` and `WithGroup` calls.
258+
We'll skip that for now.
259+
260+
Then it's time to process the attributes in the argument record.
261+
We use the `Record.Attrs` method to iterate over the attributes
262+
in the order the user passed them to the `Logger` output method.
263+
Handlers are free to reorder or de-duplicate the attributes,
264+
but ours does not.
265+
266+
Lastly, after adding the line "---" to the output to separate log records,
267+
our handler makes a single call to `h.out.Write` with the buffer we've accumulated.
268+
We hold the lock for this write to make it atomic with respect to other
269+
goroutines that may be calling `Handle` at the same time.
270+
271+
TODO(jba): talk about appendAttr
272+
273+
178274
## The `WithAttrs` method
179275

180276
One of `slog`'s performance optimizations is support for pre-formatting
@@ -232,76 +328,14 @@ one that doesn't.
232328

233329
TODO(jba): add IndentHandler examples
234330

235-
## The `Handle` method
331+
## Testing
236332

237-
The `Handle` method is passed a `Record` containing all the information to be
238-
logged for a single call to a `Logger` output method.
239-
The `Handle` method should deal with it in some way.
240-
One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
241-
But other options are to modify the `Record` and pass it on to another handler,
242-
enqueue the `Record` for later processing, or ignore it.
243-
244-
The signature of `Handle` is
245-
246-
Handle(context.Context, Record) error
247-
248-
The context is provided to support applications that provide logging information
249-
along the call chain. In a break with usual Go practice, the `Handle` method
250-
should not treat a canceled context as a signal to stop work.
251-
252-
If `Handle` processes its `Record`, it should follow the rules in the
253-
[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
254-
For example, a zero `Time` field should be ignored, as should zero `Attr`s.
255333
To verify that your handler follows these rules and generally produces proper
256334
output, use the [testing/slogtest package](https://pkg.go.dev/log/slog).
257335

258-
Before processing the attributes in the `Record`, remember to process the
259-
attributes from `WithAttrs` calls, qualified by the groups from `WithGroup`
260-
calls. Then use `Record.Attrs` to process the attributes in the `Record`, also
261-
qualified by the groups from `WithGroup` calls.
262-
263-
The attributes provided to `Handler.WithAttrs` appear in the order the user passed them
264-
to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user
265-
passed them to the `Logger` output method. Handlers are free to reorder or
266-
de-duplicate the attributes, provided group qualification is preserved.
267-
268-
Your handler can make a single copy of a `Record` with an ordinary Go
269-
assignment, channel send or function call if it doesn't retain the
270-
original.
271-
But if its actions result in more than one copy, it should call `Record.Clone`
272-
to make the copies so that they don't share state.
273-
This `Handle` method passes the record to a single handler, so it doesn't require `Clone`:
274-
275-
type Handler1 struct {
276-
h slog.Handler
277-
// ...
278-
}
279-
280-
func (h *Handler1) Handle(ctx context.Context, r slog.Record) error {
281-
return h.h.Handle(ctx, r)
282-
}
283-
284-
This `Handle` method might pass the record to more than one handler, so it
285-
should use `Clone`:
286-
287-
type Handler2 struct {
288-
hs []slog.Handler
289-
// ...
290-
}
291-
292-
func (h *Handler2) Handle(ctx context.Context, r slog.Record) error {
293-
for _, hh := range h.hs {
294-
if err := hh.Handle(ctx, r.Clone()); err != nil {
295-
return err
296-
}
297-
}
298-
return nil
299-
}
300-
301-
302-
303-
TODO(jba): example use of slogtest
336+
TODO(jba): show the test function.
304337

338+
TODO(jba): reintroduce the material on Record.Clone that used to be here.
305339

306340
# General considerations
307341

@@ -392,3 +426,5 @@ error.
392426
## Speed
393427

394428
TODO(jba): discuss
429+
430+
TODO(jba): show how to pool a []byte.

slog-handler-guide/guide.md

Lines changed: 76 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Changes to `LevelVar`s are goroutine-safe.
113113
The mutex will be used to ensure that writes to the `io.Writer` happen atomically.
114114
Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a
115115
`sync.Mutex` directly.
116-
But there is a good reason for that, which we'll explain later.
116+
There is a good reason for that, which we'll explain later.
117117

118118
TODO(jba): add link to that later explanation.
119119

@@ -139,6 +139,76 @@ of the work done by each request to be controlled independently.
139139

140140
TODO(jba): include Enabled example
141141

142+
## The `Handle` method
143+
144+
The `Handle` method is passed a `Record` containing all the information to be
145+
logged for a single call to a `Logger` output method.
146+
The `Handle` method should deal with it in some way.
147+
One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
148+
But other options are to modify the `Record` and pass it on to another handler,
149+
enqueue the `Record` for later processing, or ignore it.
150+
151+
The signature of `Handle` is
152+
153+
Handle(context.Context, Record) error
154+
155+
The context is provided to support applications that provide logging information
156+
along the call chain. In a break with usual Go practice, the `Handle` method
157+
should not treat a canceled context as a signal to stop work.
158+
159+
If `Handle` processes its `Record`, it should follow the rules in the
160+
[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
161+
For example, a zero `Time` field should be ignored, as should zero `Attr`s.
162+
163+
A `Handle` method that is going to produce output should carry out the following steps:
164+
165+
1. Allocate a buffer, typically a `[]byte`, to hold the output.
166+
It's best to construct the output in memory first,
167+
then write it with a single call to `io.Writer.Write`,
168+
to minimize interleaving with other goroutines using the same writer.
169+
170+
2. Format the special fields: time, level, message, and source location (PC).
171+
As a general rule, these fields should appear first and are not nested in
172+
groups established by `WithGroup`.
173+
174+
3. Format the result of `WithGroup` and `WithAttrs` calls.
175+
176+
4. Format the attributes in the `Record`.
177+
178+
5. Output the buffer.
179+
180+
That is how our `IndentHandler`'s `Handle` method is structured:
181+
182+
%include indenthandler1/indent_handler.go handle -
183+
184+
The first line allocates a `[]byte` that should be large enough for most log
185+
output.
186+
Allocating a buffer with some initial, fairly large capacity is a simple but
187+
significant optimization: it avoids the repeated copying and allocation that
188+
happen when the initial slice is empty or small.
189+
We'll return to this line in the section on [speed](#speed)
190+
and show how we can do even better.
191+
192+
The next part of our `Handle` method formats the special attributes,
193+
observing the rules to ignore a zero time and a zero PC.
194+
195+
Next, the method processes the result of `WithAttrs` and `WithGroup` calls.
196+
We'll skip that for now.
197+
198+
Then it's time to process the attributes in the argument record.
199+
We use the `Record.Attrs` method to iterate over the attributes
200+
in the order the user passed them to the `Logger` output method.
201+
Handlers are free to reorder or de-duplicate the attributes,
202+
but ours does not.
203+
204+
Lastly, after adding the line "---" to the output to separate log records,
205+
our handler makes a single call to `h.out.Write` with the buffer we've accumulated.
206+
We hold the lock for this write to make it atomic with respect to other
207+
goroutines that may be calling `Handle` at the same time.
208+
209+
TODO(jba): talk about appendAttr
210+
211+
142212
## The `WithAttrs` method
143213

144214
One of `slog`'s performance optimizations is support for pre-formatting
@@ -196,76 +266,14 @@ one that doesn't.
196266

197267
TODO(jba): add IndentHandler examples
198268

199-
## The `Handle` method
200-
201-
The `Handle` method is passed a `Record` containing all the information to be
202-
logged for a single call to a `Logger` output method.
203-
The `Handle` method should deal with it in some way.
204-
One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
205-
But other options are to modify the `Record` and pass it on to another handler,
206-
enqueue the `Record` for later processing, or ignore it.
207-
208-
The signature of `Handle` is
209-
210-
Handle(context.Context, Record) error
211-
212-
The context is provided to support applications that provide logging information
213-
along the call chain. In a break with usual Go practice, the `Handle` method
214-
should not treat a canceled context as a signal to stop work.
269+
## Testing
215270

216-
If `Handle` processes its `Record`, it should follow the rules in the
217-
[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
218-
For example, a zero `Time` field should be ignored, as should zero `Attr`s.
219271
To verify that your handler follows these rules and generally produces proper
220272
output, use the [testing/slogtest package](https://pkg.go.dev/log/slog).
221273

222-
Before processing the attributes in the `Record`, remember to process the
223-
attributes from `WithAttrs` calls, qualified by the groups from `WithGroup`
224-
calls. Then use `Record.Attrs` to process the attributes in the `Record`, also
225-
qualified by the groups from `WithGroup` calls.
226-
227-
The attributes provided to `Handler.WithAttrs` appear in the order the user passed them
228-
to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user
229-
passed them to the `Logger` output method. Handlers are free to reorder or
230-
de-duplicate the attributes, provided group qualification is preserved.
231-
232-
Your handler can make a single copy of a `Record` with an ordinary Go
233-
assignment, channel send or function call if it doesn't retain the
234-
original.
235-
But if its actions result in more than one copy, it should call `Record.Clone`
236-
to make the copies so that they don't share state.
237-
This `Handle` method passes the record to a single handler, so it doesn't require `Clone`:
238-
239-
type Handler1 struct {
240-
h slog.Handler
241-
// ...
242-
}
243-
244-
func (h *Handler1) Handle(ctx context.Context, r slog.Record) error {
245-
return h.h.Handle(ctx, r)
246-
}
247-
248-
This `Handle` method might pass the record to more than one handler, so it
249-
should use `Clone`:
250-
251-
type Handler2 struct {
252-
hs []slog.Handler
253-
// ...
254-
}
255-
256-
func (h *Handler2) Handle(ctx context.Context, r slog.Record) error {
257-
for _, hh := range h.hs {
258-
if err := hh.Handle(ctx, r.Clone()); err != nil {
259-
return err
260-
}
261-
}
262-
return nil
263-
}
264-
265-
266-
267-
TODO(jba): example use of slogtest
274+
TODO(jba): show the test function.
268275

276+
TODO(jba): reintroduce the material on Record.Clone that used to be here.
269277

270278
# General considerations
271279

@@ -356,3 +364,5 @@ error.
356364
## Speed
357365

358366
TODO(jba): discuss
367+
368+
TODO(jba): show how to pool a []byte.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy