I've used strucutred logging approach in Python (with structlog library).
Now looking for same options but in Golang.
Candidates are logxi, log15 and logrus.
IMO log15 has most idiomatic API while logxi is most convenient (due to stack context info)
To build and run I'm using following:
$ go build && LOGXI=* ./go_logging
All libraries are uing interfaces, so rendering could be customized
- logxi has Formatter interface
- log15 has Format interface
- logrus has Formatter interface
logxi with default settings show no context for INF, short context for WARN and full context for ERR.
Other tow libraries has more standard/boring defaults.
18:55:57.586685 INF ~ logxi info key1: value1 key2: 2
18:55:57.586937 WRN ~ logxi warn key1: value1 key2: 2
in: sampleBar(go_logs.go:36)
18:55:57.588004 ERR ~ logxi error key1: value1 key2: 2
in sampleBar(go_logs.go:37)
35: logxi.Info("logxi info", "key1", "value1", "key2", two)
36: logxi.Warn("logxi warn", "key1", "value1", "key2", two)
37: logxi.Error("logxi error", "key1", "value1", "key2", two)
38:
39: logger := log15.New("key1", "value1", "key2", two)
in sampleFoo(go_logs.go:29)
27:
28: func sampleFoo() {
29: sampleBar()
30: }
31:
in main(go_logs.go:25)
23: func main() {
24: //logger := logxi.NewLogger(os.Stdout, "logxi logger")
25: sampleFoo()
26: }
27:
INFO[03-20|19:12:17] log15 info context1=context1 context2=2 key3=value3 key4=four
WARN[03-20|19:12:17] log15 warn context1=context1 context2=2 key3=value3 key4=four
EROR[03-20|19:12:17] log15 err context1=context1 context2=2 key3=value3 key4=four
INFO[03-20|19:12:17] log15 info context1=context1 context2=2 context3=true context4=0.010 key3=value3 key4=four
INFO[0000] logrus info key1=value1 key2=2
WARN[0000] logrus warn key1=value1 key2=2
ERRO[0000] logrus error key1=value1 key2=2
Here is how JSON formatter looks like:
logxi
{
"_t": "18:21:38.001590",
"_l": "INF",
"_n": "logxi logger",
"_m": "logxi info",
"key1": "value1",
"key2": 2
}
log15
{
"context1": "context1",
"context2": 2,
"context3": "true",
"context4": 0.01,
"key3": "value3",
"key4": "four",
"lvl": 3,
"msg": "log15 info",
"t": "2015-03-20T19:13:05.675154775+02:00"
}
logrus
{
"key1": "value1",
"key2": 2,
"level": "info",
"msg": "logrus info",
"time": "2015-03-20T18:42:52+02:00"
}
Source code
package main
import (
//"os"
logrus "github.com/Sirupsen/logrus"
log15 "gopkg.in/inconshreveable/log15.v2"
// NOTE logxi works with go1.4 but failed with go1.2.1
logxi "github.com/mgutz/logxi/v1"
)
func main() {
sampleFoo()
}
func sampleFoo() {
sampleBar()
}
func sampleBar() {
two := 2
// Use env variables: LOGXI=* LOGXI_FORMAT=JSON
// Pass logger name into constructor
logger1 := logxi.New("logxi logger")
logger1.Info("logxi info", "key1", "value1", "key2", two)
logger1.Warn("logxi warn", "key1", "value1", "key2", two)
logger1.Error("logxi error", "key1", "value1", "key2", two)
// Pass some context variables into "root" logger
rootLogger := log15.New("context1", "context1", "context2", two)
//rootLogger.SetHandler(log15.StreamHandler(os.Stdout, log15.JsonFormat()))
rootLogger.Info("log15 info", "key3", "value3", "key4", "four")
rootLogger.Warn("log15 warn", "key3", "value3", "key4", "four")
rootLogger.Error("log15 err", "key3", "value3", "key4", "four")
// Add more context variables into sub logger
subLogger := rootLogger.New("context3", true, "context4", 0.01)
subLogger.Info("log15 info", "key3", "value3", "key4", "four")
// Does constructor useless here?
logger3 := logrus.New()
//logger3.SetFormatter(&logrus.JSONFormatter{})
logger3.WithFields(logrus.Fields{"key1": "value1", "key2": two}).Info("logrus info")
logger3.WithFields(logrus.Fields{"key1": "value1", "key2": two}).Warn("logrus warn")
logger3.WithFields(logrus.Fields{"key1": "value1", "key2": two}).Error("logrus error")
}