An encoder for Uber's zap logger that makes complex structured log output easily readable by humans. It prioritises displaying information in a clean and easy-to-understand way.
You get good-looking, colourised output like this:
When your log output contains structured data, it looks like this:
I personally feel the above is much more readable than the default "development" logger that comes with zap, which looks like this:
The above example was generated using:
package main
import (
"go.uber.org/zap"
"github.com/thessem/zap-prettyconsole"
)
func main() {
// Instead of zap.NewDevelopment()
logger := prettyconsole.NewLogger(zap.DebugLevel)
// I normally use the un-sugared logger. I like the type-safety.
// You can use logger.Sugar() if you prefer though!
logger.Info("doesn't this look nice",
zap.Complex64("nice_level", 12i-14),
zap.Time("the_time", time.Now()),
zap.Bools("nice_enough", []bool{true, false}),
zap.Namespace("an_object"),
zap.String("field_1", "value_1"),
zap.String("field_2", "value_2"),
)
}
This is intended as a tool for local development, and not for running in production. In production I reccomend you use zap's built in JSON mode. Your logs in production will be getting parsed by computers, not humans, after-all. Take a look at the zap advanced configuration example to configure zap to output "human" output locally, and "machine" output in production.
This package takes particular care to represent structural information with indents and newlines (slightly YAML style), hopefully making it easy to figure out what each key-value belongs to: which is the output of
type User struct {
Name string
Age int
Address UserAddress
Friend *User
}
type UserAddress struct {
Street string
City string
}
func (u *User) MarshalLogObject(e zapcore.ObjectEncoder) error {
e.AddString("name", u.Name)
e.AddInt("age", u.Age)
e.OpenNamespace("address")
e.AddString("street", u.Address.Street)
e.AddString("city", u.Address.City)
if u.Friend != nil {
_ = e.AddObject("friend", u.Friend)
}
return nil
}
func main() {
logger, _ := prettyconsole.NewConfig().Build()
sugarLogger := logger.Sugar()
u := &User{
Name: "Big Bird",
Age: 18,
Address: UserAddress{
Street: "Sesame Street",
City: "New York",
},
Friend: &User{
Name: "Oscar the Grouch",
Age: 31,
Address: UserAddress{
Street: "Wallaby Way",
City: "Sydney",
},
},
}
sugarLogger.Infow("Asking a Question",
"question", "how do you get to sesame street?",
"answer", "unsatisfying",
"user", u,
)
}
This encoder was inspired by trying to parse multiple github.com/pkg/errors/
errors, each with their own stacktraces.
I am a big fan of error wrapping and error stacktraces, I am not a fan of needing to copy text out of my terminal to see what happened.
When objects that do not satisfy ObjectMarshaler
are logged, zap-prettyconsole will use reflection (via the delightful dd library) to print it instead:
Strings passed to the logger will have their formatting printed and colourised, but you can opt out of this and print the raw strings.
Generated using:
logger := prettyconsole.NewLogger(zap.DebugLevel)
// Non-sugared version
logger = logger.With(prettyconsole.FormattedString("sql", "SELECT * FROM\n\tusers\nWHERE\n\tname = 'James'"))
sugar := logger.Sugar()
mdb := "db.users.find({\n\tname: \"\x1b[31mJames\x1b[0m\"\n});"
sugar.Debugw("string formatting",
zap.Namespace("mdb"),
// Sugared version
"formatted", prettyconsole.FormattedStringValue(mdb),
"unformatted", mdb,
)
This encoder respects all the normal encoder configuration settings. You can change your separator character, newline characters, add caller/function information and add stacktraces if you like.
Whilst this library is described as "development mode" it is still coded to be as performant as possible, saving your CPU cycles for running lots of IDE plugins.
The main performance overhead introduced with this encoder is because of the stable field ordering, we sort every structured log field alphabetically. Although the relative overhead is high, the absolute overhead is still quite small, and probably wont matter for development logging anyway!
Log a message and 10 fields:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 529 ns/op | +0% | 5 allocs/op |
⚡ zap (sugared) | 858 ns/op | +62% | 10 allocs/op |
⚡ 💅 zap-prettyconsole | 1904 ns/op | +260% | 12 allocs/op |
⚡ 💅 zap-prettyconsole (sugared) | 2372 ns/op | +348% | 17 allocs/op |
Log a message with a logger that already has 10 fields of context:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 52 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 64 ns/op | +23% | 1 allocs/op |
⚡ 💅 zap-prettyconsole | 1565 ns/op | +2910% | 7 allocs/op |
⚡ 💅 zap-prettyconsole (sugared) | 1637 ns/op | +3048% | 8 allocs/op |
Log a static string, without any context or printf
-style templating:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ 💅 zap-prettyconsole | 36 ns/op | -16% | 0 allocs/op |
⚡ zap | 43 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 58 ns/op | +35% | 1 allocs/op |
⚡ 💅 zap-prettyconsole (sugared) | 62 ns/op | +44% | 1 allocs/op |
Released under the MIT License