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>
diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 6e501b9..45dd61e 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -11,9 +11,10 @@
 1. [Loggers and their handlers](#loggers-and-their-handlers)
 1. [Implementing `Handler` methods](#implementing-`handler`-methods)
 	1. [The `Enabled` method](#the-`enabled`-method)
+	1. [The `Handle` method](#the-`handle`-method)
 	1. [The `WithAttrs` method](#the-`withattrs`-method)
 	1. [The `WithGroup` method](#the-`withgroup`-method)
-	1. [The `Handle` method](#the-`handle`-method)
+	1. [Testing](#testing)
 1. [General considerations](#general-considerations)
 	1. [Concurrency safety](#concurrency-safety)
 	1. [Robustness](#robustness)
@@ -149,7 +150,7 @@
 The mutex will be used to ensure that writes to the `io.Writer` happen atomically.
 Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a
 `sync.Mutex` directly.
-But there is a good reason for that, which we'll explain later.
+There is a good reason for that, which we'll explain later.
 
 TODO(jba): add link to that later explanation.
 
@@ -175,6 +176,101 @@
 
 TODO(jba): include Enabled example
 
+## The `Handle` method
+
+The `Handle` method is passed a `Record` containing all the information to be
+logged for a single call to a `Logger` output method.
+The `Handle` method should deal with it in some way.
+One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
+But other options are to modify the `Record` and pass it on to another handler,
+enqueue the `Record` for later processing, or ignore it.
+
+The signature of `Handle` is
+
+    Handle(context.Context, Record) error
+
+The context is provided to support applications that provide logging information
+along the call chain. In a break with usual Go practice, the `Handle` method
+should not treat a canceled context as a signal to stop work.
+
+If `Handle` processes its `Record`, it should follow the rules in the
+[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
+For example, a zero `Time` field should be ignored, as should zero `Attr`s.
+
+A `Handle` method that is going to produce output should carry out the following steps:
+
+1. Allocate a buffer, typically a `[]byte`, to hold the output.
+It's best to construct the output in memory first,
+then write it with a single call to `io.Writer.Write`,
+to minimize interleaving with other goroutines using the same writer.
+
+2. Format the special fields: time, level, message, and source location (PC).
+As a general rule, these fields should appear first and are not nested in
+groups established by `WithGroup`.
+
+3. Format the result of `WithGroup` and `WithAttrs` calls.
+
+4. Format the attributes in the `Record`.
+
+5. Output the buffer.
+
+That is how our `IndentHandler`'s `Handle` method is structured:
+
+```
+func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
+	buf := make([]byte, 0, 1024)
+	if !r.Time.IsZero() {
+		buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0)
+	}
+	buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0)
+	if r.PC != 0 {
+		fs := runtime.CallersFrames([]uintptr{r.PC})
+		f, _ := fs.Next()
+		buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0)
+	}
+	buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0)
+	indentLevel := 0
+	// TODO: output the Attrs and groups from WithAttrs and WithGroup.
+	r.Attrs(func(a slog.Attr) bool {
+		buf = h.appendAttr(buf, a, indentLevel)
+		return true
+	})
+	buf = append(buf, "---\n"...)
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	_, err := h.out.Write(buf)
+	return err
+}
+```
+
+The first line allocates a `[]byte` that should be large enough for most log
+output.
+Allocating a buffer with some initial, fairly large capacity is a simple but
+significant optimization: it avoids the repeated copying and allocation that
+happen when the initial slice is empty or small.
+We'll return to this line in the section on [speed](#speed)
+and show how we can do even better.
+
+The next part of our `Handle` method formats the special attributes,
+observing the rules to ignore a zero time and a zero PC.
+
+Next, the method processes the result of `WithAttrs` and `WithGroup` calls.
+We'll skip that for now.
+
+Then it's time to process the attributes in the argument record.
+We use the `Record.Attrs` method to iterate over the attributes
+in the order the user passed them to the `Logger` output method.
+Handlers are free to reorder or de-duplicate the attributes,
+but ours does not.
+
+Lastly, after adding the line "---" to the output to separate log records,
+our handler makes a single call to `h.out.Write` with the buffer we've accumulated.
+We hold the lock for this write to make it atomic with respect to other
+goroutines that may be calling `Handle` at the same time.
+
+TODO(jba): talk about appendAttr
+
+
 ## The `WithAttrs` method
 
 One of `slog`'s performance optimizations is support for pre-formatting
@@ -232,76 +328,14 @@
 
 TODO(jba): add IndentHandler examples
 
-## The `Handle` method
+## Testing
 
-The `Handle` method is passed a `Record` containing all the information to be
-logged for a single call to a `Logger` output method.
-The `Handle` method should deal with it in some way.
-One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
-But other options are to modify the `Record` and pass it on to another handler,
-enqueue the `Record` for later processing, or ignore it.
-
-The signature of `Handle` is
-
-    Handle(context.Context, Record) error
-
-The context is provided to support applications that provide logging information
-along the call chain. In a break with usual Go practice, the `Handle` method
-should not treat a canceled context as a signal to stop work.
-
-If `Handle` processes its `Record`, it should follow the rules in the
-[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
-For example, a zero `Time` field should be ignored, as should zero `Attr`s.
 To verify that your handler follows these rules and generally produces proper
 output, use the [testing/slogtest package](https://pkg.go.dev/log/slog).
 
-Before processing the attributes in the `Record`, remember to process the
-attributes from `WithAttrs` calls, qualified by the groups from `WithGroup`
-calls. Then use `Record.Attrs` to process the attributes in the `Record`, also
-qualified by the groups from `WithGroup` calls.
+TODO(jba): show the test function.
 
-The attributes provided to `Handler.WithAttrs` appear in the order the user passed them
-to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user
-passed them to the `Logger` output method. Handlers are free to reorder or
-de-duplicate the attributes, provided group qualification is preserved.
-
-Your handler can make a single copy of a `Record` with an ordinary Go
-assignment, channel send or function call if it doesn't retain the
-original.
-But if its actions result in more than one copy, it should call `Record.Clone`
-to make the copies so that they don't share state.
-This `Handle` method passes the record to a single handler, so it doesn't require `Clone`:
-
-    type Handler1 struct {
-        h slog.Handler
-        // ...
-    }
-
-    func (h *Handler1) Handle(ctx context.Context, r slog.Record) error {
-        return h.h.Handle(ctx, r)
-    }
-
-This `Handle` method might pass the record to more than one handler, so it
-should use `Clone`:
-
-    type Handler2 struct {
-        hs []slog.Handler
-        // ...
-    }
-
-    func (h *Handler2) Handle(ctx context.Context, r slog.Record) error {
-        for _, hh := range h.hs {
-            if err := hh.Handle(ctx, r.Clone()); err != nil {
-                return err
-            }
-        }
-        return nil
-    }
-
-
-
-TODO(jba): example use of slogtest
-
+TODO(jba): reintroduce the material on Record.Clone that used to be here.
 
 # General considerations
 
@@ -392,3 +426,5 @@
 ## Speed
 
 TODO(jba): discuss
+
+TODO(jba): show how to pool a []byte.
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index e780016..683e367 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -113,7 +113,7 @@
 The mutex will be used to ensure that writes to the `io.Writer` happen atomically.
 Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a
 `sync.Mutex` directly.
-But there is a good reason for that, which we'll explain later.
+There is a good reason for that, which we'll explain later.
 
 TODO(jba): add link to that later explanation.
 
@@ -139,6 +139,76 @@
 
 TODO(jba): include Enabled example
 
+## The `Handle` method
+
+The `Handle` method is passed a `Record` containing all the information to be
+logged for a single call to a `Logger` output method.
+The `Handle` method should deal with it in some way.
+One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
+But other options are to modify the `Record` and pass it on to another handler,
+enqueue the `Record` for later processing, or ignore it.
+
+The signature of `Handle` is
+
+    Handle(context.Context, Record) error
+
+The context is provided to support applications that provide logging information
+along the call chain. In a break with usual Go practice, the `Handle` method
+should not treat a canceled context as a signal to stop work.
+
+If `Handle` processes its `Record`, it should follow the rules in the
+[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
+For example, a zero `Time` field should be ignored, as should zero `Attr`s.
+
+A `Handle` method that is going to produce output should carry out the following steps:
+
+1. Allocate a buffer, typically a `[]byte`, to hold the output.
+It's best to construct the output in memory first,
+then write it with a single call to `io.Writer.Write`,
+to minimize interleaving with other goroutines using the same writer.
+
+2. Format the special fields: time, level, message, and source location (PC).
+As a general rule, these fields should appear first and are not nested in
+groups established by `WithGroup`.
+
+3. Format the result of `WithGroup` and `WithAttrs` calls.
+
+4. Format the attributes in the `Record`.
+
+5. Output the buffer.
+
+That is how our `IndentHandler`'s `Handle` method is structured:
+
+%include indenthandler1/indent_handler.go handle -
+
+The first line allocates a `[]byte` that should be large enough for most log
+output.
+Allocating a buffer with some initial, fairly large capacity is a simple but
+significant optimization: it avoids the repeated copying and allocation that
+happen when the initial slice is empty or small.
+We'll return to this line in the section on [speed](#speed)
+and show how we can do even better.
+
+The next part of our `Handle` method formats the special attributes,
+observing the rules to ignore a zero time and a zero PC.
+
+Next, the method processes the result of `WithAttrs` and `WithGroup` calls.
+We'll skip that for now.
+
+Then it's time to process the attributes in the argument record.
+We use the `Record.Attrs` method to iterate over the attributes
+in the order the user passed them to the `Logger` output method.
+Handlers are free to reorder or de-duplicate the attributes,
+but ours does not.
+
+Lastly, after adding the line "---" to the output to separate log records,
+our handler makes a single call to `h.out.Write` with the buffer we've accumulated.
+We hold the lock for this write to make it atomic with respect to other
+goroutines that may be calling `Handle` at the same time.
+
+TODO(jba): talk about appendAttr
+
+
 ## The `WithAttrs` method
 
 One of `slog`'s performance optimizations is support for pre-formatting
@@ -196,76 +266,14 @@
 
 TODO(jba): add IndentHandler examples
 
-## The `Handle` method
+## Testing
 
-The `Handle` method is passed a `Record` containing all the information to be
-logged for a single call to a `Logger` output method.
-The `Handle` method should deal with it in some way.
-One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do.
-But other options are to modify the `Record` and pass it on to another handler,
-enqueue the `Record` for later processing, or ignore it.
-
-The signature of `Handle` is
-
-    Handle(context.Context, Record) error
-
-The context is provided to support applications that provide logging information
-along the call chain. In a break with usual Go practice, the `Handle` method
-should not treat a canceled context as a signal to stop work.
-
-If `Handle` processes its `Record`, it should follow the rules in the
-[documentation](https://pkg.go.dev/log/slog#Handler.Handle).
-For example, a zero `Time` field should be ignored, as should zero `Attr`s.
 To verify that your handler follows these rules and generally produces proper
 output, use the [testing/slogtest package](https://pkg.go.dev/log/slog).
 
-Before processing the attributes in the `Record`, remember to process the
-attributes from `WithAttrs` calls, qualified by the groups from `WithGroup`
-calls. Then use `Record.Attrs` to process the attributes in the `Record`, also
-qualified by the groups from `WithGroup` calls.
+TODO(jba): show the test function.
 
-The attributes provided to `Handler.WithAttrs` appear in the order the user passed them
-to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user
-passed them to the `Logger` output method. Handlers are free to reorder or
-de-duplicate the attributes, provided group qualification is preserved.
-
-Your handler can make a single copy of a `Record` with an ordinary Go
-assignment, channel send or function call if it doesn't retain the
-original.
-But if its actions result in more than one copy, it should call `Record.Clone`
-to make the copies so that they don't share state.
-This `Handle` method passes the record to a single handler, so it doesn't require `Clone`:
-
-    type Handler1 struct {
-        h slog.Handler
-        // ...
-    }
-
-    func (h *Handler1) Handle(ctx context.Context, r slog.Record) error {
-        return h.h.Handle(ctx, r)
-    }
-
-This `Handle` method might pass the record to more than one handler, so it
-should use `Clone`:
-
-    type Handler2 struct {
-        hs []slog.Handler
-        // ...
-    }
-
-    func (h *Handler2) Handle(ctx context.Context, r slog.Record) error {
-        for _, hh := range h.hs {
-            if err := hh.Handle(ctx, r.Clone()); err != nil {
-                return err
-            }
-        }
-        return nil
-    }
-
-
-
-TODO(jba): example use of slogtest
-
+TODO(jba): reintroduce the material on Record.Clone that used to be here.
 
 # General considerations
 
@@ -356,3 +364,5 @@
 ## Speed
 
 TODO(jba): discuss
+
+TODO(jba): show how to pool a []byte.