slog: doc Record methods and performance

Change-Id: I260ac78047edad37bbedfe4dad0a2f02359df1ac
Reviewed-on: https://go-review.googlesource.com/c/exp/+/458207
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/slog/doc.go b/slog/doc.go
index 1e019e8..6a052dd 100644
--- a/slog/doc.go
+++ b/slog/doc.go
@@ -263,18 +263,72 @@
 The proper definition of Infof is
 
     func Infof(format string, args ...any) {
-        slog.Default().LogDepth(0, slog.LevelInfo, fmt.Sprintf(format, args...))
+        slog.Default().LogDepth(1, slog.LevelInfo, fmt.Sprintf(format, args...))
     }
 
 
-## Interoperating with other logging packages
+## Working with Records
 
-TODO: discuss NewRecord, Record.AddAttrs
+Sometimes a Handler will need to modify a Record
+before passing it on to another Handler or backend.
+A Record contains a mixture of simple public fields (e.g. Time, Level, Message)
+and hidden fields that refer to state (such as attributes) indirectly. This
+means that modifying a simple copy of a Record (e.g. by calling [Record.AddAttrs] to add attributes)
+may have unexpected effects on the original.
+Before modifying a Record, use [Clone] to
+create a copy that shares no state with the original,
+or create a new Record with [NewRecord]
+and build up its Attrs by traversing the old ones with [Record.Attrs].
+
 
 ## Performance considerations
 
-TODO: mention to avoid unnecessary work (like calling URL.String()) in log args in case
-the log statement is disabled.
+If profiling your application demonstrates that logging is taking significant time,
+the following suggestions may help.
+
+If many log lines have a common attribute, use [Logger.With] to create a Logger with
+that attribute. The built-in handlers will format that attribute only once, at the
+call to [Logger.With]. The [Handler] interface is designed to allow that optimization,
+and a well-written Handler should take advantage of it.
+
+The arguments to a log call are always evaluated, even if the log event is discarded.
+If possible, defer computation so that it happens only if the value is actually logged.
+For example, consider the call
+
+    slog.Info("starting request", "url", r.URL.String())  // may compute String unnecessarily
+
+The URL.String method will be called even if the logger discards Info-level events.
+Instead, pass the URL directly:
+
+    slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed
+
+The built-in [TextHandler] will call its String method, but only
+if the log event is enabled.
+Avoiding the call to String also preserves the structure of the underlying value.
+For example [JSONHandler] emits the components of the parsed URL as a JSON object.
+If you want to avoid eagerly paying the cost of the String call
+without causing the handler to potentially inspect the structure of the value,
+wrap the value in a fmt.Stringer implementation that hides its Marshal methods.
+
+You can also use the [LogValuer] interface to avoid unnecessary work in disabled log
+calls. Say you need to log some expensive value:
+
+    slog.Debug("frobbing", "value", computeExpensiveValue(arg))
+
+Even if this line is disabled, computeExpensiveValue will be called.
+To avoid that, define a type implementing LogValuer:
+
+    type expensive struct { arg int }
+
+    func (e expensive) LogValue() slog.Value {
+        return slog.AnyValue(computeExpensiveValue(e.arg))
+    }
+
+Then use a value of that type in log calls:
+
+    slog.Debug("frobbing", "value", expensive{arg})
+
+Now computeExpensiveValue will only be called when the line is enabled.
 
 ## Writing a handler