diff --git a/.readme.tmpl b/.readme.tmpl new file mode 100644 index 000000000..a2016dbfb --- /dev/null +++ b/.readme.tmpl @@ -0,0 +1,89 @@ +# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +Blazing fast, structured, leveled logging in Go. + +## Installation + +`go get -u go.uber.org/zap` + +## Quick Start + +In contexts where performance is nice, but not critical, use the +`SugaredLogger`. It's 4-10x faster than than other structured logging libraries +and includes both structured and `printf`-style APIs. + +```go +logger, _ := NewProduction() +sugar := logger.Sugar() +sugar.Infow("Failed to fetch URL.", + // Structured context as loosely-typed key-value pairs. + "url", url, + "attempt", retryNum, + "backoff", time.Second, +) +sugar.Infof("Failed to fetch URL: %s", url) +``` + +When performance and type safety are critical, use the `Logger`. It's even faster than +the `SugaredLogger` and allocates far less, but it only supports structured logging. + +```go +logger, _ := NewProduction() +logger.Info("Failed to fetch URL.", + // Structured context as strongly-typed Field values. + zap.String("url", url), + zap.Int("attempt", tryNum), + zap.Duration("backoff", time.Second), +) +``` + +## Performance + +For applications that log in the hot path, reflection-based serialization and +string formatting are prohibitively expensive — they're CPU-intensive and +make many small allocations. Put differently, using `encoding/json` and +`fmt.Fprintf` to log tons of `interface{}`s makes your application slow. + +Zap takes a different approach. It includes a reflection-free, zero-allocation +JSON encoder, and the base `Logger` strives to avoid serialization overhead and +allocations wherever possible. By building the high-level `SugaredLogger` on +that foundation, zap lets users *choose* when they need to count every +allocation and when they'd prefer a more familiar, loosely-typed API. + +As measured by its own [benchmarking suite][], not only is zap more performant +than comparable structured logging libraries — it's also faster than the +standard library. Like all benchmarks, take these with a grain of salt.[1](#footnote-versions) + +Log a message and 10 fields: + +{{.BenchmarkAddingFields}} + +Log a message with a logger that already has 10 fields of context: + +{{.BenchmarkAccumulatedContext}} + +Log a static string, without any context or `printf`-style templating: + +{{.BenchmarkWithoutFields}} + +## Development Status: Release Candidate 2 +The current release is `v1.0.0-rc.2`. No further breaking changes are *planned* +unless wider use reveals critical bugs or usability issues, but users who need +absolute stability should wait for the 1.0.0 release. + +
+Released under the [MIT License](LICENSE.txt). + +1 In particular, keep in mind that we may be +benchmarking against slightly older versions of other libraries. Versions are +pinned in zap's [glide.lock][] file. [↩](#anchor-versions) + +[doc-img]: https://godoc.org/go.uber.org/zap?status.svg +[doc]: https://godoc.org/go.uber.org/zap +[ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master +[ci]: https://travis-ci.org/uber-go/zap +[cov-img]: https://coveralls.io/repos/github/uber-go/zap/badge.svg?branch=master +[cov]: https://coveralls.io/github/uber-go/zap?branch=master +[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks +[glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock diff --git a/Makefile b/Makefile index fb0409698..88d268ce2 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ PKGS ?= $(shell glide novendor) # Many Go tools take file globs or directories as arguments instead of packages. PKG_FILES ?= *.go zapcore benchmarks buffer testutils internal/bufferpool internal/exit internal/multierror internal/observer internal/color +COVERALLS_IGNORE := internal/readme/readme.go + # The linting tools evolve with each Go version, so run them only on the latest # stable release. GO_VERSION := $(shell go version | cut -d " " -f 3) @@ -63,9 +65,14 @@ test: .PHONY: coveralls coveralls: - goveralls -service=travis-ci + goveralls -ignore=$(COVERALLS_IGNORE) -service=travis-ci .PHONY: bench BENCH ?= . bench: @$(foreach pkg,$(PKGS),go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) $(pkg);) + +.PHONY: updatereadme +updatereadme: + rm -f README.md + cat .readme.tmpl | go run internal/readme/readme.go > README.md diff --git a/README.md b/README.md index ac26d030e..431206ec6 100644 --- a/README.md +++ b/README.md @@ -59,35 +59,38 @@ Log a message and 10 fields: | Library | Time | Bytes Allocated | Objects Allocated | | :--- | :---: | :---: | :---: | -| :zap: zap | 1436 ns/op | 705 B/op | 2 allocs/op | -| :zap: zap (sugared) | 2436 ns/op | 1931 B/op | 21 allocs/op | -| logrus | 9393 ns/op | 5783 B/op | 77 allocs/op | -| go-kit | 6929 ns/op | 3119 B/op | 65 allocs/op | -| log15 | 25004 ns/op | 5535 B/op | 91 allocs/op | -| apex/log | 18450 ns/op | 4025 B/op | 64 allocs/op | +| :zap: zap | 694 ns/op | 705 B/op | 2 allocs/op | +| :zap: zap (sugared) | 1174 ns/op | 1613 B/op | 20 allocs/op | +| logrus | 6824 ns/op | 6100 B/op | 78 allocs/op | +| go-kit | 3830 ns/op | 2897 B/op | 66 allocs/op | +| log15 | 16650 ns/op | 5632 B/op | 93 allocs/op | +| apex/log | 13381 ns/op | 3834 B/op | 65 allocs/op | +| lion | 5807 ns/op | 5811 B/op | 63 allocs/op | Log a message with a logger that already has 10 fields of context: | Library | Time | Bytes Allocated | Objects Allocated | | :--- | :---: | :---: | :---: | -| :zap: zap | 368 ns/op | 0 B/op | 0 allocs/op | -| :zap: zap (sugared) | 388 ns/op | 0 B/op | 0 allocs/op | -| logrus | 8420 ns/op | 3967 B/op | 61 allocs/op | -| go-kit | 7288 ns/op | 2950 B/op | 50 allocs/op | -| log15 | 17678 ns/op | 2546 B/op | 42 allocs/op | -| apex/log | 16126 ns/op | 2801 B/op | 49 allocs/op | +| :zap: zap | 233 ns/op | 0 B/op | 0 allocs/op | +| :zap: zap (sugared) | 356 ns/op | 80 B/op | 2 allocs/op | +| logrus | 5647 ns/op | 4568 B/op | 63 allocs/op | +| go-kit | 4416 ns/op | 3048 B/op | 52 allocs/op | +| log15 | 14623 ns/op | 2643 B/op | 44 allocs/op | +| apex/log | 11730 ns/op | 2898 B/op | 51 allocs/op | +| lion | 3553 ns/op | 4076 B/op | 38 allocs/op | Log a static string, without any context or `printf`-style templating: | Library | Time | Bytes Allocated | Objects Allocated | | :--- | :---: | :---: | :---: | -| :zap: zap | 398 ns/op | 0 B/op | 0 allocs/op | -| :zap: zap (sugared) | 400 ns/op | 80 B/op | 2 allocs/op | -| standard library | 678 ns/op | 80 B/op | 2 allocs/op | -| logrus | 2778 ns/op | 1409 B/op | 25 allocs/op | -| go-kit | 1318 ns/op | 656 B/op | 13 allocs/op | -| log15 | 5720 ns/op | 1496 B/op | 24 allocs/op | -| apex/log | 3282 ns/op | 584 B/op | 11 allocs/op | +| :zap: zap | 266 ns/op | 0 B/op | 0 allocs/op | +| :zap: zap (sugared) | 367 ns/op | 80 B/op | 2 allocs/op | +| standard library | 584 ns/op | 80 B/op | 2 allocs/op | +| logrus | 1586 ns/op | 1507 B/op | 27 allocs/op | +| go-kit | 612 ns/op | 656 B/op | 13 allocs/op | +| log15 | 4911 ns/op | 1592 B/op | 26 allocs/op | +| apex/log | 2557 ns/op | 584 B/op | 11 allocs/op | +| lion | 914 ns/op | 1225 B/op | 10 allocs/op | ## Development Status: Release Candidate 2 The current release is `v1.0.0-rc.2`. No further breaking changes are *planned* diff --git a/internal/readme/readme.go b/internal/readme/readme.go new file mode 100644 index 000000000..a73f7b351 --- /dev/null +++ b/internal/readme/readme.go @@ -0,0 +1,175 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + "text/template" +) + +var ( + libraryNames = []string{ + "Zap", + "Zap.Sugar", + "stdlib.Println", + "sirupsen/logrus", + "go-kit/kit/log", + "inconshreveable/log15", + "apex/log", + "go.pedge.io/lion", + } + libraryNameToMarkdownName = map[string]string{ + "Zap": ":zap: zap", + "Zap.Sugar": ":zap: zap (sugared)", + "stdlib.Println": "standard library", + "sirupsen/logrus": "logrus", + "go-kit/kit/log": "go-kit", + "inconshreveable/log15": "log15", + "apex/log": "apex/log", + "go.pedge.io/lion": "lion", + } +) + +func main() { + flag.Parse() + if err := do(); err != nil { + log.Fatal(err) + } +} + +func do() error { + tmplData, err := getTmplData() + if err != nil { + return err + } + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + t, err := template.New("tmpl").Parse(string(data)) + if err != nil { + return err + } + if err := t.Execute(os.Stdout, tmplData); err != nil { + return err + } + return nil +} + +type tmplData struct { + BenchmarkAddingFields string + BenchmarkAccumulatedContext string + BenchmarkWithoutFields string +} + +func getTmplData() (*tmplData, error) { + tmplData := &tmplData{} + rows, err := getBenchmarkRows("BenchmarkAddingFields") + if err != nil { + return nil, err + } + tmplData.BenchmarkAddingFields = rows + rows, err = getBenchmarkRows("BenchmarkAccumulatedContext") + if err != nil { + return nil, err + } + tmplData.BenchmarkAccumulatedContext = rows + rows, err = getBenchmarkRows("BenchmarkWithoutFields") + if err != nil { + return nil, err + } + tmplData.BenchmarkWithoutFields = rows + return tmplData, nil +} + +func getBenchmarkRows(benchmarkName string) (string, error) { + benchmarkOutput, err := getBenchmarkOutput(benchmarkName) + if err != nil { + return "", err + } + rows := []string{ + "| Library | Time | Bytes Allocated | Objects Allocated |", + "| :--- | :---: | :---: | :---: |", + } + for _, libraryName := range libraryNames { + row, err := getBenchmarkRow(benchmarkOutput, benchmarkName, libraryName) + if err != nil { + return "", err + } + if row == "" { + continue + } + rows = append(rows, row) + } + return strings.Join(rows, "\n"), nil +} + +func getBenchmarkRow(input []string, benchmarkName string, libraryName string) (string, error) { + line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName)) + if err != nil { + return "", err + } + if line == "" { + return "", nil + } + split := strings.Split(line, "\t") + if len(split) < 5 { + return "", fmt.Errorf("unknown benchmark line: %s", line) + } + return fmt.Sprintf( + "| %s | %s | %s | %s |", + libraryNameToMarkdownName[libraryName], + strings.TrimSpace(split[2]), + strings.TrimSpace(split[3]), + strings.TrimSpace(split[4]), + ), nil +} + +func findUniqueSubstring(input []string, substring string) (string, error) { + var output string + for _, line := range input { + if strings.Contains(line, substring) { + if output != "" { + return "", fmt.Errorf("input has duplicate substring %s", substring) + } + output = line + } + } + return output, nil +} + +func getBenchmarkOutput(benchmarkName string) ([]string, error) { + return getOutput("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem", "./benchmarks") +} + +func getOutput(name string, arg ...string) ([]string, error) { + output, err := exec.Command(name, arg...).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("error running %s %s: %v\n%s", name, strings.Join(arg, " "), err, string(output)) + } + return strings.Split(string(output), "\n"), nil +}