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).
+
+ 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
+}