slog
packagemoduleThis package is not in the latest version of its module.
Details
Validgo.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used, modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
- Learn more about best practices
Repository
Links
README¶
slog
slog is a minimal structured logging library for Go.
Install
go get cdr.dev/slogFeatures
- Minimal API
- First classcontext.Context support
- First classtesting.TB support
- Packageslogtest/assert provides test assertion helpers
- Beautiful human readable logging output
- Prints multiline fields and errors nicely
- Machine readable JSON output
- GCP Stackdriver support
- Stdlib log adapter
- Skip caller frames withslog.Helper
- Encodes values as if with
json.Marshal - Transparently logopencensus trace and span IDs
- Single dependency on go.opencensus.io
- Log to multiple sinks
Example
Many more examples available atgodoc.
log := slog.Make(sloghuman.Sink(os.Stdout))log.Info(context.Background(), "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), slog.Error( xerrors.Errorf("wrap1: %w", xerrors.Errorf("wrap2: %w", io.EOF, ), ), ),)
Why?
AtCoder we’ve used Uber’szap for several years.It is a fantastic library for performance. Thanks Uber!
However we felt the API and developer experience could be improved.
Here is a list of reasons how we improved on zap with slog.
sloghas a minimal API surfacesloghas a concise semi typed API- We found zap's fully typed API cumbersome. It does offer asugared APIbut it's too easy to pass an invalid fields list since there is no static type checking.Furthermore, it's harder to read as there is no syntax grouping for each key value pair.
- We wanted an API that only accepted the equivalent ofzap.Anyfor every field. This isslog.F.
sloghumanuses a very human readable format- It colors distinct parts of each line to make it easier to scan logs. Even the JSON that representsthe fields in each log is syntax highlighted so that is very easy to scan. See the screenshot above.
- zap lacks appropriate colors for different levels and fields.
- slog automatically prints one multiline field after the log to make errors and such much easier to read.
- zap logs multiline fields and errors stack traces as JSON strings which made them unreadable in a terminal.
- When logging to JSON, slog automatically converts a
golang.org/x/xerrorschaininto an array with fields for the location and wrapping messages.
- It colors distinct parts of each line to make it easier to scan logs. Even the JSON that representsthe fields in each log is syntax highlighted so that is very easy to scan. See the screenshot above.
Fullcontext.Context support
sloglets you set fields in acontext.Contextsuch that any log with the context prints those fields.- We wanted to be able to pull up all relevant logs for a given trace, user or request. With zap, we were pluggingthese fields in for every relevant log or passing around a logger with the fields set. This became very verbose.
Simple and easy to extend
- A new backend only has to implement the simple Sink interface.
- The Logger type provides a nice API around Sink but also implementsSink to allow for composition.
- zap is hard and confusing to extend. There are too many structures and configuration options.
Structured logging of Go structures with
json.Marshal- Entire encoding process is documented ongodoc.
- With zap, We found ourselves often implementing zap'sObjectMarshaler to log Go structures. This wasverbose and most of the time we ended up only implementing
fmt.Stringerand usingzap.Stringerinstead.
slog takes inspiration from Go's stdlib and implements
slog.Helperwhich works just liket.Helper- It marks the calling function as a helper and skips it when reporting location info.
- We had many helper functions for logging but we wanted the line reported to be of the parent function.zap has anAPI for this but it's verbose and requirespassing the logger around explicitly.
Tight integration with stdlib's
testingpackage- You can configure
slogtestto exit on any ERROR logsand it has a global stateless API that takes atesting.TBso you do not need to create a logger first. - Test assertion helpers are provided inslogtest/assert.
- zap haszaptest but the API surface is large and doesn'tintegrate well. It does not support any of the features described above.
- You can configure
Documentation¶
Overview¶
Package slog implements minimal structured logging.
Seehttps://cdr.dev/slog for overview docs and a comparison with existing libraries.
The examples are the best way to understand how to use this library effectively.
The Logger type implements a high level API around the Sink interface.Logger implements Sink as well to allow composition.
Implementations of the Sink interface are available in the sloggers subdirectory.
Example¶
package mainimport ("context""io""os""time""golang.org/x/xerrors""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func main() {log := slog.Make(sloghuman.Sink(os.Stdout))log.Info(context.Background(), "my message here",slog.F("field_name", "something or the other"),slog.F("some_map", slog.M(slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)),)),slog.Error(xerrors.Errorf("wrap1: %w",xerrors.Errorf("wrap2: %w",io.EOF,),),),)// 2019-12-09 05:04:53.398 [INFO]<example.go:16>my message here{"field_name": "something or the other", "some_map": {"nested_fields": "2000-02-05T04:04:04Z"}} ...// "error": wrap1:// main.main// /Users/nhooyr/src/cdr/scratch/example.go:22// - wrap2:// main.main// /Users/nhooyr/src/cdr/scratch/example.go:23// - EOF}Example (Marshaller)¶
package mainimport ("context""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")type myStruct struct {foo intbar int}func (s myStruct) MarshalJSON() ([]byte, error) {return slog.M(slog.F("foo", s.foo),slog.F("bar", s.foo),).MarshalJSON()}func main() {l := slog.Make(sloghuman.Sink(os.Stdout))l.Info(context.Background(), "wow",slog.F("myStruct", myStruct{foo: 1,bar: 2,}),)// 2019-12-16 17:31:37.120 [INFO]<example_marshaller_test.go:26>wow{"myStruct": {"foo": 1, "bar": 1}}}Example (Multiple)¶
package mainimport ("context""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman""cdr.dev/slog/sloggers/slogstackdriver")func main() {l := slog.Make(sloghuman.Sink(os.Stdout))f, err := os.OpenFile("stackdriver", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644)if err != nil {l.Fatal(context.Background(), "failed to open stackdriver log file", slog.Error(err))}l = l.AppendSinks(slogstackdriver.Sink(f))l.Info(context.Background(), "log to stdout and stackdriver")// 2019-12-07 20:59:55.790 [INFO]log to stdout and stackdriver}Example (Struct)¶
package mainimport ("context""os""time""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func main() {l := slog.Make(sloghuman.Sink(os.Stdout))type hello struct {Meow int `json:"meow"`Bar string `json:"bar"`M time.Time `json:"m"`}l.Info(context.Background(), "check out my structure",slog.F("hello", hello{Meow: 1,Bar: "barbar",M: time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC),}),)// 2019-12-16 17:31:51.769 [INFO]<example_test.go:56>check out my structure{"hello": {"meow": 1, "bar": "barbar", "m": "2000-02-05T04:04:04Z"}}}Example (Testing)¶
package mainimport ("testing""cdr.dev/slog""cdr.dev/slog/sloggers/slogtest")func main() {// Provided by the testing package in tests.var t testing.TBslogtest.Info(t, "my message here",slog.F("field_name", "something or the other"),)// t.go:55: 2019-12-05 21:20:31.218 [INFO]my message herefield_name="something or the other"}Index¶
- func Helper()
- func Stdlib(ctx context.Context, l Logger, level Level) *log.Logger
- func With(ctx context.Context, fields ...Field) context.Context
- type Field
- type Level
- type Logger
- func (l Logger) AppendSinks(s ...Sink) Logger
- func (l Logger) Critical(ctx context.Context, msg string, fields ...any)
- func (l Logger) Debug(ctx context.Context, msg string, fields ...any)
- func (l Logger) Error(ctx context.Context, msg string, fields ...any)
- func (l Logger) Fatal(ctx context.Context, msg string, fields ...any)
- func (l Logger) Info(ctx context.Context, msg string, fields ...any)
- func (l Logger) Leveled(level Level) Logger
- func (l Logger) Log(ctx context.Context, e SinkEntry)
- func (l Logger) Named(name string) Logger
- func (l Logger) Sync()
- func (l Logger) Warn(ctx context.Context, msg string, fields ...any)
- func (l Logger) With(fields ...Field) Logger
- type Map
- type Sink
- type SinkEntry
Examples¶
Constants¶
This section is empty.
Variables¶
This section is empty.
Functions¶
funcHelper¶
func Helper()
Helper marks the calling function as a helperand skips it for source location information.It's the slog equivalent of testing.TB.Helper().
Example¶
package mainimport ("context""net/http""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func httpLogHelper(ctx context.Context, status int) {slog.Helper()l.Info(ctx, "sending HTTP response",slog.F("status", status),)}var l = slog.Make(sloghuman.Sink(os.Stdout))func main() {ctx := context.Background()httpLogHelper(ctx, http.StatusBadGateway)// 2019-12-07 21:18:42.567 [INFO]<example_helper_test.go:24>sending HTTP response{"status": 502}}funcStdlib¶
Stdlib creates a standard library logger from the given logger.
All logs will be logged at the level set by the logger and thegiven ctx will be passed to the logger's Log method, therebylogging all fields and tracing info in the context.
You can redirect the stdlib default logger with log.SetOutputto the Writer on the logger returned by this function.See the example.
Example¶
package mainimport ("context""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func main() {ctx := slog.With(context.Background(), slog.F("field", 1))l := slog.Stdlib(ctx, slog.Make(sloghuman.Sink(os.Stdout)), slog.LevelInfo)l.Print("msg")// 2019-12-07 20:54:23.986 [INFO](stdlib)msgfield=1}funcWith¶
With returns a context that contains the given fields.
Any logs written with the provided context will have the given logs prepended.
It will append to any fields already in ctx.
Example¶
package mainimport ("context""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func main() {ctx := slog.With(context.Background(), slog.F("field", 1))l := slog.Make(sloghuman.Sink(os.Stdout))l.Info(ctx, "msg")// 2019-12-07 20:54:23.986 [INFO]msgfield=1}}Types¶
typeLevel¶
type Levelint
Level represents a log level.
const (// LevelDebug is used for development and debugging messages.LevelDebugLevel =iota// LevelInfo is used for normal informational messages.LevelInfo// LevelWarn is used when something has possibly gone wrong.LevelWarn// LevelError is used when something has certainly gone wrong.LevelError// LevelCritical is used when when something has gone wrong and should// be immediately investigated.LevelCritical// LevelFatal is used when the process is about to exit due to an error.LevelFatal)
The supported log levels.
The default level is Info.
typeLogger¶
type Logger struct {// contains filtered or unexported fields}Logger wraps Sink with a nice API to log entries.
Logger is safe for concurrent use.
func (Logger)AppendSinks¶added inv1.4.0
AppendSinks appends the sinks to the set sinktargets on the logger.
func (Logger)Critical¶
Critical logs the msg and fields at LevelCritical.See Info() for information on the fields argument.
It will then Sync().
func (Logger)Debug¶
Debug logs the msg and fields at LevelDebug.See Info for information on the fields argument.
func (Logger)Error¶
Error logs the msg and fields at LevelError.See Info() for information on the fields argument.
It will then Sync().
func (Logger)Fatal¶
Fatal logs the msg and fields at LevelFatal.See Info() for information on the fields argument.
It will then Sync() and os.Exit(1).
func (Logger)Info¶
Info logs the msg and fields at LevelInfo.Fields may contain any combination of key value pairs, Field, and Map.For example:
log.Info(ctx, "something happened", "user", "alex", slog.F("age", 20))is equivalent to:
log.Info(ctx, "something happened", slog.F("user", "alex"), slog.F("age", 20))is equivalent to:
log.Info(ctx, "something happened", slog.M(slog.F("user", "alex"),slog.F("age", 20),))is equivalent to:
log.Info(ctx, "something happened", "user", "alex", "age", 20)
In general, prefer using key value pairs over Field and Map, as that is howthe standard library's slog package works.
func (Logger)Leveled¶added inv0.5.0
Leveled returns a Logger that only logs entriesequal to or above the given level.
Example¶
package mainimport ("context""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func main() {ctx := context.Background()l := slog.Make(sloghuman.Sink(os.Stdout))l.Debug(ctx, "testing1")l.Info(ctx, "received request")l = l.Leveled(slog.LevelDebug)l.Debug(ctx, "testing2")// 2019-12-07 21:26:20.945 [INFO]received request// 2019-12-07 21:26:20.945 [DEBU]testing2}func (Logger)Log¶added inv1.4.0
Log logs the given entry with the context to theunderlying sinks.
It extends the entry with the set fields and names.
func (Logger)Named¶
Named appends the name to the set nameson the logger.
Example¶
package mainimport ("context""net""os""cdr.dev/slog""cdr.dev/slog/sloggers/sloghuman")func main() {ctx := context.Background()l := slog.Make(sloghuman.Sink(os.Stdout))l = l.Named("http")l.Info(ctx, "received request", slog.F("remote address", net.IPv4(127, 0, 0, 1)))// 2019-12-07 21:20:56.974 [INFO](http)received requestremote_address=127.0.0.1}}typeMap¶
type Map []Field
Map represents an ordered map of fields.
func (Map)MarshalJSON¶
MarshalJSON implements json.Marshaler.
It is guaranteed to return a nil error.Any error marshalling a field will become the field's value.
Every field value is encoded with the following process:
1. json.Marshaller is handled.
2. xerrors.Formatter is handled.
3. structs that have a field with a json tag are encoded with json.Marshal.
4. error and fmt.Stringer is handled.
5. slices and arrays go through the encode function for every element.
6. For values that cannot be encoded with json.Marshal, fmt.Sprintf("%+v") is used.
7. json.Marshal(v) is used for all other values.
Directories¶
| Path | Synopsis |
|---|---|
internal | |
assert Package assert contains helpers for test assertions. | Package assert contains helpers for test assertions. |
entryhuman Package entryhuman contains the code to format slog.SinkEntry for humans. | Package entryhuman contains the code to format slog.SinkEntry for humans. |
syncwriter Package syncwriter implements a concurrency safe io.Writer wrapper. | Package syncwriter implements a concurrency safe io.Writer wrapper. |
sloggers | |
sloghuman Package sloghuman contains the slogger that writes logs in a human readable format. | Package sloghuman contains the slogger that writes logs in a human readable format. |
slogjson Package slogjson contains the slogger that writes logs in JSON. | Package slogjson contains the slogger that writes logs in JSON. |
slogstackdriver Package slogstackdriver contains the slogger for google cloud's stackdriver. | Package slogstackdriver contains the slogger for google cloud's stackdriver. |
slogtest Package slogtest contains the slogger for use with Go's testing package. | Package slogtest contains the slogger for use with Go's testing package. |
slogtest/assert Package assert is a helper package for test assertions. | Package assert is a helper package for test assertions. |