@@ -325,13 +325,13 @@ Next, it follows the handler rule that says that empty attributes should be
325
325
ignored.
326
326
327
327
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
329
329
produce something reasonable. It handles strings and times specially:
330
330
strings by quoting them, and times by formatting them in a standard way.
331
331
332
332
When ` appendAttr ` sees a ` Group ` , it calls itself recursively on the group's
333
333
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.
335
335
Second, a group with an empty key is inlined: the group boundary isn't marked in
336
336
any way. In our case, that means the group's attributes aren't indented.
337
337
@@ -360,8 +360,7 @@ the original handler (its receiver) unchanged. For example, this call:
360
360
creates a new logger, ` logger2 ` , with an additional attribute, but has no
361
361
effect on ` logger1 ` .
362
362
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 ` .
365
364
366
365
## The ` WithGroup ` method
367
366
@@ -390,7 +389,128 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`.
390
389
We will look at two implementations of ` WithGroup ` and ` WithAttrs ` , one that pre-formats and
391
390
one that doesn't.
392
391
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
394
514
395
515
## Testing
396
516
@@ -465,7 +585,7 @@ to format a value will probably switch on the value's kind:
465
585
What should happen in the default case, when the handler encounters a ` Kind `
466
586
that it doesn't know about?
467
587
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 .
469
589
They do not panic or return an error.
470
590
Your own handlers might in addition want to report the problem through your production monitoring
471
591
or error-tracking telemetry system.
0 commit comments