diff --git a/log/README.md b/log/README.md index 7e84740c9..ea576fc81 100644 --- a/log/README.md +++ b/log/README.md @@ -1,59 +1,63 @@ # package log `package log` provides a minimal interface for structured logging in services. -It may be wrapped to encode conventions, enforce type-safety, etc. +It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on. It can be used for both typical application log events, and log-structured data streams. -## Rationale +## Structured logging -TODO +Structured logging is, basically, conceding to the reality that logs are _data_, + and warrant some level of schematic rigor. +Using a stricter, key/value-oriented message format for our logs, + containing contextual and semantic information, + makes it much easier to get insight into the operational activity of the systems we build. +Consequently, `package log` is of the strong belief that + "[the benefits of structured logging outweigh the minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". -## Usage - -Typical application logging. +Migrating from unstructured to structured logging is probably a lot easier than you'd expect. ```go -import ( - "os" - - "github.com/go-kit/kit/log" -) +// Unstructured +log.Printf("HTTP server listening on %s", addr) -func main() { - logger := log.NewLogfmtLogger(os.Stderr) - logger.Log("question", "what is the meaning of life?", "answer", 42) -} +// Structured +logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") ``` -Output: -``` -question="what is the meaning of life?" answer=42 -``` +## Usage -Contextual logging. +### Typical application logging ```go -func handle(logger log.Logger, req *Request) { - ctx := log.NewContext(logger).With("txid", req.TransactionID, "query", req.Query) - ctx.Log() +logger := log.NewLogfmtLogger(os.Stderr) +logger.Log("question", "what is the meaning of life?", "answer", 42) + +// Output: +// question="what is the meaning of life?" answer=42 +``` - answer, err := process(ctx, req.Query) - if err != nil { - ctx.Log("err", err) - return - } +### Log contexts + +```go +func main() { + var logger log.Logger + logger = log.NewLogfmtLogger(os.Stderr) + logger = log.NewContext(logger).With("instance_id", 123) - ctx.Log("answer", answer) + logger.Log("msg", "starting") + NewWorker(log.NewContext(logger).With("component", "worker")).Run() + NewSlacker(log.NewContext(logger).With("component", "slacker")).Run() } -``` -Output: -``` -txid=12345 query="some=query" -txid=12345 query="some=query" answer=42 +// Output: +// instance_id=123 msg=starting +// instance_id=123 component=worker msg=running +// instance_id=123 component=slacker msg=running ``` -Redirect stdlib log to gokit logger. +### Interact with stdlib logger + +Redirect stdlib logger to Go kit logger. ```go import ( @@ -65,58 +69,78 @@ import ( func main() { logger := kitlog.NewJSONLogger(os.Stdout) stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) - stdlog.Print("I sure like pie") } -``` -Output +// Output: +// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} ``` -{"msg":"I sure like pie","ts":"2016/01/28 19:41:08"} + +Or, if, for legacy reasons, + you need to pipe all of your logging through the stdlib log package, + you can redirect Go kit logger to the stdlib logger. + +```go +logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) +logger.Log("legacy", true, "msg", "at least it's something") + +// Output: +// 2016/01/01 12:34:56 legacy=true msg="at least it's something" ``` -Adding a timestamp to contextual logs +### Timestamps and callers ```go -func handle(logger log.Logger, req *Request) { - ctx := log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "query", req.Query) - ctx.Log() +var logger log.Logger +logger = log.NewLogfmtLogger(os.Stderr) +logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) - answer, err := process(ctx, req.Query) - if err != nil { - ctx.Log("err", err) - return - } +logger.Log("msg", "hello") - ctx.Log("answer", answer) -} +// Output: +// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello ``` -Output -``` -ts=2016-01-29T00:46:04Z query="some=query" -ts=2016-01-29T00:46:04Z query="some=query" answer=42 -``` +## Supported output formats -Adding caller info to contextual logs +- [Logfmt](https://brandur.org/logfmt) +- JSON -```go -func handle(logger log.Logger, req *Request) { - ctx := log.NewContext(logger).With("caller", log.DefaultCaller, "query", req.Query) - ctx.Log() +## Enhancements - answer, err := process(ctx, req.Query) - if err != nil { - ctx.Log("err", err) - return - } +`package log` is centered on the one-method Logger interface. - ctx.Log("answer", answer) +```go +type Logger interface { + Log(keyvals ...interface{}) error } ``` -Output -``` -caller=logger.go:20 query="some=query" -caller=logger.go:28 query="some=query" answer=42 -``` +This interface, and its supporting code like [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context), + is the product of much iteration and evaluation. +For more details on the evolution of the Logger interface, + see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), + a talk by [Chris Hines](https://github.com/ChrisHines). +Also, see + [#63](https://github.com/go-kit/kit/issues/63), + [#76](https://github.com/go-kit/kit/pull/76), + [#131](https://github.com/go-kit/kit/issues/131), + [#157](https://github.com/go-kit/kit/pull/157), and + [#252](https://github.com/go-kit/kit/pull/252) + to review historical conversations about package log and the Logger interface. + +Value-add packages and suggestions, + like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/levels), + are of course welcome. +Good proposals should + +- Be composable with [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context), +- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped context, and +- Be friendly to packages that accept only an unadorned log.Logger. + +## Benchmarks & comparisons + +There are a few Go logging benchmarks and comparisons that include Go kit's package log. + +- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log +- [uber-common/zap](https://github.com/uber-common/zap), a logging library, include a comparison with kit/log