Skip to content

Commit 183ca08

Browse files
committed
slog-handler-guide: WithXXX methods without pre-formatting
Change-Id: Ia95d8ddd50eb72bef057768d88b216d7b9809c58 Reviewed-on: https://go-review.googlesource.com/c/example/+/511576 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Ian Cottrell <[email protected]>
1 parent f15fe1c commit 183ca08

File tree

4 files changed

+495
-11
lines changed

4 files changed

+495
-11
lines changed

slog-handler-guide/README.md

+126-6
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,13 @@ Next, it follows the handler rule that says that empty attributes should be
325325
ignored.
326326

327327
Then it switches on the attribute kind to determine what format to use. For most
328-
(the default case of the switch), it relies on `slog.Value`'s `String` method to
328+
kinds (the default case of the switch), it relies on `slog.Value`'s `String` method to
329329
produce something reasonable. It handles strings and times specially:
330330
strings by quoting them, and times by formatting them in a standard way.
331331

332332
When `appendAttr` sees a `Group`, it calls itself recursively on the group's
333333
attributes, after applying two more handler rules.
334-
First, a group with no attributes is ignored&emdash;not even its key is displayed.
334+
First, a group with no attributes is ignored&mdash;not even its key is displayed.
335335
Second, a group with an empty key is inlined: the group boundary isn't marked in
336336
any way. In our case, that means the group's attributes aren't indented.
337337

@@ -360,8 +360,7 @@ the original handler (its receiver) unchanged. For example, this call:
360360
creates a new logger, `logger2`, with an additional attribute, but has no
361361
effect on `logger1`.
362362

363-
364-
We will show an example implementation of `WithAttrs` below, when we discuss `WithGroup`.
363+
We will show example implementations of `WithAttrs` below, when we discuss `WithGroup`.
365364

366365
## The `WithGroup` method
367366

@@ -390,7 +389,128 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`.
390389
We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and
391390
one that doesn't.
392391

393-
TODO(jba): add IndentHandler examples
392+
### Without pre-formatting
393+
394+
Our first implementation will collect the information from `WithGroup` and
395+
`WithAttrs` calls to build up a slice of group names and attribute lists,
396+
and loop over that slice in `Handle`. We start with a struct that can hold
397+
either a group name or some attributes:
398+
399+
```
400+
// groupOrAttrs holds either a group name or a list of slog.Attrs.
401+
type groupOrAttrs struct {
402+
group string // group name if non-empty
403+
attrs []slog.Attr // attrs if non-empty
404+
}
405+
```
406+
407+
Then we add a slice of `groupOrAttrs` to our handler:
408+
409+
```
410+
type IndentHandler struct {
411+
opts Options
412+
goas []groupOrAttrs
413+
mu *sync.Mutex
414+
out io.Writer
415+
}
416+
```
417+
418+
As stated above, The `WithGroup` and `WithAttrs` methods should not modify their
419+
receiver.
420+
To that end, we define a method that will copy our handler struct
421+
and append one `groupOrAttrs` to the copy:
422+
423+
```
424+
func (h *IndentHandler) withGroupOrAttrs(goa groupOrAttrs) *IndentHandler {
425+
h2 := *h
426+
h2.goas = make([]groupOrAttrs, len(h.goas)+1)
427+
copy(h2.goas, h.goas)
428+
h2.goas[len(h2.goas)-1] = goa
429+
return &h2
430+
}
431+
```
432+
433+
Most of the fields of `IndentHandler` can be copied shallowly, but the slice of
434+
`groupOrAttrs` requires a deep copy, or the clone and the original will point to
435+
the same underlying array. If we used `append` instead of making an explicit
436+
copy, we would introduce that subtle aliasing bug.
437+
438+
Using `withGroupOrAttrs`, the `With` methods are easy:
439+
440+
```
441+
func (h *IndentHandler) WithGroup(name string) slog.Handler {
442+
if name == "" {
443+
return h
444+
}
445+
return h.withGroupOrAttrs(groupOrAttrs{group: name})
446+
}
447+
448+
func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
449+
if len(attrs) == 0 {
450+
return h
451+
}
452+
return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs})
453+
}
454+
```
455+
456+
The `Handle` method can now process the groupOrAttrs slice after
457+
the built-in attributes and before the ones in the record:
458+
459+
```
460+
func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
461+
buf := make([]byte, 0, 1024)
462+
if !r.Time.IsZero() {
463+
buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0)
464+
}
465+
buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0)
466+
if r.PC != 0 {
467+
fs := runtime.CallersFrames([]uintptr{r.PC})
468+
f, _ := fs.Next()
469+
buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0)
470+
}
471+
buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0)
472+
indentLevel := 0
473+
// Handle state from WithGroup and WithAttrs.
474+
goas := h.goas
475+
if r.NumAttrs() == 0 {
476+
// If the record has no Attrs, remove groups at the end of the list; they are empty.
477+
for len(goas) > 0 && goas[len(goas)-1].group != "" {
478+
goas = goas[:len(goas)-1]
479+
}
480+
}
481+
for _, goa := range goas {
482+
if goa.group != "" {
483+
buf = fmt.Appendf(buf, "%*s%s:\n", indentLevel*4, "", goa.group)
484+
indentLevel++
485+
} else {
486+
for _, a := range goa.attrs {
487+
buf = h.appendAttr(buf, a, indentLevel)
488+
}
489+
}
490+
}
491+
r.Attrs(func(a slog.Attr) bool {
492+
buf = h.appendAttr(buf, a, indentLevel)
493+
return true
494+
})
495+
buf = append(buf, "---\n"...)
496+
h.mu.Lock()
497+
defer h.mu.Unlock()
498+
_, err := h.out.Write(buf)
499+
return err
500+
}
501+
```
502+
503+
You may have noticed that our algorithm for
504+
recording `WithGroup` and `WithAttrs` information is quadratic in the
505+
number of calls to those methods, because of the repeated copying.
506+
That is unlikely to matter in practice, but if it bothers you,
507+
you can use a linked list instead,
508+
which `Handle` will have to reverse or visit recursively.
509+
See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation.
510+
511+
### With pre-formatting
512+
513+
TODO(jba): write
394514

395515
## Testing
396516

@@ -465,7 +585,7 @@ to format a value will probably switch on the value's kind:
465585
What should happen in the default case, when the handler encounters a `Kind`
466586
that it doesn't know about?
467587
The built-in handlers try to muddle through by using the result of the value's
468-
`String` method.
588+
`String` method, as our example handler does.
469589
They do not panic or return an error.
470590
Your own handlers might in addition want to report the problem through your production monitoring
471591
or error-tracking telemetry system.

slog-handler-guide/guide.md

+48-5
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ strings by quoting them, and times by formatting them in a standard way.
228228

229229
When `appendAttr` sees a `Group`, it calls itself recursively on the group's
230230
attributes, after applying two more handler rules.
231-
First, a group with no attributes is ignored&emdash;not even its key is displayed.
231+
First, a group with no attributes is ignored&mdash;not even its key is displayed.
232232
Second, a group with an empty key is inlined: the group boundary isn't marked in
233233
any way. In our case, that means the group's attributes aren't indented.
234234

@@ -257,8 +257,7 @@ the original handler (its receiver) unchanged. For example, this call:
257257
creates a new logger, `logger2`, with an additional attribute, but has no
258258
effect on `logger1`.
259259

260-
261-
We will show an example implementation of `WithAttrs` below, when we discuss `WithGroup`.
260+
We will show example implementations of `WithAttrs` below, when we discuss `WithGroup`.
262261

263262
## The `WithGroup` method
264263

@@ -287,7 +286,51 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`.
287286
We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and
288287
one that doesn't.
289288

290-
TODO(jba): add IndentHandler examples
289+
### Without pre-formatting
290+
291+
Our first implementation will collect the information from `WithGroup` and
292+
`WithAttrs` calls to build up a slice of group names and attribute lists,
293+
and loop over that slice in `Handle`. We start with a struct that can hold
294+
either a group name or some attributes:
295+
296+
%include indenthandler2/indent_handler.go gora -
297+
298+
Then we add a slice of `groupOrAttrs` to our handler:
299+
300+
%include indenthandler2/indent_handler.go IndentHandler -
301+
302+
As stated above, The `WithGroup` and `WithAttrs` methods should not modify their
303+
receiver.
304+
To that end, we define a method that will copy our handler struct
305+
and append one `groupOrAttrs` to the copy:
306+
307+
%include indenthandler2/indent_handler.go withgora -
308+
309+
Most of the fields of `IndentHandler` can be copied shallowly, but the slice of
310+
`groupOrAttrs` requires a deep copy, or the clone and the original will point to
311+
the same underlying array. If we used `append` instead of making an explicit
312+
copy, we would introduce that subtle aliasing bug.
313+
314+
Using `withGroupOrAttrs`, the `With` methods are easy:
315+
316+
%include indenthandler2/indent_handler.go withs -
317+
318+
The `Handle` method can now process the groupOrAttrs slice after
319+
the built-in attributes and before the ones in the record:
320+
321+
%include indenthandler2/indent_handler.go handle -
322+
323+
You may have noticed that our algorithm for
324+
recording `WithGroup` and `WithAttrs` information is quadratic in the
325+
number of calls to those methods, because of the repeated copying.
326+
That is unlikely to matter in practice, but if it bothers you,
327+
you can use a linked list instead,
328+
which `Handle` will have to reverse or visit recursively.
329+
See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation.
330+
331+
### With pre-formatting
332+
333+
TODO(jba): write
291334

292335
## Testing
293336

@@ -362,7 +405,7 @@ to format a value will probably switch on the value's kind:
362405
What should happen in the default case, when the handler encounters a `Kind`
363406
that it doesn't know about?
364407
The built-in handlers try to muddle through by using the result of the value's
365-
`String` method.
408+
`String` method, as our example handler does.
366409
They do not panic or return an error.
367410
Your own handlers might in addition want to report the problem through your production monitoring
368411
or error-tracking telemetry system.

0 commit comments

Comments
 (0)