_content/blog/slog.md: blog post for log/slog

Change-Id: Ia4c4a4f0ea5e635393e205874aad62de14811eec
Reviewed-on: https://go-review.googlesource.com/c/website/+/518017
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Seth Vargo <sethvargo@google.com>
Reviewed-by: Eli Bendersky <eliben@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Jonathan Amsterdam 2023-08-09 17:32:51 -04:00 коммит произвёл Eli Bendersky
Родитель 07cc88f98d
Коммит 5a066bd7e3
1 изменённых файлов: 282 добавлений и 0 удалений

282
_content/blog/slog.md Normal file
Просмотреть файл

@ -0,0 +1,282 @@
---
title: Structured Logging with slog
date: 2023-08-22
by:
- Jonathan Amsterdam
summary: The Go 1.21 standard library includes a new structured logging package, log/slog.
---
The new `log/slog` package in Go 1.21 brings structured logging to the standard
library. Structured logs use key-value pairs so they can be parsed, filtered,
searched, and analyzed quickly and reliably.
For servers, logging is an important 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.
The standard library has had a logging package, `log`,
since Go's initial release over a decade ago.
Over time,
we've learned that structured logging is important to Go programmers. It has
consistently ranked high in our annual survey, and many packages in the Go
ecosystem provide it. Some of these are quite popular: one of the first structured
logging packages for Go, [logrus](https://pkg.go.dev/github.com/sirupsen/logrus),
is used in over 100,000 other packages.
With many structured logging packages to choose from, large programs
will often end up including more than one through their dependencies. The main program
might have to configure each of these logging packages so that the log output is
consistent: it all goes to the same place, in the same format. By including
structured logging in the standard library, we can provide a common framework
that all the other structured logging packages can share.
## A tour of `slog`
Here is the simplest program that uses `slog`:
```
package main
import "log/slog"
func main() {
slog.Info("hello, world")
}
```
As of this writing, it prints:
2023/08/04 16:09:19 INFO hello, world
The `Info` function prints a message at the Info log level using the
default logger, which in this case is the default logger from the `log`
package---the same logger you get when you write `log.Printf`.
That explains why the output looks so similar: only the "INFO" is new.
Out of the box, `slog` and the original `log` package work together to make it
easy to get started.
Besides `Info`, there are functions for three other levels---`Debug`, `Warn`, and
`Error`---as well as a more general `Log` function that takes the level as an
argument. In `slog`, levels are just integers, so you aren't limited to the four
named levels. For example, `Info` is zero and `Warn` is 4, so if your logging system
has a level in between those, you can use 2 for it.
Unlike with the `log` package, we can easily add key-value pairs to our output
by writing them after the message:
```
slog.Info("hello, world", "user", os.Getenv("USER"))
```
The output now looks like this:
2023/08/04 16:27:19 INFO hello, world user=jba
As we mentioned, `slog`'s top-level functions use the default logger.
We can get this logger explicitly, and call its methods:
```
logger := slog.Default()
logger.Info("hello, world", "user", os.Getenv("USER"))
```
Every top-level function corresponds to a method on a `slog.Logger`.
The output is the same as before.
Initially, slog's output goes through the default `log.Logger`, producing the
output we've seen above.
We can change the output by changing the _handler_ used by the logger.
`slog` comes with two built-in handlers.
A `TextHandler` emits all log information in the form `key=value`.
This program creates a new logger using a `TextHandler` and
makes the same call to the `Info` method:
```
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
logger.Info("hello, world", "user", os.Getenv("USER"))
```
Now the output looks like this:
time=2023-08-04T16:56:03.786-04:00 level=INFO msg="hello, world" user=jba
Everything has been turned into a key-value pair, with strings quoted as needed to preserve structure.
For JSON output, install the built-in `JSONHandler` instead:
```
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("hello, world", "user", os.Getenv("USER"))
```
Now our output is a sequence of JSON objects, one per logging call:
{"time":"2023-08-04T16:58:02.939245411-04:00","level":"INFO","msg":"hello, world","user":"jba"}
You are not limited to the built-in handlers. Anyone can write a handler by
implementing the `slog.Handler` interface. A handler can generate output in a
particular format, or it can wrap another handler to add functionality.
One of the
[examples](https://pkg.go.dev/log/slog@master#example-Handler-LevelHandler) in
the `slog` documentation shows how to write a wrapping handler that changes the minimum
level at which log messages will be displayed.
The alternating key-value syntax for attributes that we've been using so far is convenient, but for frequently executed log statements it may be more efficient to use the `Attr` type and call the `LogAttrs` method.
These work together to minimize memory allocations.
There are functions for building `Attr`s out of strings, numbers, and other common
types. This call to `LogAttrs` produces the same output as above, but does it faster:
```
slog.LogAttrs(context.Background(), slog.LevelInfo, "hello, world",
slog.String("user", os.Getenv("USER")))
```
There is a lot more to `slog`:
- As the call to `LogAttrs` shows, you can pass a `context.Context` to some log functions
so a handler can extract context information like trace IDs. (Canceling the context does not prevent the log entry from being written.)
- You can call `Logger.With` to add attributes to a logger that will appear in
all of its output, effectively factoring out the common parts of several log
statements. This is not only convenient, but it can also help performance, as discussed below.
- Attributes can be combined into groups. This can add more structure to your
log output and can help to disambiguate keys that would otherwise be identical.
- You can control how a value appears in the logs by providing its type with a `LogValue`
method. That can be used to [log the fields of a struct as a
group](https://pkg.go.dev/log/slog@master#example-LogValuer-Group) or [redact
sensitive data](https://pkg.go.dev/log/slog@master#example-LogValuer-Secret),
among other things.
The best place to learn about all of `slog` is the [package
documentation](https://pkg.dev.go/log/slog).
## Performance
We wanted `slog` to be fast.
For large-scale performance gains, we designed [the `Handler`
interface](https://pkg.go.dev/log/slog#Handler) to provide optimization
opportunities. The `Enabled` method is called at the beginning of every log
event, giving the handler a chance to drop unwanted log events quickly. The
`WithAttrs` and `WithGroup` methods let the handler format
attributes added by `Logger.With` once, rather than at each logging call. This
pre-formatting can provide a significant speedup when large attributes, like an
`http.Request`, are added to a `Logger` and then used in many logging calls.
To inform our performance optimization work, we investigated typical patterns of logging in existing open-source projects. We found that over 95%
of calls to logging methods pass five or fewer attributes. We also categorized
the types of attributes, finding that a handful of common types accounted for
the majority.
We then wrote benchmarks that captured the common cases, and used them as a
guide to see where the time went.
The greatest gains came from paying careful attention to memory allocation.
## The design process
The `slog` package is one of the largest additions to the standard library since
Go 1 was released in 2012. We wanted to take our time designing it, and we knew
that community feedback would be essential.
By April 2022, we had gathered enough data to demonstrate the importance of
structured logging to the Go community. The Go team decided to explore adding it
to the standard library.
We began by looking at how the existing structured logging packages were
designed. We also took advantage of the large collection of open-source Go code
stored on the Go module proxy to learn how these packages were actually used.
Our first design was informed by this research as well as Go's spirit of
simplicity.
We wanted an API that is light on the page and easy to understand, without
sacrificing performance.
It was never a goal to replace existing third-party logging packages.
They are all good at what they do, and replacing existing code that works well
is rarely a good use of a developer's time.
We divided the API into a frontend, `Logger`, that calls a backend interface, `Handler`.
That way, existing logging packages can talk to a common backend,
so the packages that use them can interoperate without having to be rewritten.
Handlers are written or in progress for many common logging
packages, including
[Zap](https://github.com/uber-go/zap/tree/master/exp/zapslog),
[logr](https://github.com/go-logr/logr/pull/196)
and [hclog](https://github.com/evanphx/go-hclog-slog).
We shared our initial design within the Go team and other developers who had
extensive logging experience. We made alterations based on their feedback, and
by August of 2022 we felt we had a workable design. On August 29,
we made our [experimental
implementation](https://github.com/golang/exp/tree/master/slog) public
and began a [GitHub discussion](https://github.com/golang/go/discussions/54763) to hear
what the community had to say.
The response was enthusiastic and largely positive.
Thanks to insightful comments from the designers and users of other structured
logging packages, we made several changes and added a few features, like groups
and the `LogValuer` interface. We changed the mapping from log levels to
integers twice.
After two months and about 300 comments, we felt we were ready for an actual
[proposal](https://go.dev/issue/56345) and accompanying [design
doc](https://go.googlesource.com/proposal/+/03441cb358c7b27a8443bca839e5d7a314677ea6/design/56345-structured-logging.md).
The proposal issue garnered over 800 comments and resulted in many improvements to
the API and the implementation. Here are two examples of API changes, both
concerning `context.Context`:
1. Originally the API supported adding loggers to a
context. Many felt that this was a convenient way to plumb a logger easily
through levels of code that didn't care about it. But others felt it was
smuggling in an implicit dependency, making the code harder to understand.
Ultimately, we removed the feature as being too controversial.
2. We also wrestled
with the related question of passing a context to logging methods, trying a
number of designs. We initially resisted the standard pattern of passing the
context as the first argument because we didn't want every logging call to
require a context, but ultimately created two sets of logging methods, one
with a context and one without.
One change we did not make concerned the alternating key-and-value syntax for
expressing attributes:
slog.Info("message", "k1", v1, "k2", v2)
Many felt strongly that this was a bad idea. They found it hard to read
and easy to get wrong by omitting a key or value. They preferred explicit
attributes for expressing structure:
slog.Info("message", slog.Int("k1", v1), slog.String("k2", v2))
But we felt that the lighter syntax was important to keeping Go easy and fun to
use, especially for new Go programmers. We also knew that several Go logging
packages, like `logr`, `go-kit/log` and `zap` (with its `SugaredLogger`)
successfully used alternating keys and
values. We added a [vet
check](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog)
to catch common mistakes, but did not change the design.
On March 15, 2023, the proposal was accepted, but there were still some minor
unresolved issues. Over the next few weeks, ten additional changes were proposed
and resolved. By early July, the `log/slog` package implementation was complete,
along with the `testing/slogtest` package for verifying
handlers and the vet check for correct usage of alternating keys and values.
And on August 8, Go 1.21 was released, and `slog` with it.
We hope you find it useful, and as fun to use as it was to build.
And a big thanks to everyone who participated in the discussion and the proposal
process. Your contributions improved `slog` immensely.
## Resources
The [documentation](https://pkg.go.dev/log/slog) for the `log/slog` package
explains how to use it and provides several examples.
The [wiki page](https://github.com/golang/go/wiki/Resources-for-slog) has
additional resources provided by the Go community, including a variety of
handlers.
If you want to write a handler, consult the
[handler writing
guide](https://github.com/golang/example/blob/master/slog-handler-guide/README.md).