design/56345-structured-logging.md: add design doc
This design document is a modification of the top post of the GitHub
discussion on structured logging
(https://github.com/golang/go/discussions/54763).
Most of the document is unchanged, except for reformatting.
The section on contexts has been rewritten to accommodate the new
`WithContext` method and `Ctx` function, and the sections on groups
and the `LogValue` method have been added.
Change-Id: Ife6385c87c872c2a31c036ca63f8f61e6e2902a0
Reviewed-on: https://go-review.googlesource.com/c/proposal/+/444415
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/design/56345-structured-logging.md b/design/56345-structured-logging.md
new file mode 100644
index 0000000..a92d2a0
--- /dev/null
+++ b/design/56345-structured-logging.md
@@ -0,0 +1,1574 @@
+# Proposal: Structured Logging
+
+Author: Jonathan Amsterdam
+
+Date: 2022-10-19
+
+Issue: https://go.dev/issue/NNNNN
+
+Discussion: https://github.com/golang/go/discussions/54763
+
+Preliminary implementation: https://go.googlesource.com/exp/+/refs/heads/master/slog
+
+Package documentation: https://pkg.go.dev/golang.org/x/exp/slog
+
+We propose adding structured logging with levels to the standard library, to
+reside in a new package with import path `log/slog`.
+
+Structured logging is the ability to output logs with machine-readable
+structure, typically key-value pairs, in addition to a human-readable message.
+Structured logs can be parsed, filtered, searched and analyzed faster and more
+reliably than logs designed only for people to read.
+For many programs that aren't run directly by a user, like servers, logging is
+the main way for developers to observe the detailed behavior of the system, and
+often the first place they go to debug it.
+Logs therefore tend to be voluminous, and the ability to search and filter them
+quickly is essential.
+
+In theory, one can produce structured logs with any logging package:
+```
+log.Printf(`{"message": %q, "count": %d}`, msg, count)
+```
+In practice, this is too tedious and error-prone, so structured logging packages
+provide an API for expressing key-value pairs.
+This proposal contains such an API.
+
+We also propose generalizing the logging "backend."
+The `log` package provides control only over the `io.Writer` that logs are
+written to.
+In the new package, every logger has a handler that can process a log event
+however it wishes.
+Although it is possible to have a structured logger with a fixed backend (for
+instance, [zerolog] outputs only JSON), having a flexible backend provides
+several benefits: programs can display the logs in a variety of formats, convert
+them to an RPC message for a network logging service, store them for later
+processing, and add to or modify the data.
+
+Lastly, the design incorporates levels in a way that accommodates both
+traditional named levels and [logr]-style verbosities.
+
+The goals of this design are:
+
+- Ease of use.
+ A survey of the existing logging packages shows that programmers
+ want an API that is light on the page and easy to understand.
+ This proposal adopts the most popular way to express key-value pairs:
+ alternating keys and values.
+
+- High performance.
+ The API has been designed to minimize allocation and locking.
+ It provides an alternative to alternating keys and values that is
+ more cumbersome but faster (similar to [Zap]'s `Field`s).
+
+- Integration with runtime tracing.
+ The Go team is developing an improved runtime tracing system.
+ Logs from this package will be incorporated seamlessly
+ into those traces, giving developers the ability to correlate their program's
+ actions with the behavior of the runtime.
+
+## What does success look like?
+
+Go has many popular structured logging packages, all good at what they do.
+We do not expect developers to rewrite their existing third-party structured
+logging code to use this new package.
+We expect existing logging packages to coexist with this one for the foreseeable
+future.
+
+We have tried to provide an API that is pleasant enough that users will prefer it to existing
+packages in new code, if only to avoid a dependency.
+(Some developers may find the runtime tracing integration compelling.)
+We also expect newcomers to Go to encounter this package before
+learning third-party packages, so they will likely be most familiar with it.
+
+But more important than any traction gained by the "frontend" is the promise of
+a common "backend."
+An application with many dependencies may find that it has linked in many
+logging packages.
+When all the logging packages support the standard handler interface proposed here,
+then the application can create a single handler and install it once
+for each logging library to get consistent logging across all its dependencies.
+Since this happens in the application's main function, the benefits of a unified
+backend can be obtained with minimal code churn.
+We expect that this proposal's handlers will be implemented for all popular logging
+formats and network protocols, and that every common logging framework will
+provide a shim from their own backend to a handler.
+Then the Go logging community can work together to build high-quality backends
+that all can share.
+
+## Prior Work
+
+The existing `log` package has been in the standard library since the release of
+Go 1 in March 2012. It provides formatted logging, but not structured logging or
+levels.
+
+[Logrus](https://github.com/Sirupsen/logrus), one of the first structured
+logging packages, showed how an API could add structure while preserving the
+formatted printing of the `log` package. It uses maps to hold key-value pairs,
+which is relatively inefficient.
+
+[Zap] grew out of Uber's frustration with the slow log times of their
+high-performance servers. It showed how a logger that avoided allocations could
+be very fast.
+
+[Zerolog] reduced allocations even further, but at the cost of reducing the
+flexibility of the logging backend.
+
+All the above loggers include named levels along with key-value pairs. [Logr]
+and Google's own [glog] use integer verbosities instead of named levels,
+providing a more fine-grained approach to filtering high-detail logs.
+
+Other popular logging packages are Go-kit's
+[log](https://pkg.go.dev/github.com/go-kit/log), HashiCorp's [hclog], and
+[klog](https://github.com/kubernetes/klog).
+
+## Design
+
+### Overview
+
+Here is a short program that uses some of the new API:
+
+```
+import "log/slog"
+
+func main() {
+ slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr)))
+ slog.Info("hello", "name", "Al")
+ slog.Error("oops", net.ErrClosed, "status", 500)
+ slog.LogAttrs(slog.ErrorLevel, "oops",
+ slog.Int("status", 500), slog.Any("err", net.ErrClosed))
+}
+```
+
+This program generates the following output on standard error:
+
+```
+time=2022-10-24T16:05:48.054-04:00 level=INFO msg=hello name=Al
+time=2022-10-24T16:05:48.054-04:00 level=ERROR msg=oops status=500 err="use of closed network connection"
+time=2022-10-24T16:05:48.054-04:00 level=ERROR msg=oops status=500 err="use of closed network connection"
+```
+
+It begins by setting the default logger to one that writes log records in an
+easy-to-read format similar to [logfmt].
+(There is also a built-in handler for JSON.)
+
+If the `slog.SetDefault` line is omitted,
+the output is sent to the standard log package,
+producing mostly structured output:
+
+```
+2022/10/24 16:07:00 INFO hello name=Al
+2022/10/24 16:07:00 ERROR oops status=500 err="use of closed network connection"
+2022/10/24 16:07:00 ERROR oops status=500 err="use of closed network connection"
+```
+
+The program outputs three log messages augmented with key-value pairs.
+The first logs at the Info level, passing a single key-value pair along with the
+message.
+The second logs at the Error level, passing an `error` and a key-value pair.
+
+The third produces the same output as the second, but more efficiently.
+Functions like `Any` and `Int` construct `slog.Attr` values, which are key-value
+pairs that avoid memory allocation for most values.
+
+### Main Types
+
+The `slog` package contains three main types:
+
+- `Logger` is the frontend, providing output methods like `Info` and `LogAttrs` that
+ developers call to produce logs.
+
+- Each call to a `Logger` output method creates a `Record`.
+
+- The `Record` is passed to a `Handler` for output.
+
+We cover these bottom-up, beginning with `Handler`.
+
+### Handlers
+
+A `Handler` describes the logging backend.
+It handles log records produced by a `Logger`.
+
+A typical handler may print log records to standard error, or write them to
+a file, database or network service, or perhaps augment them with additional attributes and
+pass them on to another handler.
+
+```
+type Handler interface {
+ // Enabled reports whether the handler handles records at the given level.
+ // The handler ignores records whose level is lower.
+ // Enabled is called early, before any arguments are processed,
+ // to save effort if the log event should be discarded.
+ Enabled(Level) bool
+
+ // Handle handles the Record.
+ // It will only be called if Enabled returns true.
+ // Handle methods that produce output should observe the following rules:
+ // - If r.Time is the zero time, ignore the time.
+ // - If an Attr's key is the empty string, ignore the Attr.
+ Handle(r Record) error
+
+ // WithAttrs returns a new Handler whose attributes consist of
+ // both the receiver's attributes and the arguments.
+ // The Handler owns the slice: it may retain, modify or discard it.
+ WithAttrs(attrs []Attr) Handler
+
+ // WithGroup returns a new Handler with the given group appended to
+ // the receiver's existing groups.
+ // The keys of all subsequent attributes, whether added by With or in a
+ // Record, should be qualified by the sequence of group names.
+ //
+ // How this qualification happens is up to the Handler, so long as
+ // this Handler's attribute keys differ from those of another Handler
+ // with a different sequence of group names.
+ //
+ // A Handler should treat WithGroup as starting a Group of Attrs that ends
+ // at the end of the log event. That is,
+ //
+ // logger.WithGroup("s").LogAttrs(slog.Int("a", 1), slog.Int("b", 2))
+ //
+ // should behave like
+ //
+ // logger.LogAttrs(slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
+ WithGroup(name string) Handler
+}
+```
+
+The `slog` package provides two handlers, one for simple textual output and one
+for JSON. They are described in more detail below.
+
+### The `Record` Type
+
+A Record holds information about a log event.
+
+```
+type Record struct {
+ // The time at which the output method (Log, Info, etc.) was called.
+ Time time.Time
+
+ // The log message.
+ Message string
+
+ // The level of the event.
+ Level Level
+
+ // The context of the Logger that created the Record. Present
+ // solely to provide Handlers access to the context's values.
+ // Canceling the context should not affect record processing.
+ Context context.Context
+
+ // Has unexported fields.
+}
+
+func (r Record) SourceLine() (file string, line int)
+ SourceLine returns the file and line of the log event. If the Record
+ was created without the necessary information, or if the location is
+ unavailable, it returns ("", 0).
+```
+
+Records have two methods for accessing the sequence of `Attr`s. This API allows
+an efficient implementation of the `Attr` sequence that avoids copying and
+minimizes allocation.
+
+```
+func (r Record) Attrs(f func(Attr))
+ Attrs calls f on each Attr in the Record.
+
+func (r Record) NumAttrs() int
+ NumAttrs returns the number of attributes in the Record.
+```
+
+So that other logging backends can wrap `Handler`s, it is possible to construct
+a `Record` directly and add attributes to it:
+
+```
+func NewRecord(t time.Time, level Level, msg string, calldepth int, ctx context.Context) Record
+ NewRecord creates a Record from the given arguments. Use Record.AddAttrs
+ to add attributes to the Record. If calldepth is greater than zero,
+ Record.SourceLine will return the file and line number at that depth,
+ where 1 means the caller of NewRecord.
+
+ NewRecord is intended for logging APIs that want to support a Handler as a
+ backend.
+
+func (r *Record) AddAttrs(attrs ...Attr)
+ AddAttrs appends the given attrs to the Record's list of Attrs.
+```
+
+Copies of a `Record` share state. A `Record` should not be modified after
+handing out a copy to it. Use `Clone` for that:
+
+```
+func (r Record) Clone() Record
+ Clone returns a copy of the record with no shared state. The original record
+ and the clone can both be modified without interfering with each other.
+```
+
+### The `Attr` and `Value` Types
+
+An `Attr` is a key-value pair.
+
+```
+type Attr struct {
+ Key string
+ Value Value
+}
+```
+
+There are convenience functions for constructing `Attr`s with various value
+types, as well as `Equal` and `String` methods.
+
+```
+func Any(key string, value any) Attr
+ Any returns an Attr for the supplied value. See Value.AnyValue for how
+ values are treated.
+
+func Bool(key string, v bool) Attr
+ Bool returns an Attr for a bool.
+
+func Duration(key string, v time.Duration) Attr
+ Duration returns an Attr for a time.Duration.
+
+func Float64(key string, v float64) Attr
+ Float64 returns an Attr for a floating-point number.
+
+func Group(key string, as ...Attr) Attr
+ Group returns an Attr for a Group Value. The caller must not subsequently
+ mutate the argument slice.
+
+ Use Group to collect several Attrs under a single key on a log line, or as
+ the result of LogValue in order to log a single value as multiple Attrs.
+
+func Int(key string, value int) Attr
+ Int converts an int to an int64 and returns an Attr with that value.
+
+func Int64(key string, value int64) Attr
+ Int64 returns an Attr for an int64.
+
+func String(key, value string) Attr
+ String returns an Attr for a string value.
+
+func Time(key string, v time.Time) Attr
+ Time returns an Attr for a time.Time. It discards the monotonic portion.
+
+func Uint64(key string, v uint64) Attr
+ Uint64 returns an Attr for a uint64.
+
+func (a Attr) Equal(b Attr) bool
+ Equal reports whether a and b have equal keys and values.
+
+func (a Attr) String() string
+```
+
+A `Value` can represent any Go value, but unlike type `any`, it can represent
+most small values without an allocation.
+In particular, integer types and strings, which account for the vast
+majority of values in log messages, do not require allocation.
+The default version of `Value` uses package `unsafe` to store any value in three
+machine words.
+The version without `unsafe` requires five.
+
+There are constructor functions for common types, and a general one,
+`AnyValue`, that dispatches on its argument type.
+
+```
+type Value struct {
+ // Has unexported fields.
+}
+
+func AnyValue(v any) Value
+ AnyValue returns a Value for the supplied value.
+
+ Given a value of one of Go's predeclared string, bool, or (non-complex)
+ numeric types, AnyValue returns a Value of kind String, Bool, Uint64, Int64,
+ or Float64. The width of the original numeric type is not preserved.
+
+ Given a time.Time or time.Duration value, AnyValue returns a Value of kind
+ TimeKind or DurationKind. The monotonic time is not preserved.
+
+ For nil, or values of all other types, including named types whose
+ underlying type is numeric, AnyValue returns a value of kind AnyKind.
+
+func BoolValue(v bool) Value
+ BoolValue returns a Value for a bool.
+
+func DurationValue(v time.Duration) Value
+ DurationValue returns a Value for a time.Duration.
+
+func Float64Value(v float64) Value
+ Float64Value returns a Value for a floating-point number.
+
+func GroupValue(as ...Attr) Value
+ GroupValue returns a new Value for a list of Attrs. The caller must not
+ subsequently mutate the argument slice.
+
+func Int64Value(v int64) Value
+ Int64Value returns a Value for an int64.
+
+func IntValue(v int) Value
+ IntValue returns a Value for an int.
+
+func StringValue(value string) Value
+ String returns a new Value for a string.
+
+func TimeValue(v time.Time) Value
+ TimeValue returns a Value for a time.Time. It discards the monotonic
+ portion.
+
+func Uint64Value(v uint64) Value
+ Uint64Value returns a Value for a uint64.
+```
+
+Extracting Go values from a `Value` is reminiscent of `reflect.Value`: there is
+a `Kind` method that returns an enum of type `Kind`, and a method for each `Kind`
+that returns the value or panics if it is the wrong kind.
+
+```
+type Kind int
+ Kind is the kind of a Value.
+
+const (
+ AnyKind Kind = iota
+ BoolKind
+ DurationKind
+ Float64Kind
+ Int64Kind
+ StringKind
+ TimeKind
+ Uint64Kind
+ GroupKind
+ LogValuerKind
+)
+
+func (v Value) Any() any
+ Any returns v's value as an any.
+
+func (v Value) Bool() bool
+ Bool returns v's value as a bool. It panics if v is not a bool.
+
+func (a Value) Duration() time.Duration
+ Duration returns v's value as a time.Duration. It panics if v is not a
+ time.Duration.
+
+func (v Value) Equal(w Value) bool
+ Equal reports whether v and w have equal keys and values.
+
+func (v Value) Float64() float64
+ Float64 returns v's value as a float64. It panics if v is not a float64.
+
+func (v Value) Group() []Attr
+ Group returns v's value as a []Attr. It panics if v's Kind is not GroupKind.
+
+func (v Value) Int64() int64
+ Int64 returns v's value as an int64. It panics if v is not a signed integer.
+
+func (v Value) Kind() Kind
+ Kind returns v's Kind.
+
+func (v Value) LogValuer() LogValuer
+ LogValuer returns v's value as a LogValuer. It panics if v is not a
+ LogValuer.
+
+func (v Value) Resolve() Value
+ Resolve repeatedly calls LogValue on v while it implements LogValuer, and
+ returns the result. If the number of LogValue calls exceeds a threshold, a
+ Value containing an error is returned. Resolve's return value is guaranteed
+ not to be of Kind LogValuerKind.
+
+func (v Value) String() string
+ String returns Value's value as a string, formatted like fmt.Sprint.
+ Unlike the methods Int64, Float64, and so on, which panic if v is of the
+ wrong kind, String never panics.
+
+func (v Value) Time() time.Time
+ Time returns v's value as a time.Time. It panics if v is not a time.Time.
+
+func (v Value) Uint64() uint64
+ Uint64 returns v's value as a uint64. It panics if v is not an unsigned
+ integer.
+```
+
+#### The LogValuer interface
+
+A LogValuer is any Go value that can convert itself into a Value for
+logging.
+
+This mechanism may be used to defer expensive operations until they are
+needed, or to expand a single value into a sequence of components.
+
+```
+type LogValuer interface {
+ LogValue() Value
+}
+```
+
+If the Go value in a `Value` implements `LogValuer`,
+then a `Handler` should use the result of calling `LogValue`.
+It can use `Value.Resolve` to do so.
+
+```
+func (v Value) Resolve() Value
+ Resolve repeatedly calls LogValue on v while it implements LogValuer, and
+ returns the result. If the number of LogValue calls exceeds a threshold, a
+ Value containing an error is returned. Resolve's return value is guaranteed
+ not to be of Kind LogValuerKind.
+```
+
+As an example, a type could obscure its value in log output like so:
+
+```
+type Password string
+
+func (p Password) LogValue() slog.Value {
+ return slog.StringValue("REDACTED")
+}
+```
+
+### Loggers
+
+A Logger records structured information about each call to its Log, Debug,
+Info, Warn, and Error methods. For each call, it creates a `Record` and passes
+it to a `Handler`.
+```
+type Logger struct {
+ // Has unexported fields.
+}
+```
+
+A `Logger` consists of a `Handler` and a context (about which see [the Context
+section below](#context-support)). Use `New` to create `Logger` with a
+`Handler`, and the `Handler` method to retrieve it.
+
+```
+func New(h Handler) Logger
+ New creates a new Logger with the given Handler.
+
+func (l Logger) Handler() Handler
+ Handler returns l's Handler.
+```
+
+There is a single, global default `Logger`.
+It can be set and retrieved with the `SetDefault` and
+`Default` functions.
+
+```
+func SetDefault(l Logger)
+ SetDefault makes l the default Logger. After this call, output from the
+ log package's default Logger (as with log.Print, etc.) will be logged at
+ InfoLevel using l's Handler.
+
+func Default() Logger
+ Default returns the default Logger.
+```
+
+The `slog` package works to ensure consistent output with the `log` package.
+Writing to `slog`'s default logger without setting a handler will write
+structured text to `log`'s default logger.
+Once a handler is set with `SetDefault`, as in the example above, the default
+`log` logger will send its text output to the structured handler.
+
+#### Output methods
+
+`Logger`'s output methods produce log output by constructing a `Record` and
+passing it to the `Logger`'s handler.
+There is an output method for each of four most common levels, a `Log` method
+that takes any level, and a `LogAttrs` method that accepts only `Attr`s as an
+optimization.
+
+These methods first call `Handler.Enabled` to see if they should proceed.
+
+Each of these methods has a corresponding top-level function that uses the
+default logger.
+
+```
+func (l Logger) Log(level Level, msg string, args ...any)
+ Log emits a log record with the current time and the given level and
+ message. The Record's Attrs consist of the Logger's attributes followed by
+ the Attrs specified by args.
+
+ The attribute arguments are processed as follows:
+ - If an argument is an Attr, it is used as is.
+ - If an argument is a string and this is not the last argument, the
+ following argument is treated as the value and the two are combined into
+ an Attr.
+ - Otherwise, the argument is treated as a value with key "!BADKEY".
+
+func (l Logger) LogAttrs(level Level, msg string, attrs ...Attr)
+ LogAttrs is a more efficient version of Logger.Log that accepts only Attrs.
+
+func (l Logger) Debug(msg string, args ...any)
+ Debug logs at DebugLevel.
+
+func (l Logger) Info(msg string, args ...any)
+ Info logs at InfoLevel.
+
+func (l Logger) Warn(msg string, args ...any)
+ Warn logs at WarnLevel.
+
+func (l Logger) Error(msg string, err error, args ...any)
+ Error logs at ErrorLevel. If err is non-nil, Error appends Any("err",
+ err) to the list of attributes.
+```
+
+The `Log` and `LogAttrs` methods have variants that take a call depth, so other
+functions can wrap them and adjust the source line information.
+
+```
+func (l Logger) LogDepth(calldepth int, level Level, msg string, args ...any)
+ LogDepth is like Logger.Log, but accepts a call depth to adjust the file
+ and line number in the log record. 0 refers to the caller of LogDepth;
+ 1 refers to the caller's caller; and so on.
+
+func (l Logger) LogAttrsDepth(calldepth int, level Level, msg string, attrs ...Attr)
+ LogAttrsDepth is like Logger.LogAttrs, but accepts a call depth argument
+ which it interprets like Logger.LogDepth.
+```
+
+Loggers can have attributes as well, added by the `With` method.
+
+```
+func (l Logger) With(args ...any) Logger
+ With returns a new Logger that includes the given arguments, converted to
+ Attrs as in Logger.Log. The Attrs will be added to each output from the
+ Logger.
+
+ The new Logger's handler is the result of calling WithAttrs on the
+ receiver's handler.
+```
+
+### Groups
+
+Although most attribute values are simple types like strings and integers,
+sometimes aggregate or composite values are desired.
+For example, consider
+
+```
+type Name struct {
+ First, Last string
+}
+```
+
+To handle values like this we include `GroupKind` for groups of Attrs.
+To log a `Name` `n` as a group, we could write
+
+```
+slog.Info("message",
+ slog.Group("name",
+ slog.String("first", n.First),
+ slog.String("last", n.Last),
+ ),
+)
+```
+
+Handlers should qualify a group's members by its name.
+What "qualify" means depends on the handler.
+A handler that supports recursive data, like the
+built-in `JSONHandler`, can use the group name as a key to a nested object:
+
+```
+"name": {"first": "Ren", "last": "Hoek"}
+```
+
+Handlers that use a flat output representation, like the built-in `TextHandler`,
+could prefix the group member's keys with the group name.
+This is `TextHandler`'s output:
+
+```
+name.first=Ren name.last=Hoek
+```
+
+If the author of the `Name` type wanted to arrange matters so that `Name`s
+always logged in this way, they could implement the `LogValuer` interface discussed
+[above](#the-logvaluer-interface):
+
+```
+func (n Name) LogValue() slog.Value {
+ return slog.GroupValue(
+ slog.String("first", n.First),
+ slog.String("last", n.Last),
+ )
+}
+```
+
+Now, if `n` is a `Name`, the log line
+
+```
+slog.Info("message", "name", n)
+```
+
+will render exactly like the example with an explicit `slog.Group` above.
+
+#### Logger groups
+
+Sometimes it is useful to qualify all the attribute keys from a Logger.
+For example, an application may be composed of multiple subsystems, some of
+which may use the same attribute keys.
+Qualifying each subsystem's keys is one way to avoid duplicates.
+This can be done with `Logger.WithGroup`.
+Duplicate keys can be avoided by handing each subsystem a `Logger` with a
+different group.
+
+
+```
+func (l Logger) WithGroup(name string) Logger
+ WithGroup returns a new Logger that starts a group. The keys of all
+ attributes added to the Logger will be qualified by the given name.
+```
+
+### Context Support
+
+#### Loggers in contexts
+
+Passing a logger in a `context.Context` is a common practice and one way to
+include dynamically scoped information in log messages.
+
+The `slog` package has two functions to support this pattern.
+
+```
+func FromContext(ctx context.Context) Logger
+ FromContext returns the Logger stored in ctx by NewContext, or the default
+ Logger if there is none.
+
+func NewContext(ctx context.Context, l Logger) context.Context
+ NewContext returns a context that contains the given Logger. Use FromContext
+ to retrieve the Logger.
+```
+
+As an example, an HTTP server might want to create a new `Logger` for each
+request. The logger would contain request-wide attributes and be stored in the
+context for the request.
+
+```
+func handle(w http.ResponseWriter, r *http.Request) {
+ rlogger := slog.FromContext(r.Context()).With(
+ "method", r.Method,
+ "url", r.URL,
+ "traceID", getTraceID(r),
+ )
+ ctx := slog.NewContext(r.Context(), rlogger)
+ // ... use slog.FromContext(ctx) ...
+}
+```
+
+#### Contexts in Loggers
+
+Putting a new `Logger` into a context each time a value is added to the context
+complicates the code, and is easy to forget to do.
+Tracing spans are a prime example.
+In tracing packages like the one provided by [Open
+Telemetry](https://opentelemetry.io), spans can be created at any point in the
+code with
+
+```
+ctx, span := tracer.Start(ctx, name, opts)
+```
+
+It is too much to require programmers to then write
+```
+ctx = slog.NewContext(slog.FromContext(ctx))
+```
+so that subsequent logging has access to the span ID.
+So we provide the `Logger.WithContext` method to convey a context to a
+`Handler`.
+The method returns a new `Logger` that can be stored, or immediately
+used to produce log output.
+A `Logger`'s context is available to a `Handler.Handle` method in the
+`Record.Context` field.
+We also provide a convenient shorthand, `slog.Ctx`.
+
+```
+func (l Logger) WithContext(ctx context.Context) Logger
+ WithContext returns a new Logger with the same handler as the receiver and
+ the given context.
+
+func (l Logger) Context() context.Context
+ Context returns l's context.
+
+func Ctx(ctx context.Context) Logger
+ Ctx retrieves a Logger from the given context using FromContext. Then it
+ adds the given context to the Logger using WithContext and returns the
+ result.
+```
+
+### Levels
+
+A Level is the importance or severity of a log event. The higher the level,
+the more important or severe the event.
+
+```
+type Level int
+```
+
+The `slog` package provides names for common levels.
+
+The level numbers below don't really matter too much. Any system can map
+them to another numbering scheme if it wishes. We picked them to satisfy
+three constraints.
+
+First, we wanted the default level to be Info. Since Levels are ints,
+Info is the default value for int, zero.
+
+Second, we wanted to make it easy to work with verbosities instead of levels.
+As discussed above,
+some logging packages like [glog] and [Logr] use verbosities instead, where
+a verbosity of 0 corresponds to the Info level and higher values represent less
+important messages.
+Negating a verbosity converts it into a Level. To use a verbosity of `v` with
+this design, pass `-v` to `Log` or `LogAttrs`.
+
+Third, we wanted some room between levels to accommodate schemes with
+named levels between ours. For example, Google Cloud Logging defines a
+Notice level between Info and Warn. Since there are only a few of these
+intermediate levels, the gap between the numbers need not be large.
+Our gap of 4 matches OpenTelemetry's mapping. Subtracting 9 from an
+OpenTelemetry level in the DEBUG, INFO, WARN and ERROR ranges converts it to
+the corresponding slog Level range. OpenTelemetry also has the names TRACE
+and FATAL, which slog does not. But those OpenTelemetry levels can still be
+represented as slog Levels by using the appropriate integers.
+
+```
+const (
+ DebugLevel Level = -4
+ InfoLevel Level = 0
+ WarnLevel Level = 4
+ ErrorLevel Level = 8
+)
+```
+
+The `Leveler` interface generalizes `Level`, so that a `Handler.Enabled`
+implementation can vary its behavior. One way to get dynamic behavior
+is to use `LevelVar`.
+
+```
+type Leveler interface {
+ Level() Level
+}
+ A Leveler provides a Level value.
+
+ As Level itself implements Leveler, clients typically supply a Level value
+ wherever a Leveler is needed, such as in HandlerOptions. Clients who need to
+ vary the level dynamically can provide a more complex Leveler implementation
+ such as *LevelVar.
+
+func (l Level) Level() Level
+ Level returns the receiver. It implements Leveler.
+
+type LevelVar struct {
+ // Has unexported fields.
+}
+ A LevelVar is a Level variable, to allow a Handler level to change
+ dynamically. It implements Leveler as well as a Set method, and it is safe
+ for use by multiple goroutines. The zero LevelVar corresponds to InfoLevel.
+
+func (v *LevelVar) Level() Level
+ Level returns v's level.
+
+func (v *LevelVar) Set(l Level)
+ Set sets v's level to l.
+
+func (v *LevelVar) String() string
+```
+
+### Provided Handlers
+
+The `slog` package includes two handlers, which behave similarly except for
+their output format. `TextHandler` emits attributes as `KEY=VALUE`, and
+`JSONHandler` writes line-delimited JSON objects.
+Both can be configured using a `HandlerOptions`.
+A zero `HandlerOptions` consists entirely of default values.
+
+```
+type HandlerOptions struct {
+ // When AddSource is true, the handler adds a ("source", "file:line")
+ // attribute to the output indicating the source code position of the log
+ // statement. AddSource is false by default to skip the cost of computing
+ // this information.
+ AddSource bool
+
+ // Level reports the minimum record level that will be logged.
+ // The handler discards records with lower levels.
+ // If Level is nil, the handler assumes InfoLevel.
+ // The handler calls Level.Level for each record processed;
+ // to adjust the minimum level dynamically, use a LevelVar.
+ Level Leveler
+
+ // ReplaceAttr is called to rewrite each attribute before it is logged.
+ // If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
+ //
+ // The built-in attributes with keys "time", "level", "source", and "msg"
+ // are passed to this function first, except that time and level are omitted
+ // if zero, and source is omitted if AddSourceLine is false.
+ //
+ // ReplaceAttr can be used to change the default keys of the built-in
+ // attributes, convert types (for example, to replace a `time.Time` with the
+ // integer seconds since the Unix epoch), sanitize personal information, or
+ // remove attributes from the output.
+ ReplaceAttr func(a Attr) Attr
+}
+```
+
+## Interoperating with Other Log Packages
+
+As stated earlier, we expect that this package will interoperate with other log
+packages.
+
+One way that could happen is for another package's frontend to send
+`slog.Record`s to a `slog.Handler`.
+For instance, a `logr.LogSink` implementation could construct a `Record` from a
+message and list of keys and values, and pass it to a `Handler`.
+That is facilitated by `NewRecord` and `Record.AddAttrs`, described above.
+
+Another way for two log packages to work together is for the other package to
+wrap its backend as a `slog.Handler`, so users could write code with the `slog`
+package's API but connect the results to an existing `logr.LogSink`, for
+example.
+This involves writing a `slog.Handler` that wraps the other logger's backend.
+Doing so doesn't seem to require any additional support from this package.
+
+## Acknowledgements
+
+Ian Cottrell's ideas about high-performance observability, captured in the
+`golang.org/x/exp/event` package, informed a great deal of the design and
+implementation of this proposal.
+
+Seth Vargo’s ideas on logging were a source of motivation and inspiration. His
+comments on an earlier draft helped improve the proposal.
+
+Michael Knyszek explained how logging could work with runtime tracing.
+
+Tim Hockin helped us understand logr's design choices, which led to significant
+improvements.
+
+Abhinav Gupta helped me understand Zap in depth, which informed the design.
+
+Russ Cox provided valuable feedback and helped shape the final design.
+
+Alan Donovan's CL reviews greatly improved the implementation.
+
+The participants in the [GitHub
+discussion](https://github.com/golang/go/discussions/54763) helped us confirm we
+were on the right track, and called our attention to important features we had
+overlooked (and have since added).
+
+[zerolog]: https://pkg.go.dev/github.com/rs/zerolog
+[Zerolog]: https://pkg.go.dev/github.com/rs/zerolog
+[logfmt]: https://pkg.go.dev/github.com/kr/logfmt
+[zap]: https://pkg.go.dev/go.uber.org/zap
+[logr]: https://pkg.go.dev/github.com/go-logr/logr
+[Logr]: https://pkg.go.dev/github.com/go-logr/logr
+[hclog]: https://pkg.go.dev/github.com/hashicorp/go-hclog
+[glog]: https://pkg.go.dev/github.com/golang/glog
+
+## Appendix: API
+
+```
+package slog
+
+
+CONSTANTS
+
+const (
+ // TimeKey is the key used by the built-in handlers for the time
+ // when the log method is called. The associated Value is a [time.Time].
+ TimeKey = "time"
+ // LevelKey is the key used by the built-in handlers for the level
+ // of the log call. The associated value is a [Level].
+ LevelKey = "level"
+ // MessageKey is the key used by the built-in handlers for the
+ // message of the log call. The associated value is a string.
+ MessageKey = "msg"
+ // SourceKey is the key used by the built-in handlers for the source file
+ // and line of the log call. The associated value is a string.
+ SourceKey = "source"
+)
+ Keys for "built-in" attributes.
+
+
+FUNCTIONS
+
+func Debug(msg string, args ...any)
+ Debug calls Logger.Debug on the default logger.
+
+func Error(msg string, err error, args ...any)
+ Error calls Logger.Error on the default logger.
+
+func Info(msg string, args ...any)
+ Info calls Logger.Info on the default logger.
+
+func Log(level Level, msg string, args ...any)
+ Log calls Logger.Log on the default logger.
+
+func LogAttrs(level Level, msg string, attrs ...Attr)
+ LogAttrs calls Logger.LogAttrs on the default logger.
+
+func NewContext(ctx context.Context, l Logger) context.Context
+ NewContext returns a context that contains the given Logger. Use FromContext
+ to retrieve the Logger.
+
+func SetDefault(l Logger)
+ SetDefault makes l the default Logger. After this call, output from the
+ log package's default Logger (as with log.Print, etc.) will be logged at
+ InfoLevel using l's Handler.
+
+func Warn(msg string, args ...any)
+ Warn calls Logger.Warn on the default logger.
+
+
+TYPES
+
+type Attr struct {
+ Key string
+ Value Value
+}
+ An Attr is a key-value pair.
+
+func Any(key string, value any) Attr
+ Any returns an Attr for the supplied value. See [Value.AnyValue] for how
+ values are treated.
+
+func Bool(key string, v bool) Attr
+ Bool returns an Attr for a bool.
+
+func Duration(key string, v time.Duration) Attr
+ Duration returns an Attr for a time.Duration.
+
+func Float64(key string, v float64) Attr
+ Float64 returns an Attr for a floating-point number.
+
+func Group(key string, as ...Attr) Attr
+ Group returns an Attr for a Group Value. The caller must not subsequently
+ mutate the argument slice.
+
+ Use Group to collect several Attrs under a single key on a log line, or as
+ the result of LogValue in order to log a single value as multiple Attrs.
+
+func Int(key string, value int) Attr
+ Int converts an int to an int64 and returns an Attr with that value.
+
+func Int64(key string, value int64) Attr
+ Int64 returns an Attr for an int64.
+
+func String(key, value string) Attr
+ String returns an Attr for a string value.
+
+func Time(key string, v time.Time) Attr
+ Time returns an Attr for a time.Time. It discards the monotonic portion.
+
+func Uint64(key string, v uint64) Attr
+ Uint64 returns an Attr for a uint64.
+
+func (a Attr) Equal(b Attr) bool
+ Equal reports whether a and b have equal keys and values.
+
+func (a Attr) String() string
+
+type Handler interface {
+ // Enabled reports whether the handler handles records at the given level.
+ // The handler ignores records whose level is lower.
+ // Enabled is called early, before any arguments are processed,
+ // to save effort if the log event should be discarded.
+ Enabled(Level) bool
+
+ // Handle handles the Record.
+ // It will only be called if Enabled returns true.
+ // Handle methods that produce output should observe the following rules:
+ // - If r.Time is the zero time, ignore the time.
+ // - If an Attr's key is the empty string, ignore the Attr.
+ Handle(r Record) error
+
+ // WithAttrs returns a new Handler whose attributes consist of
+ // both the receiver's attributes and the arguments.
+ // The Handler owns the slice: it may retain, modify or discard it.
+ WithAttrs(attrs []Attr) Handler
+
+ // WithGroup returns a new Handler with the given group appended to
+ // the receiver's existing groups.
+ // The keys of all subsequent attributes, whether added by With or in a
+ // Record, should be qualified by the sequence of group names.
+ //
+ // How this qualification happens is up to the Handler, so long as
+ // this Handler's attribute keys differ from those of another Handler
+ // with a different sequence of group names.
+ //
+ // A Handler should treat WithGroup as starting a Group of Attrs that ends
+ // at the end of the log event. That is,
+ //
+ // logger.WithGroup("s").LogAttrs(slog.Int("a", 1), slog.Int("b", 2))
+ //
+ // should behave like
+ //
+ // logger.LogAttrs(slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
+ WithGroup(name string) Handler
+}
+ A Handler handles log records produced by a Logger..
+
+ A typical handler may print log records to standard error, or write them to
+ a file or database, or perhaps augment them with additional attributes and
+ pass them on to another handler.
+
+ Any of the Handler's methods may be called concurrently with itself or
+ with other methods. It is the responsibility of the Handler to manage this
+ concurrency.
+
+type HandlerOptions struct {
+ // When AddSource is true, the handler adds a ("source", "file:line")
+ // attribute to the output indicating the source code position of the log
+ // statement. AddSource is false by default to skip the cost of computing
+ // this information.
+ AddSource bool
+
+ // Level reports the minimum record level that will be logged.
+ // The handler discards records with lower levels.
+ // If Level is nil, the handler assumes InfoLevel.
+ // The handler calls Level.Level for each record processed;
+ // to adjust the minimum level dynamically, use a LevelVar.
+ Level Leveler
+
+ // ReplaceAttr is called to rewrite each attribute before it is logged.
+ // If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
+ //
+ // The built-in attributes with keys "time", "level", "source", and "msg"
+ // are passed to this function first, except that time and level are omitted
+ // if zero, and source is omitted if AddSourceLine is false.
+ //
+ // ReplaceAttr can be used to change the default keys of the built-in
+ // attributes, convert types (for example, to replace a `time.Time` with the
+ // integer seconds since the Unix epoch), sanitize personal information, or
+ // remove attributes from the output.
+ ReplaceAttr func(a Attr) Attr
+}
+ HandlerOptions are options for a TextHandler or JSONHandler. A zero
+ HandlerOptions consists entirely of default values.
+
+func (opts HandlerOptions) NewJSONHandler(w io.Writer) *JSONHandler
+ NewJSONHandler creates a JSONHandler with the given options that writes to
+ w.
+
+func (opts HandlerOptions) NewTextHandler(w io.Writer) *TextHandler
+ NewTextHandler creates a TextHandler with the given options that writes to
+ w.
+
+type JSONHandler struct {
+ // Has unexported fields.
+}
+ JSONHandler is a Handler that writes Records to an io.Writer as
+ line-delimited JSON objects.
+
+func NewJSONHandler(w io.Writer) *JSONHandler
+ NewJSONHandler creates a JSONHandler that writes to w, using the default
+ options.
+
+func (h *JSONHandler) Enabled(level Level) bool
+ Enabled reports whether the handler handles records at the given level.
+ The handler ignores records whose level is lower.
+
+func (h *JSONHandler) Handle(r Record) error
+ Handle formats its argument Record as a JSON object on a single line.
+
+ If the Record's time is zero, the time is omitted. Otherwise, the key is
+ "time" and the value is output as with json.Marshal.
+
+ If the Record's level is zero, the level is omitted. Otherwise, the key is
+ "level" and the value of Level.String is output.
+
+ If the AddSource option is set and source information is available, the key
+ is "source" and the value is output as "FILE:LINE".
+
+ The message's key is "msg".
+
+ To modify these or other attributes, or remove them from the output,
+ use [HandlerOptions.ReplaceAttr].
+
+ Values are formatted as with encoding/json.Marshal, with the following
+ exceptions:
+ - Floating-point NaNs and infinities are formatted as one of the strings
+ "NaN", "+Inf" or "-Inf".
+ - Levels are formatted as with Level.String.
+
+ Each call to Handle results in a single serialized call to io.Writer.Write.
+
+func (h *JSONHandler) WithAttrs(attrs []Attr) Handler
+ With returns a new JSONHandler whose attributes consists of h's attributes
+ followed by attrs.
+
+func (h *JSONHandler) WithGroup(name string) Handler
+
+type Kind int
+ Kind is the kind of a Value.
+
+const (
+ AnyKind Kind = iota
+ BoolKind
+ DurationKind
+ Float64Kind
+ Int64Kind
+ StringKind
+ TimeKind
+ Uint64Kind
+ GroupKind
+ LogValuerKind
+)
+func (k Kind) String() string
+
+type Level int
+ A Level is the importance or severity of a log event. The higher the level,
+ the more important or severe the event.
+
+const (
+ DebugLevel Level = -4
+ InfoLevel Level = 0
+ WarnLevel Level = 4
+ ErrorLevel Level = 8
+)
+ The level numbers below don't really matter too much. Any system can map
+ them to another numbering scheme if it wishes. We picked them to satisfy
+ three constraints.
+
+ First, we wanted the default level to be Info, Since Levels are ints,
+ Info is the default value for int, zero.
+
+ Second, we wanted to make it easy to work with verbosities instead of
+ levels. Verbosities start at 0 corresponding to Info, and larger values are
+ less severe Negating a verbosity converts it into a Level.
+
+ Third, we wanted some room between levels to accommodate schemes with
+ named levels between ours. For example, Google Cloud Logging defines a
+ Notice level between Info and Warn. Since there are only a few of these
+ intermediate levels, the gap between the numbers need not be large.
+ Our gap of 4 matches OpenTelemetry's mapping. Subtracting 9 from an
+ OpenTelemetry level in the DEBUG, INFO, WARN and ERROR ranges converts it to
+ the corresponding slog Level range. OpenTelemetry also has the names TRACE
+ and FATAL, which slog does not. But those OpenTelemetry levels can still be
+ represented as slog Levels by using the appropriate integers.
+
+ Names for common levels.
+
+func (l Level) Level() Level
+ Level returns the receiver. It implements Leveler.
+
+func (l Level) MarshalJSON() ([]byte, error)
+
+func (l Level) String() string
+ String returns a name for the level. If the level has a name, then that
+ name in uppercase is returned. If the level is between named values, then an
+ integer is appended to the uppercased name. Examples:
+
+ WarnLevel.String() => "WARN"
+ (WarnLevel-2).String() => "WARN-2"
+
+type LevelVar struct {
+ // Has unexported fields.
+}
+ A LevelVar is a Level variable, to allow a Handler level to change
+ dynamically. It implements Leveler as well as a Set method, and it is safe
+ for use by multiple goroutines. The zero LevelVar corresponds to InfoLevel.
+
+func (v *LevelVar) Level() Level
+ Level returns v's level.
+
+func (v *LevelVar) Set(l Level)
+ Set sets v's level to l.
+
+func (v *LevelVar) String() string
+
+type Leveler interface {
+ Level() Level
+}
+ A Leveler provides a Level value.
+
+ As Level itself implements Leveler, clients typically supply a Level value
+ wherever a Leveler is needed, such as in HandlerOptions. Clients who need to
+ vary the level dynamically can provide a more complex Leveler implementation
+ such as *LevelVar.
+
+type LogValuer interface {
+ LogValue() Value
+}
+ A LogValuer is any Go value that can convert itself into a Value for
+ logging.
+
+ This mechanism may be used to defer expensive operations until they are
+ needed, or to expand a single value into a sequence of components.
+
+type Logger struct {
+ // Has unexported fields.
+}
+ A Logger records structured information about each call to its Log, Debug,
+ Info, Warn, and Error methods. For each call, it creates a Record and passes
+ it to a Handler.
+
+ To create a new Logger, call New or a Logger method that begins "With".
+
+func Ctx(ctx context.Context) Logger
+ Ctx retrieves a Logger from the given context using FromContext. Then it
+ adds the given context to the Logger using WithContext and returns the
+ result.
+
+func Default() Logger
+ Default returns the default Logger.
+
+func FromContext(ctx context.Context) Logger
+ FromContext returns the Logger stored in ctx by NewContext, or the default
+ Logger if there is none.
+
+func New(h Handler) Logger
+ New creates a new Logger with the given Handler.
+
+func With(args ...any) Logger
+ With calls Logger.With on the default logger.
+
+func (l Logger) Context() context.Context
+ Context returns l's context.
+
+func (l Logger) Debug(msg string, args ...any)
+ Debug logs at DebugLevel.
+
+func (l Logger) Enabled(level Level) bool
+ Enabled reports whether l emits log records at the given level.
+
+func (l Logger) Error(msg string, err error, args ...any)
+ Error logs at ErrorLevel. If err is non-nil, Error appends Any("err",
+ err) to the list of attributes.
+
+func (l Logger) Handler() Handler
+ Handler returns l's Handler.
+
+func (l Logger) Info(msg string, args ...any)
+ Info logs at InfoLevel.
+
+func (l Logger) Log(level Level, msg string, args ...any)
+ Log emits a log record with the current time and the given level and
+ message. The Record's Attrs consist of the Logger's attributes followed by
+ the Attrs specified by args.
+
+ The attribute arguments are processed as follows:
+ - If an argument is an Attr, it is used as is.
+ - If an argument is a string and this is not the last argument, the
+ following argument is treated as the value and the two are combined into
+ an Attr.
+ - Otherwise, the argument is treated as a value with key "!BADKEY".
+
+func (l Logger) LogAttrs(level Level, msg string, attrs ...Attr)
+ LogAttrs is a more efficient version of Logger.Log that accepts only Attrs.
+
+func (l Logger) LogAttrsDepth(calldepth int, level Level, msg string, attrs ...Attr)
+ LogAttrsDepth is like Logger.LogAttrs, but accepts a call depth argument
+ which it interprets like Logger.LogDepth.
+
+func (l Logger) LogDepth(calldepth int, level Level, msg string, args ...any)
+ LogDepth is like Logger.Log, but accepts a call depth to adjust the file
+ and line number in the log record. 0 refers to the caller of LogDepth;
+ 1 refers to the caller's caller; and so on.
+
+func (l Logger) Warn(msg string, args ...any)
+ Warn logs at WarnLevel.
+
+func (l Logger) With(args ...any) Logger
+ With returns a new Logger that includes the given arguments, converted to
+ Attrs as in Logger.Log. The Attrs will be added to each output from the
+ Logger.
+
+ The new Logger's handler is the result of calling WithAttrs on the
+ receiver's handler.
+
+func (l Logger) WithContext(ctx context.Context) Logger
+ WithContext returns a new Logger with the same handler as the receiver and
+ the given context.
+
+func (l Logger) WithGroup(name string) Logger
+ WithGroup returns a new Logger that starts a group. The keys of all
+ attributes added to the Logger will be qualified by the given name.
+
+type Record struct {
+ // The time at which the output method (Log, Info, etc.) was called.
+ Time time.Time
+
+ // The log message.
+ Message string
+
+ // The level of the event.
+ Level Level
+
+ // The context of the Logger that created the Record. Present
+ // solely to provide Handlers access to the context's values.
+ // Canceling the context should not affect record processing.
+ Context context.Context
+
+ // Has unexported fields.
+}
+ A Record holds information about a log event. Copies of a Record share
+ state. Do not modify a Record after handing out a copy to it. Use
+ Record.Clone to create a copy with no shared state.
+
+func NewRecord(t time.Time, level Level, msg string, calldepth int, ctx context.Context) Record
+ NewRecord creates a Record from the given arguments. Use Record.AddAttrs
+ to add attributes to the Record. If calldepth is greater than zero,
+ Record.SourceLine will return the file and line number at that depth,
+ where 1 means the caller of NewRecord.
+
+ NewRecord is intended for logging APIs that want to support a Handler as a
+ backend.
+
+func (r *Record) AddAttrs(attrs ...Attr)
+ AddAttrs appends the given attrs to the Record's list of Attrs.
+
+func (r Record) Attrs(f func(Attr))
+ Attrs calls f on each Attr in the Record.
+
+func (r Record) Clone() Record
+ Clone returns a copy of the record with no shared state. The original record
+ and the clone can both be modified without interfering with each other.
+
+func (r Record) NumAttrs() int
+ NumAttrs returns the number of attributes in the Record.
+
+func (r Record) SourceLine() (file string, line int)
+ SourceLine returns the file and line of the log event. If the Record
+ was created without the necessary information, or if the location is
+ unavailable, it returns ("", 0).
+
+type TextHandler struct {
+ // Has unexported fields.
+}
+ TextHandler is a Handler that writes Records to an io.Writer as a sequence
+ of key=value pairs separated by spaces and followed by a newline.
+
+func NewTextHandler(w io.Writer) *TextHandler
+ NewTextHandler creates a TextHandler that writes to w, using the default
+ options.
+
+func (h *TextHandler) Enabled(level Level) bool
+ Enabled reports whether the handler handles records at the given level.
+ The handler ignores records whose level is lower.
+
+func (h *TextHandler) Handle(r Record) error
+ Handle formats its argument Record as a single line of space-separated
+ key=value items.
+
+ If the Record's time is zero, the time is omitted. Otherwise, the key is
+ "time" and the value is output in RFC3339 format with millisecond precision.
+
+ If the Record's level is zero, the level is omitted. Otherwise, the key is
+ "level" and the value of Level.String is output.
+
+ If the AddSource option is set and source information is available, the key
+ is "source" and the value is output as FILE:LINE.
+
+ The message's key "msg".
+
+ To modify these or other attributes, or remove them from the output,
+ use [HandlerOptions.ReplaceAttr].
+
+ If a value implements encoding.TextMarshaler, the result of MarshalText is
+ written. Otherwise, the result of fmt.Sprint is written.
+
+ Keys and values are quoted with strconv.Quote if they contain Unicode space
+ characters, non-printing characters, '"' or '='.
+
+ Keys inside groups consist of components (keys or group names) separated by
+ dots. No further escaping is performed. If it is necessary to reconstruct
+ the group structure of a key even in the presence of dots inside components,
+ use [HandlerOptions.ReplaceAttr] to escape the keys.
+
+ Each call to Handle results in a single serialized call to io.Writer.Write.
+
+func (h *TextHandler) WithAttrs(attrs []Attr) Handler
+ With returns a new TextHandler whose attributes consists of h's attributes
+ followed by attrs.
+
+func (h *TextHandler) WithGroup(name string) Handler
+
+type Value struct {
+ // Has unexported fields.
+}
+ A Value can represent any Go value, but unlike type any, it can represent
+ most small values without an allocation. The zero Value corresponds to nil.
+
+func AnyValue(v any) Value
+ AnyValue returns a Value for the supplied value.
+
+ Given a value of one of Go's predeclared string, bool, or (non-complex)
+ numeric types, AnyValue returns a Value of kind String, Bool, Uint64, Int64,
+ or Float64. The width of the original numeric type is not preserved.
+
+ Given a time.Time or time.Duration value, AnyValue returns a Value of kind
+ TimeKind or DurationKind. The monotonic time is not preserved.
+
+ For nil, or values of all other types, including named types whose
+ underlying type is numeric, AnyValue returns a value of kind AnyKind.
+
+func BoolValue(v bool) Value
+ BoolValue returns a Value for a bool.
+
+func DurationValue(v time.Duration) Value
+ DurationValue returns a Value for a time.Duration.
+
+func Float64Value(v float64) Value
+ Float64Value returns a Value for a floating-point number.
+
+func GroupValue(as ...Attr) Value
+ GroupValue returns a new Value for a list of Attrs. The caller must not
+ subsequently mutate the argument slice.
+
+func Int64Value(v int64) Value
+ Int64Value returns a Value for an int64.
+
+func IntValue(v int) Value
+ IntValue returns a Value for an int.
+
+func StringValue(value string) Value
+ String returns a new Value for a string.
+
+func TimeValue(v time.Time) Value
+ TimeValue returns a Value for a time.Time. It discards the monotonic
+ portion.
+
+func Uint64Value(v uint64) Value
+ Uint64Value returns a Value for a uint64.
+
+func (v Value) Any() any
+ Any returns v's value as an any.
+
+func (v Value) Bool() bool
+ Bool returns v's value as a bool. It panics if v is not a bool.
+
+func (a Value) Duration() time.Duration
+ Duration returns v's value as a time.Duration. It panics if v is not a
+ time.Duration.
+
+func (v Value) Equal(w Value) bool
+ Equal reports whether v and w have equal keys and values.
+
+func (v Value) Float64() float64
+ Float64 returns v's value as a float64. It panics if v is not a float64.
+
+func (v Value) Group() []Attr
+ Group returns v's value as a []Attr. It panics if v's Kind is not GroupKind.
+
+func (v Value) Int64() int64
+ Int64 returns v's value as an int64. It panics if v is not a signed integer.
+
+func (v Value) Kind() Kind
+ Kind returns v's Kind.
+
+func (v Value) LogValuer() LogValuer
+ LogValuer returns v's value as a LogValuer. It panics if v is not a
+ LogValuer.
+
+func (v Value) Resolve() Value
+ Resolve repeatedly calls LogValue on v while it implements LogValuer, and
+ returns the result. If the number of LogValue calls exceeds a threshold, a
+ Value containing an error is returned. Resolve's return value is guaranteed
+ not to be of Kind LogValuerKind.
+
+func (v Value) String() string
+ String returns Value's value as a string, formatted like fmt.Sprint.
+ Unlike the methods Int64, Float64, and so on, which panic if v is of the
+ wrong kind, String never panics.
+
+func (v Value) Time() time.Time
+ Time returns v's value as a time.Time. It panics if v is not a time.Time.
+
+func (v Value) Uint64() uint64
+ Uint64 returns v's value as a uint64. It panics if v is not an unsigned
+ integer.
+```