├── .gitignore ├── .go ├── LICENSE ├── Makefile ├── README.md ├── cmd └── benchjson │ └── main.go ├── docs ├── CNAME ├── _config.yml ├── _layouts │ └── default.html └── index.html ├── go.mod ├── go.sum ├── go.version ├── logrus_test.go ├── main_test.go ├── results ├── slog_test.go ├── zap_test.go ├── zapsugar_test.go └── zerolog_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | docs/_site/ 2 | .cache/ 3 | .goroot/ 4 | -------------------------------------------------------------------------------- /.go: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ROOT=$(dirname "$0") 6 | 7 | if [ -z "$GO_VERSION" ]; then 8 | source "$ROOT/go.version" 9 | fi 10 | 11 | if [ -z "$GO_VERSION" ]; then 12 | echo "Missing \$GO_VERSION environment" >&2 13 | exit 1 14 | fi 15 | 16 | GOROOT="$ROOT"/.goroot/$GO_VERSION 17 | GOROOT_CURRENT="$ROOT"/.goroot/current 18 | CACHE_DIR="$ROOT"/.cache 19 | 20 | if [[ "$("$GOROOT"/bin/go version 2>&1 | cut -f3 -d\ )" != "go$GO_VERSION" ]]; then 21 | # Make sure only one instance is executed at the same time 22 | [ "$FLOCKER" != "$0" ] && exec env FLOCKER="$0" flock -e $0 $0 "$@" 23 | 24 | rm -rf "$GOROOT" 25 | 26 | case "$(uname -m)" in 27 | "x86_64") 28 | ARCH=amd64 29 | ;; 30 | "arm64") 31 | ARCH=arm64 32 | ;; 33 | "aarch64") 34 | ARCH=arm64 35 | ;; 36 | *) 37 | echo "Unsupported arch: $(uname -m)" 38 | exit 1 39 | esac 40 | 41 | case "$(uname -s)" in 42 | "Darwin") 43 | OS=darwin 44 | ;; 45 | "Linux") 46 | OS=linux 47 | ;; 48 | "FreeBSD") 49 | OS=freebsd 50 | ;; 51 | *) 52 | echo "Unsupported OS: $(uname -s)" 53 | exit 1 54 | esac 55 | 56 | GO_URL="https://dl.google.com/go/go$GO_VERSION.$OS-$ARCH.tar.gz" 57 | 58 | # Download and install Go. 59 | rm -rf "$GOROOT" 60 | mkdir -p "$GOROOT" 61 | mkdir -p "$CACHE_DIR" 62 | cache_file="$CACHE_DIR"/${GO_URL##*/} 63 | if [ ! -f "$cache_file" ]; then 64 | echo "Installing $GO_URL" >&2 65 | curl -sS -o "$cache_file" "$GO_URL" 66 | else 67 | echo "Installing $GO_URL (from cache)" >&2 68 | fi 69 | 70 | tar --strip-components 1 -C "$GOROOT" -zxf "$cache_file" 71 | 72 | # Reset quilt state. 73 | rm -rf "$ROOT"/.pc 74 | 75 | # Update current symlink so quilt find the files. 76 | rm -rf "$GOROOT_CURRENT" 77 | ln -sf -T "$GO_VERSION" "$GOROOT_CURRENT" 78 | else 79 | # Update current symlink so quilt find the files, may be rolling back to previous version. 80 | ln -sf -T "$GO_VERSION" "$GOROOT_CURRENT" 81 | fi 82 | 83 | "$GOROOT"/bin/go "$@" 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Olivier Poitrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | ./.go test -timeout 5h -count 5 -bench . -benchmem | tee results 3 | (echo "---\nlayout: default\n---"; go run cmd/benchjson/main.go < results) > docs/index.html 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logbench 2 | 3 | Logbench is a comparison of benchmarks of various Golang logging libraries. 4 | 5 | See [results here](http://bench.zerolog.io). 6 | -------------------------------------------------------------------------------- /cmd/benchjson/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "sort" 10 | "strings" 11 | 12 | "golang.org/x/perf/benchstat" 13 | ) 14 | 15 | type Chart struct { 16 | Labels []string `json:"labels"` 17 | Datasets []Dataset `json:"datasets"` 18 | } 19 | 20 | type Dataset struct { 21 | Label string `json:"label"` 22 | Data []*float64 `json:"data"` 23 | } 24 | 25 | // group -> test -> lib 26 | type Report map[string]map[string]*benchstat.Metrics 27 | 28 | func main() { 29 | libs := map[string]struct{}{} 30 | reports := map[string]Report{} 31 | 32 | c := &benchstat.Collection{} 33 | data, err := ioutil.ReadAll(os.Stdin) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | c.AddConfig("-", data) 38 | c.Tables() // trigger stats update 39 | for key, metrics := range c.Metrics { 40 | report := reports[key.Unit] 41 | if report == nil { 42 | report = Report{} 43 | reports[key.Unit] = report 44 | } 45 | idx := strings.IndexByte(key.Benchmark[1:], '/') 46 | if idx == -1 { 47 | log.Fatalf("invalid benchmark name: %s", key.Benchmark) 48 | } 49 | lib := key.Benchmark[1 : idx+1] 50 | libs[lib] = struct{}{} 51 | name := key.Benchmark[idx+2:] 52 | n := report[name] 53 | if n == nil { 54 | n = map[string]*benchstat.Metrics{} 55 | report[name] = n 56 | } 57 | n[lib] = metrics 58 | } 59 | 60 | slibs := []string{} 61 | for lib := range libs { 62 | slibs = append(slibs, lib) 63 | } 64 | sort.Sort(sort.Reverse(sort.StringSlice(slibs))) 65 | 66 | charts := map[string]Chart{} 67 | for unit, report := range reports { 68 | charts[unit] = buildChart(report, slibs) 69 | } 70 | b, err := json.Marshal(charts) 71 | if err != nil { 72 | panic(err) 73 | } 74 | fmt.Println(string(b)) 75 | } 76 | 77 | func buildChart(report Report, libs []string) Chart { 78 | chart := Chart{} 79 | for name := range report { 80 | chart.Labels = append(chart.Labels, name) 81 | } 82 | sort.Sort(sort.Reverse(sort.StringSlice(chart.Labels))) 83 | for _, lib := range libs { 84 | chart.Datasets = append(chart.Datasets, Dataset{ 85 | Label: lib, 86 | Data: make([]*float64, len(chart.Labels)), 87 | }) 88 | } 89 | for i, name := range chart.Labels { 90 | results := report[name] 91 | for j, ds := range chart.Datasets { 92 | if m, found := results[ds.Label]; found { 93 | chart.Datasets[j].Data[i] = &m.Mean 94 | } 95 | } 96 | } 97 | return chart 98 | } 99 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | bench.zerolog.io -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rs/logbench/34a41c891b51bab2ee468adf56d200885e73893e/docs/_config.yml -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 34 | 35 | 36 | 37 | Fork me on GitHub 39 |

Logbench

40 | 41 |

Logbench is comparing benchmark results of popular Golang logging libraries.

42 | 43 |
44 | 45 |
46 |
47 | 52 |
53 |
54 | 55 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | {"B/op":{"labels":["Enabled/WithContext/MsgComplex-24","Enabled/WithContext/Msg-24","Enabled/WithContext/Formatting-24","Enabled/WithContext/Fields/Time-24","Enabled/WithContext/Fields/Strings-24","Enabled/WithContext/Fields/String-24","Enabled/WithContext/Fields/Object-24","Enabled/WithContext/Fields/Ints-24","Enabled/WithContext/Fields/Int-24","Enabled/WithContext/Fields/Floats64-24","Enabled/WithContext/Fields/Floats32-24","Enabled/WithContext/Fields/Float64-24","Enabled/WithContext/Fields/Float32-24","Enabled/WithContext/Fields/Errors-24","Enabled/WithContext/Fields/Error-24","Enabled/WithContext/Fields/Bools-24","Enabled/WithContext/Fields/Bool-24","Enabled/NoContext/MsgComplex-24","Enabled/NoContext/Msg-24","Enabled/NoContext/Formatting-24","Enabled/NoContext/Fields/Time-24","Enabled/NoContext/Fields/Strings-24","Enabled/NoContext/Fields/String-24","Enabled/NoContext/Fields/Object-24","Enabled/NoContext/Fields/Ints-24","Enabled/NoContext/Fields/Int-24","Enabled/NoContext/Fields/Floats64-24","Enabled/NoContext/Fields/Floats32-24","Enabled/NoContext/Fields/Float64-24","Enabled/NoContext/Fields/Float32-24","Enabled/NoContext/Fields/Errors-24","Enabled/NoContext/Fields/Error-24","Enabled/NoContext/Fields/Bools-24","Enabled/NoContext/Fields/Bool-24","Disabled/WithContext/MsgComplex-24","Disabled/WithContext/Msg-24","Disabled/WithContext/Formatting-24","Disabled/WithContext/Fields/Time-24","Disabled/WithContext/Fields/Strings-24","Disabled/WithContext/Fields/String-24","Disabled/WithContext/Fields/Object-24","Disabled/WithContext/Fields/Ints-24","Disabled/WithContext/Fields/Int-24","Disabled/WithContext/Fields/Floats64-24","Disabled/WithContext/Fields/Floats32-24","Disabled/WithContext/Fields/Float64-24","Disabled/WithContext/Fields/Float32-24","Disabled/WithContext/Fields/Errors-24","Disabled/WithContext/Fields/Error-24","Disabled/WithContext/Fields/Bools-24","Disabled/WithContext/Fields/Bool-24","Disabled/NoContext/MsgComplex-24","Disabled/NoContext/Msg-24","Disabled/NoContext/Formatting-24","Disabled/NoContext/Fields/Time-24","Disabled/NoContext/Fields/Strings-24","Disabled/NoContext/Fields/String-24","Disabled/NoContext/Fields/Object-24","Disabled/NoContext/Fields/Ints-24","Disabled/NoContext/Fields/Int-24","Disabled/NoContext/Fields/Floats64-24","Disabled/NoContext/Fields/Floats32-24","Disabled/NoContext/Fields/Float64-24","Disabled/NoContext/Fields/Float32-24","Disabled/NoContext/Fields/Errors-24","Disabled/NoContext/Fields/Error-24","Disabled/NoContext/Fields/Bools-24","Disabled/NoContext/Fields/Bool-24"],"datasets":[{"label":"slog","data":[16,16,97,276,626,48,146,170,32,170,170,162,162,553,32,170,32,16,16,97,276,626,48,146,170,32,170,170,162,162,553,32,170,32,0,0,80,40,40,32,16,40,16,40,40,24,20,40,16,40,16,0,0,80,40,40,32,16,40,16,40,40,24,20,40,16,40,16]},{"label":"Zerolog","data":[0,0,80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"label":"ZapSugar","data":[16,16,81,194,194,162,146,195,146,194,194.6,154,150,null,146,195,146,16,16,81,194,194,162,146,195,146,194,195,154,150,null,146,194.6,146,16,16,0,40,40,32,16,40,16,40,40,24,20,null,16,40,16,16,16,0,40,40,32,16,40,16,40,40,24,20,null,16,40,16]},{"label":"Zap","data":[0,0,null,89,88,64,64,89,65,89,89,64,64,null,64,89,65,0,0,null,89,88,64,64,89,65,89,89,64,64,null,64,89,65,0,0,null,88,88,64,64,88,64,88,88,64,64,null,64,88,64,0,0,null,88,88,64,64,88,64,88,88,64,64,null,64,88,64]},{"label":"Logrus","data":[1675.4,1641,1740,null,2834,2819,2802.4,2827.4,2799,2827,2827,2808,2804,null,null,2827,2800,1030,997,1094,null,2192,2174.6,2158,2181.6,2158,2182,2181.6,2166,2161,null,null,2183,2158,32,32,0,null,824,816,800,824,800,824,824,808,804,null,null,824,800,32,32,0,null,873,865,849,873,849,873,873,857,853,null,null,873,849]}]},"allocs/op":{"labels":["Enabled/WithContext/MsgComplex-24","Enabled/WithContext/Msg-24","Enabled/WithContext/Formatting-24","Enabled/WithContext/Fields/Time-24","Enabled/WithContext/Fields/Strings-24","Enabled/WithContext/Fields/String-24","Enabled/WithContext/Fields/Object-24","Enabled/WithContext/Fields/Ints-24","Enabled/WithContext/Fields/Int-24","Enabled/WithContext/Fields/Floats64-24","Enabled/WithContext/Fields/Floats32-24","Enabled/WithContext/Fields/Float64-24","Enabled/WithContext/Fields/Float32-24","Enabled/WithContext/Fields/Errors-24","Enabled/WithContext/Fields/Error-24","Enabled/WithContext/Fields/Bools-24","Enabled/WithContext/Fields/Bool-24","Enabled/NoContext/MsgComplex-24","Enabled/NoContext/Msg-24","Enabled/NoContext/Formatting-24","Enabled/NoContext/Fields/Time-24","Enabled/NoContext/Fields/Strings-24","Enabled/NoContext/Fields/String-24","Enabled/NoContext/Fields/Object-24","Enabled/NoContext/Fields/Ints-24","Enabled/NoContext/Fields/Int-24","Enabled/NoContext/Fields/Floats64-24","Enabled/NoContext/Fields/Floats32-24","Enabled/NoContext/Fields/Float64-24","Enabled/NoContext/Fields/Float32-24","Enabled/NoContext/Fields/Errors-24","Enabled/NoContext/Fields/Error-24","Enabled/NoContext/Fields/Bools-24","Enabled/NoContext/Fields/Bool-24","Disabled/WithContext/MsgComplex-24","Disabled/WithContext/Msg-24","Disabled/WithContext/Formatting-24","Disabled/WithContext/Fields/Time-24","Disabled/WithContext/Fields/Strings-24","Disabled/WithContext/Fields/String-24","Disabled/WithContext/Fields/Object-24","Disabled/WithContext/Fields/Ints-24","Disabled/WithContext/Fields/Int-24","Disabled/WithContext/Fields/Floats64-24","Disabled/WithContext/Fields/Floats32-24","Disabled/WithContext/Fields/Float64-24","Disabled/WithContext/Fields/Float32-24","Disabled/WithContext/Fields/Errors-24","Disabled/WithContext/Fields/Error-24","Disabled/WithContext/Fields/Bools-24","Disabled/WithContext/Fields/Bool-24","Disabled/NoContext/MsgComplex-24","Disabled/NoContext/Msg-24","Disabled/NoContext/Formatting-24","Disabled/NoContext/Fields/Time-24","Disabled/NoContext/Fields/Strings-24","Disabled/NoContext/Fields/String-24","Disabled/NoContext/Fields/Object-24","Disabled/NoContext/Fields/Ints-24","Disabled/NoContext/Fields/Int-24","Disabled/NoContext/Fields/Floats64-24","Disabled/NoContext/Fields/Floats32-24","Disabled/NoContext/Fields/Float64-24","Disabled/NoContext/Fields/Float32-24","Disabled/NoContext/Fields/Errors-24","Disabled/NoContext/Fields/Error-24","Disabled/NoContext/Fields/Bools-24","Disabled/NoContext/Fields/Bool-24"],"datasets":[{"label":"slog","data":[3,3,4,9,7,5,6,7,4,7,7,8,8,9,4,7,4,3,3,4,9,7,5,6,7,4,7,7,8,8,9,4,7,4,0,0,1,2,2,2,1,2,1,2,2,2,2,2,1,2,1,0,0,1,2,2,2,1,2,1,2,2,2,2,2,1,2,1]},{"label":"Zerolog","data":[0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"label":"ZapSugar","data":[1,1,1,4,4,3,2,4,2,4,4,3,3,null,2,4,2,1,1,1,4,4,3,2,4,2,4,4,3,3,null,2,4,2,1,1,0,2,2,2,1,2,1,2,2,2,2,null,1,2,1,1,1,0,2,2,2,1,2,1,2,2,2,2,null,1,2,1]},{"label":"Zap","data":[0,0,null,2,2,1,1,2,1,2,2,1,1,null,1,2,1,0,0,null,2,2,1,1,2,1,2,2,1,1,null,1,2,1,0,0,null,2,2,1,1,2,1,2,2,1,1,null,1,2,1,0,0,null,2,2,1,1,2,1,2,2,1,1,null,1,2,1]},{"label":"Logrus","data":[29,29,29,null,36,36,35,36,35,36,36,36,36,null,null,36,35,20,20,20,null,28,28,27,28,27,28,28,28,28,null,null,28,27,2,2,0,null,7,7,6,7,6,7,7,7,7,null,null,7,6,2,2,0,null,8,8,7,8,7,8,8,8,8,null,null,8,7]}]},"ns/op":{"labels":["Enabled/WithContext/MsgComplex-24","Enabled/WithContext/Msg-24","Enabled/WithContext/Formatting-24","Enabled/WithContext/Fields/Time-24","Enabled/WithContext/Fields/Strings-24","Enabled/WithContext/Fields/String-24","Enabled/WithContext/Fields/Object-24","Enabled/WithContext/Fields/Ints-24","Enabled/WithContext/Fields/Int-24","Enabled/WithContext/Fields/Floats64-24","Enabled/WithContext/Fields/Floats32-24","Enabled/WithContext/Fields/Float64-24","Enabled/WithContext/Fields/Float32-24","Enabled/WithContext/Fields/Errors-24","Enabled/WithContext/Fields/Error-24","Enabled/WithContext/Fields/Bools-24","Enabled/WithContext/Fields/Bool-24","Enabled/NoContext/MsgComplex-24","Enabled/NoContext/Msg-24","Enabled/NoContext/Formatting-24","Enabled/NoContext/Fields/Time-24","Enabled/NoContext/Fields/Strings-24","Enabled/NoContext/Fields/String-24","Enabled/NoContext/Fields/Object-24","Enabled/NoContext/Fields/Ints-24","Enabled/NoContext/Fields/Int-24","Enabled/NoContext/Fields/Floats64-24","Enabled/NoContext/Fields/Floats32-24","Enabled/NoContext/Fields/Float64-24","Enabled/NoContext/Fields/Float32-24","Enabled/NoContext/Fields/Errors-24","Enabled/NoContext/Fields/Error-24","Enabled/NoContext/Fields/Bools-24","Enabled/NoContext/Fields/Bool-24","Disabled/WithContext/MsgComplex-24","Disabled/WithContext/Msg-24","Disabled/WithContext/Formatting-24","Disabled/WithContext/Fields/Time-24","Disabled/WithContext/Fields/Strings-24","Disabled/WithContext/Fields/String-24","Disabled/WithContext/Fields/Object-24","Disabled/WithContext/Fields/Ints-24","Disabled/WithContext/Fields/Int-24","Disabled/WithContext/Fields/Floats64-24","Disabled/WithContext/Fields/Floats32-24","Disabled/WithContext/Fields/Float64-24","Disabled/WithContext/Fields/Float32-24","Disabled/WithContext/Fields/Errors-24","Disabled/WithContext/Fields/Error-24","Disabled/WithContext/Fields/Bools-24","Disabled/WithContext/Fields/Bool-24","Disabled/NoContext/MsgComplex-24","Disabled/NoContext/Msg-24","Disabled/NoContext/Formatting-24","Disabled/NoContext/Fields/Time-24","Disabled/NoContext/Fields/Strings-24","Disabled/NoContext/Fields/String-24","Disabled/NoContext/Fields/Object-24","Disabled/NoContext/Fields/Ints-24","Disabled/NoContext/Fields/Int-24","Disabled/NoContext/Fields/Floats64-24","Disabled/NoContext/Fields/Floats32-24","Disabled/NoContext/Fields/Float64-24","Disabled/NoContext/Fields/Float32-24","Disabled/NoContext/Fields/Errors-24","Disabled/NoContext/Fields/Error-24","Disabled/NoContext/Fields/Bools-24","Disabled/NoContext/Fields/Bool-24"],"datasets":[{"label":"slog","data":[211.14,210.18,247.32,350.04,585.76,234.44,305.06000000000006,271.14,224.48000000000002,332.9200000000001,312.9,307.14,306.21999999999997,557.2,228.74,268.075,228.84,208.46,207.16,245.29999999999998,346.35999999999996,580.04,233.48000000000002,304.74,270.02,223.74,331.14,310.34000000000003,305.53999999999996,304.22,549.4,228.2,265.03999999999996,225.24,0.5284800000000001,0.5314599999999999,56.45,20.544,20.662,17.703999999999997,9.293000000000001,20.708000000000002,9.4532,20.792,20.6725,13.314,11.656,20.614,9.3232,20.694,9.2706,0.53172,0.53142,56.006,20.486,20.598,17.636,9.277600000000001,20.498,9.4526,20.688000000000002,20.616,13.228,11.758,20.655,9.284,20.582,9.158800000000001]},{"label":"Zerolog","data":[16.556,10.559600000000001,72.248,15.709999999999997,31.066000000000003,11.1775,15.532,17.424000000000003,12.68,65.604,46.716,19.954,16.366,53.924,17.79,11.99,12.4578,13.578,8.0004,70.404,14.868,29.428,11.488,13.868,14.138,10.0912,63.97800000000001,44.682500000000005,17.107999999999997,14.388,50.726,11.582,12.2,10.156400000000001,0.85132,0.85102,0.98736,1.1016,1.102,1.0274,1.0494,1.1004,1.1118000000000001,1.1092,1.2144,1.1454,1.0824,1.1006,1.0628,1.1016,1.116,0.85382,0.85226,0.99318,1.101,1.101,1.0258,1.0496,1.102,1.1016,1.1114000000000002,1.2231999999999998,1.1366,1.0898,1.0984,1.0694,1.1004,1.1152000000000002]},{"label":"ZapSugar","data":[56.248,49.446000000000005,105.74,209.14,269.82,139.22,129.38,162.07999999999998,121.1,216.34,202.3,138.34,136.57999999999998,null,129.1,158.84,121.1,53.294,44.916000000000004,102.72,206.07999999999998,267.03999999999996,138,126.58,161.18,118.74,213.04,199.18,136.2,134.34,null,126.58,157.58,119.26,9.309000000000001,9.2578,0.49858,20.983999999999998,20.904,17.96,9.4698,20.716,9.6718,20.86,20.909999999999997,13.540000000000001,12.011999999999999,null,9.5128,20.906,9.4406,9.240400000000001,9.23125,0.50066,20.906000000000002,20.862000000000002,17.932000000000002,9.509599999999999,20.726000000000003,9.5384,20.776,20.782,13.636,11.918,null,9.636999999999999,20.932,9.540799999999999]},{"label":"Zap","data":[38.019999999999996,32.224,null,144.92,220.62,82.12400000000001,80.592,95.282,69.36200000000001,152.82,136.96,78.456,75.958,null,79.748,91.106,69.52,36.748,29.476,null,141.42,219.62,79.526,79.616,92.54,67.57400000000001,152.72,133.82,76.338,73.826,null,79.762,89.07400000000001,66.98599999999999,0.59128,0.59634,null,40.779999999999994,40.748,28.12,27.988000000000003,40.955999999999996,28.401999999999997,40.862,40.668,28.414,27.914,null,28.22,40.54,27.952,0.5962,0.59432,null,40.898,40.529999999999994,28.418,28.152,41.416000000000004,28.706,41.518,41.394,28.47,28.07,null,28.436,40.75,28.21]},{"label":"Logrus","data":[6903.2,6809.2,7475.6,null,10550.2,9394.6,9014.8,9540.8,8890.2,11415,10735.6,9476.8,9629.4,null,null,9370,9040.6,3665.8,3516.6,4425.6,null,6611.6,5574.8,5763.8,5789.8,5398.6,7239.8,6723.8,5746.2,5485.2,null,null,5770.6,5315.2,16.628,16.6,0.36854,null,373.04,360.38,354.55999999999995,377.28,351.56,373.62,373.70000000000005,374.76,370.44,null,null,373.5,351.26,16.066,15.840000000000002,0.36296,null,369.26,357.38,347.66,368.12,348.54,367.34000000000003,365.86,365.66,363.38,null,null,370,349.7]}]}} 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rs/logbench 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/juju/testing v0.0.0-20190613124551-e81189438503 7 | github.com/rs/zerolog v1.30.0 8 | github.com/sirupsen/logrus v1.9.3 9 | go.uber.org/zap v1.25.0 10 | golang.org/x/perf v0.0.0-20201207232921-bdcc6220ee90 11 | ) 12 | 13 | require ( 14 | github.com/benbjohnson/clock v1.3.0 // indirect 15 | github.com/juju/loggo v1.0.0 // indirect 16 | github.com/mattn/go-colorable v0.1.13 // indirect 17 | github.com/mattn/go-isatty v0.0.19 // indirect 18 | go.uber.org/multierr v1.11.0 // indirect 19 | golang.org/x/sys v0.11.0 // indirect 20 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 // indirect 21 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect 22 | gopkg.in/yaml.v2 v2.2.2 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= 3 | github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= 4 | github.com/aclements/go-moremath v0.0.0-20161014184102-0ff62e0875ff/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ= 5 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 6 | github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 7 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 12 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 13 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 14 | github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= 15 | github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= 16 | github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= 17 | github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= 18 | github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= 19 | github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 20 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 21 | github.com/juju/loggo v1.0.0 h1:Y6ZMQOGR9Aj3BGkiWx7HBbIx6zNwNkxhVNOHU2i1bl0= 22 | github.com/juju/loggo v1.0.0/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg= 23 | github.com/juju/testing v0.0.0-20190613124551-e81189438503 h1:ZUgTbk8oHgP0jpMieifGC9Lv47mHn8Pb3mFX3/Ew4iY= 24 | github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= 25 | github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 26 | github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 27 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 28 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 29 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 30 | github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 31 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 32 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 33 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 34 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 35 | github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= 36 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 37 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 38 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 39 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 40 | github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= 41 | github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= 42 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 43 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 44 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 45 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 46 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 47 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 48 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 49 | go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 50 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 51 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 52 | go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= 53 | go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= 54 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 55 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 56 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 57 | golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 58 | golang.org/x/perf v0.0.0-20201207232921-bdcc6220ee90 h1:P+M61+mQKVHzooHFlNUTNBfj+TqHiQOGgx2kKL7mHbA= 59 | golang.org/x/perf v0.0.0-20201207232921-bdcc6220ee90/go.mod h1:KRSrLY7jerMEa0Ih7gBheQ3FYDiSx6liMnniX1o3j2g= 60 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 61 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 62 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 63 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 64 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 65 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 67 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 68 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 70 | google.golang.org/api v0.0.0-20170206182103-3d017632ea10/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 71 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 72 | google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 h1:+j1SppRob9bAgoYmsdW9NNBdKZfgYuWpqnYHv78Qt8w= 75 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 76 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= 77 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 78 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 79 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 80 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 81 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 82 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 83 | -------------------------------------------------------------------------------- /go.version: -------------------------------------------------------------------------------- 1 | GO_VERSION=1.21.0 2 | -------------------------------------------------------------------------------- /logrus_test.go: -------------------------------------------------------------------------------- 1 | package logbench 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func init() { 11 | tests["Logrus"] = logrusTester{} 12 | } 13 | 14 | type logrusTester struct { 15 | l logrus.FieldLogger 16 | } 17 | 18 | var ( 19 | _ logTesterArray = (*logrusTester)(nil) 20 | ) 21 | 22 | func (logrusTester) newLogger(out io.Writer, disabled bool) logTester { 23 | lvl := logrus.DebugLevel 24 | if disabled { 25 | lvl = logrus.PanicLevel 26 | } 27 | return logrusTester{&logrus.Logger{ 28 | Out: out, 29 | Formatter: &logrus.JSONFormatter{ 30 | DisableTimestamp: true, 31 | FieldMap: logrus.FieldMap{ 32 | logrus.FieldKeyTime: "", 33 | logrus.FieldKeyMsg: "message", 34 | }, 35 | }, 36 | Hooks: make(logrus.LevelHooks), 37 | Level: lvl, 38 | }} 39 | } 40 | 41 | func (t logrusTester) logMsg(msg string) { 42 | t.l.Info(msg) 43 | } 44 | 45 | func (t logrusTester) logFormat(format string, v ...interface{}) bool { 46 | t.l.Infof(format, v...) 47 | return true 48 | } 49 | 50 | func (t logrusTester) withContext(context map[string]interface{}) (logTester, bool) { 51 | return logrusTester{t.l.WithFields(context)}, true 52 | } 53 | 54 | func (t logrusTester) logBool(msg, key string, value bool) bool { 55 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 56 | return true 57 | } 58 | 59 | func (t logrusTester) logInt(msg, key string, value int) bool { 60 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 61 | return true 62 | } 63 | 64 | func (t logrusTester) logFloat32(msg, key string, value float32) bool { 65 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 66 | return true 67 | } 68 | 69 | func (t logrusTester) logFloat64(msg, key string, value float64) bool { 70 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 71 | return true 72 | } 73 | 74 | func (t logrusTester) logTime(msg, key string, value time.Time) bool { 75 | return false 76 | } 77 | 78 | func (t logrusTester) logDuration(msg, key string, value time.Duration) bool { 79 | return false 80 | } 81 | 82 | func (t logrusTester) logError(msg, key string, value error) bool { 83 | return false 84 | } 85 | 86 | func (t logrusTester) logString(msg, key string, value string) bool { 87 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 88 | return true 89 | } 90 | 91 | func (t logrusTester) logObject(msg, key string, value *obj) bool { 92 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 93 | return true 94 | } 95 | 96 | func (t logrusTester) logBools(msg, key string, value []bool) bool { 97 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 98 | return true 99 | } 100 | 101 | func (t logrusTester) logInts(msg, key string, value []int) bool { 102 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 103 | return true 104 | } 105 | 106 | func (t logrusTester) logFloats32(msg, key string, value []float32) bool { 107 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 108 | return true 109 | } 110 | 111 | func (t logrusTester) logFloats64(msg, key string, value []float64) bool { 112 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 113 | return true 114 | } 115 | 116 | func (t logrusTester) logTimes(msg, key string, value []time.Time) bool { 117 | return false 118 | } 119 | 120 | func (t logrusTester) logDurations(msg, key string, value []time.Duration) bool { 121 | return false 122 | } 123 | 124 | func (t logrusTester) logErrors(msg, key string, value []error) bool { 125 | return false 126 | } 127 | 128 | func (t logrusTester) logStrings(msg, key string, value []string) bool { 129 | t.l.WithFields(logrus.Fields{key: value}).Info(msg) 130 | return true 131 | } 132 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package logbench 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "testing" 10 | "time" 11 | 12 | "github.com/juju/testing/checkers" 13 | ) 14 | 15 | type tester interface { 16 | newLogger(out io.Writer, disabled bool) logTester 17 | } 18 | 19 | type logTester interface { 20 | logMsg(msg string) 21 | logFormat(format string, v ...interface{}) bool 22 | 23 | withContext(context map[string]interface{}) (logTester, bool) 24 | 25 | logBool(msg, key string, value bool) bool 26 | logInt(msg, key string, value int) bool 27 | logFloat32(msg, key string, value float32) bool 28 | logFloat64(msg, key string, value float64) bool 29 | logTime(msg, key string, value time.Time) bool 30 | logDuration(msg, key string, value time.Duration) bool 31 | logError(msg, key string, value error) bool 32 | logString(msg, key string, value string) bool 33 | logObject(msg, key string, value *obj) bool 34 | } 35 | 36 | type logTesterArray interface { 37 | logBools(msg, key string, value []bool) bool 38 | logInts(msg, key string, value []int) bool 39 | logFloats32(msg, key string, value []float32) bool 40 | logFloats64(msg, key string, value []float64) bool 41 | logTimes(msg, key string, value []time.Time) bool 42 | logDurations(msg, key string, value []time.Duration) bool 43 | logErrors(msg, key string, value []error) bool 44 | logStrings(msg, key string, value []string) bool 45 | } 46 | 47 | var tests = map[string]tester{} 48 | 49 | type obj struct { 50 | Name string `json:"name"` 51 | Count int `json:"count"` 52 | Enabled bool `json:"enabled"` 53 | } 54 | 55 | func (o obj) jsonMap() map[string]interface{} { 56 | return map[string]interface{}{ 57 | "name": o.Name, 58 | "count": o.Count, 59 | "enabled": o.Enabled, 60 | } 61 | } 62 | 63 | type objs []*obj 64 | 65 | var ( 66 | sampleBools = []bool{true, false, true, false, true, false, true, false, true, false} 67 | sampleInts = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} 68 | sampleFloats32 = []float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} 69 | sampleFloats64 = []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.0, 0.1} 70 | sampleStrings = fakeStrings(10) 71 | sampleErrors = fakeErrors(10) 72 | sampleTimes = []time.Time{ 73 | time.Unix(0, 0), 74 | time.Unix(1, 0), 75 | time.Unix(2, 0), 76 | time.Unix(3, 0), 77 | time.Unix(4, 0), 78 | time.Unix(5, 0), 79 | time.Unix(6, 0), 80 | time.Unix(7, 0), 81 | time.Unix(8, 0), 82 | time.Unix(9, 0), 83 | } 84 | sampleDurations = []time.Duration{ 85 | 0 * time.Millisecond, 86 | 1 * time.Millisecond, 87 | 2 * time.Millisecond, 88 | 3 * time.Millisecond, 89 | 4 * time.Millisecond, 90 | 5 * time.Millisecond, 91 | 6 * time.Millisecond, 92 | 7 * time.Millisecond, 93 | 8 * time.Millisecond, 94 | 9 * time.Millisecond, 95 | } 96 | sampleObjects = objs{ 97 | &obj{"a", 1, true}, 98 | &obj{"b", 2, false}, 99 | &obj{"c", 3, true}, 100 | &obj{"d", 4, false}, 101 | &obj{"e", 5, true}, 102 | &obj{"f", 6, false}, 103 | &obj{"g", 7, true}, 104 | &obj{"h", 8, false}, 105 | &obj{"i", 9, true}, 106 | &obj{"j", 0, false}, 107 | } 108 | sampleString = "some string with a somewhat realistic length" 109 | sampleStringComplex = "some string with \"special ❤️ chars\" and somewhat realistic length" 110 | sampleFormat = "Test format %d %f %s" 111 | sampleFormatArgs = []interface{}{1, 1.0, sampleString} 112 | sampleContext = map[string]interface{}{ 113 | "ctx_int": sampleInts[0], 114 | "ctx_string": sampleStrings[0], 115 | "ctx_error": sampleErrors[0], 116 | "ctx_object": sampleObjects[0], 117 | } 118 | ) 119 | 120 | func fakeStrings(n int) []string { 121 | strs := make([]string, n) 122 | for i := range strs { 123 | strs[i] = fmt.Sprintf("%s %d", sampleString, i) 124 | } 125 | return strs 126 | } 127 | 128 | func fakeErrors(n int) []error { 129 | errs := make([]error, n) 130 | for i := range errs { 131 | errs[i] = fmt.Errorf("sample error number %d", i) 132 | } 133 | return errs 134 | } 135 | 136 | func Benchmark(b *testing.B) { 137 | for name, t := range tests { 138 | b.Run(name, func(b *testing.B) { 139 | b.Run("Enabled", func(b *testing.B) { 140 | l := t.newLogger(ioutil.Discard, false) 141 | benchmarkContext(b, l) 142 | }) 143 | b.Run("Disabled", func(b *testing.B) { 144 | l := t.newLogger(ioutil.Discard, true) 145 | benchmarkContext(b, l) 146 | }) 147 | }) 148 | } 149 | } 150 | 151 | func benchmarkContext(b *testing.B, l logTester) { 152 | b.Run("NoContext", func(b *testing.B) { 153 | benchmark(b, l) 154 | }) 155 | if l, supported := l.withContext(sampleContext); supported { 156 | b.Run("WithContext", func(b *testing.B) { 157 | benchmark(b, l) 158 | }) 159 | } 160 | } 161 | 162 | func benchmark(b *testing.B, l logTester) { 163 | b.Run("Msg", func(b *testing.B) { 164 | b.RunParallel(func(pb *testing.PB) { 165 | for pb.Next() { 166 | l.logMsg(sampleString) 167 | } 168 | }) 169 | }) 170 | b.Run("MsgComplex", func(b *testing.B) { 171 | b.RunParallel(func(pb *testing.PB) { 172 | for pb.Next() { 173 | l.logMsg(sampleStringComplex) 174 | } 175 | }) 176 | }) 177 | if l.logFormat(sampleFormat, sampleFormatArgs...) { 178 | b.Run("Formatting", func(b *testing.B) { 179 | b.RunParallel(func(pb *testing.PB) { 180 | for pb.Next() { 181 | l.logFormat(sampleFormat, sampleFormatArgs...) 182 | } 183 | }) 184 | }) 185 | } 186 | b.Run("Fields", func(b *testing.B) { 187 | benchmarkFields(b, l) 188 | }) 189 | } 190 | 191 | func benchmarkFields(b *testing.B, l logTester) { 192 | bs := map[string]func(){} 193 | if l.logBool(sampleString, "bool", sampleBools[0]) { 194 | bs["Bool"] = func() { 195 | l.logBool(sampleString, "bool", sampleBools[0]) 196 | } 197 | } 198 | if l.logInt(sampleString, "int", sampleInts[0]) { 199 | bs["Int"] = func() { 200 | l.logInt(sampleString, "int", sampleInts[0]) 201 | } 202 | } 203 | if l.logFloat32(sampleString, "float32", sampleFloats32[0]) { 204 | bs["Float32"] = func() { 205 | l.logFloat32(sampleString, "float32", sampleFloats32[0]) 206 | } 207 | } 208 | if l.logFloat64(sampleString, "float64", sampleFloats64[0]) { 209 | bs["Float64"] = func() { 210 | l.logFloat64(sampleString, "float64", sampleFloats64[0]) 211 | } 212 | } 213 | if l.logTime(sampleString, "time", sampleTimes[0]) { 214 | bs["Time"] = func() { 215 | l.logTime(sampleString, "time", sampleTimes[0]) 216 | } 217 | } 218 | if l.logDuration(sampleString, "duration", sampleDurations[0]) { 219 | bs["Time"] = func() { 220 | l.logDuration(sampleString, "duration", sampleDurations[0]) 221 | } 222 | } 223 | if l.logString(sampleString, "string", sampleStrings[0]) { 224 | bs["String"] = func() { 225 | l.logString(sampleString, "string", sampleStrings[0]) 226 | } 227 | } 228 | if l.logError(sampleString, "error", sampleErrors[0]) { 229 | bs["Error"] = func() { 230 | l.logError(sampleString, "error", sampleErrors[0]) 231 | } 232 | } 233 | if l.logObject(sampleString, "object", sampleObjects[0]) { 234 | bs["Object"] = func() { 235 | l.logObject(sampleString, "object", sampleObjects[0]) 236 | } 237 | } 238 | 239 | if l, ok := l.(logTesterArray); ok { 240 | if l.logBools(sampleString, "bools", sampleBools) { 241 | bs["Bools"] = func() { 242 | l.logBools(sampleString, "bools", sampleBools) 243 | } 244 | } 245 | if l.logInts(sampleString, "ints", sampleInts) { 246 | bs["Ints"] = func() { 247 | l.logInts(sampleString, "ints", sampleInts) 248 | } 249 | } 250 | if l.logFloats32(sampleString, "floats32", sampleFloats32) { 251 | bs["Floats32"] = func() { 252 | l.logFloats32(sampleString, "floats32", sampleFloats32) 253 | } 254 | } 255 | if l.logFloats64(sampleString, "floats64", sampleFloats64) { 256 | bs["Floats64"] = func() { 257 | l.logFloats64(sampleString, "floats64", sampleFloats64) 258 | } 259 | } 260 | if l.logTimes(sampleString, "time", sampleTimes) { 261 | bs["Time"] = func() { 262 | l.logTimes(sampleString, "time", sampleTimes) 263 | } 264 | } 265 | if l.logDurations(sampleString, "duration", sampleDurations) { 266 | bs["Time"] = func() { 267 | l.logDurations(sampleString, "duration", sampleDurations) 268 | } 269 | } 270 | if l.logStrings(sampleString, "strings", sampleStrings) { 271 | bs["Strings"] = func() { 272 | l.logStrings(sampleString, "strings", sampleStrings) 273 | } 274 | } 275 | if l.logErrors(sampleString, "errors", sampleErrors) { 276 | bs["Errors"] = func() { 277 | l.logErrors(sampleString, "errors", sampleErrors) 278 | } 279 | } 280 | } 281 | 282 | b.ResetTimer() 283 | for name, f := range bs { 284 | b.Run(name, func(b *testing.B) { 285 | b.RunParallel(func(pb *testing.PB) { 286 | for pb.Next() { 287 | f() 288 | } 289 | }) 290 | }) 291 | } 292 | } 293 | 294 | type testCtx struct { 295 | tester logTester 296 | buf *bytes.Buffer 297 | context map[string]interface{} 298 | enabled bool 299 | } 300 | 301 | func Test(t *testing.T) { 302 | for name, lt := range tests { 303 | t.Run(name, func(t *testing.T) { 304 | t.Run("Enabled", func(t *testing.T) { 305 | buf := &bytes.Buffer{} 306 | ctx := &testCtx{ 307 | tester: lt.newLogger(buf, false), 308 | buf: buf, 309 | enabled: true, 310 | } 311 | testContext(t, ctx) 312 | }) 313 | t.Run("Disabled", func(t *testing.T) { 314 | buf := &bytes.Buffer{} 315 | ctx := &testCtx{ 316 | tester: lt.newLogger(buf, true), 317 | buf: buf, 318 | enabled: false, 319 | } 320 | testContext(t, ctx) 321 | }) 322 | }) 323 | } 324 | } 325 | 326 | func testContext(t *testing.T, ctx *testCtx) { 327 | t.Run("NoContext", func(t *testing.T) { 328 | test(t, ctx) 329 | }) 330 | if l, supported := ctx.tester.withContext(sampleContext); supported { 331 | t.Run("WithContext", func(t *testing.T) { 332 | ctx.context = sampleContext 333 | ctx.tester = l 334 | test(t, ctx) 335 | }) 336 | } 337 | } 338 | 339 | func test(t *testing.T, ctx *testCtx) { 340 | ctx.tester.logMsg(sampleString) 341 | validate(t, ctx, "Msg", true, map[string]interface{}{"message": sampleString}) 342 | ctx.tester.logMsg(sampleStringComplex) 343 | validate(t, ctx, "MsgComplex", true, map[string]interface{}{"message": sampleStringComplex}) 344 | validate(t, ctx, "Formatting", 345 | ctx.tester.logFormat(sampleFormat, sampleFormatArgs...), 346 | map[string]interface{}{"message": fmt.Sprintf(sampleFormat, sampleFormatArgs...)}) 347 | t.Run("Fields", func(t *testing.T) { 348 | testFields(t, ctx) 349 | }) 350 | } 351 | 352 | func testFields(t *testing.T, ctx *testCtx) { 353 | l := ctx.tester 354 | validate(t, ctx, "Bool", 355 | l.logBool(sampleString, "bool", sampleBools[0]), 356 | map[string]interface{}{"message": sampleString, "bool": sampleBools[0]}) 357 | validate(t, ctx, "Int", 358 | l.logInt(sampleString, "int", sampleInts[0]), 359 | map[string]interface{}{"message": sampleString, "int": sampleInts[0]}) 360 | validate(t, ctx, "Float32", 361 | l.logFloat32(sampleString, "float32", sampleFloats32[0]), 362 | map[string]interface{}{"message": sampleString, "float32": sampleFloats32[0]}) 363 | validate(t, ctx, "Float64", 364 | l.logFloat64(sampleString, "float64", sampleFloats64[0]), 365 | map[string]interface{}{"message": sampleString, "float64": sampleFloats64[0]}) 366 | validate(t, ctx, "Time", 367 | l.logTime(sampleString, "time", sampleTimes[0]), 368 | map[string]interface{}{"message": sampleString, "time": sampleTimes[0]}) 369 | validate(t, ctx, "Duration", 370 | l.logDuration(sampleString, "duration", sampleDurations[0]), 371 | map[string]interface{}{"message": sampleString, "duration": sampleDurations[0]}) 372 | validate(t, ctx, "String", 373 | l.logString(sampleString, "string", sampleStrings[0]), 374 | map[string]interface{}{"message": sampleString, "string": sampleStrings[0]}) 375 | validate(t, ctx, "Error", 376 | l.logError(sampleString, "error", sampleErrors[0]), 377 | map[string]interface{}{"message": sampleString, "error": sampleErrors[0]}) 378 | validate(t, ctx, "Object", 379 | l.logObject(sampleString, "object", sampleObjects[0]), 380 | map[string]interface{}{"message": sampleString, "object": sampleObjects[0]}) 381 | 382 | if l, ok := l.(logTesterArray); ok { 383 | validate(t, ctx, "Bools", 384 | l.logBools(sampleString, "bools", sampleBools), 385 | map[string]interface{}{"message": sampleString, "bools": sampleBools}) 386 | validate(t, ctx, "Ints", 387 | l.logInts(sampleString, "ints", sampleInts), 388 | map[string]interface{}{"message": sampleString, "ints": sampleInts}) 389 | validate(t, ctx, "Floats32", 390 | l.logFloats32(sampleString, "floats32", sampleFloats32), 391 | map[string]interface{}{"message": sampleString, "floats32": sampleFloats32}) 392 | validate(t, ctx, "Floats64", 393 | l.logFloats64(sampleString, "floats64", sampleFloats64), 394 | map[string]interface{}{"message": sampleString, "floats64": sampleFloats64}) 395 | validate(t, ctx, "Times", 396 | l.logTimes(sampleString, "time", sampleTimes), 397 | map[string]interface{}{"message": sampleString, "time": sampleTimes}) 398 | validate(t, ctx, "Durations", 399 | l.logDurations(sampleString, "duration", sampleDurations), 400 | map[string]interface{}{"message": sampleString, "duration": sampleDurations}) 401 | validate(t, ctx, "Strings", 402 | l.logStrings(sampleString, "strings", sampleStrings), 403 | map[string]interface{}{"message": sampleString, "strings": sampleStrings}) 404 | validate(t, ctx, "Errors", 405 | l.logErrors(sampleString, "errors", sampleErrors), 406 | map[string]interface{}{"message": sampleString, "errors": sampleErrors}) 407 | } 408 | } 409 | 410 | func validate(t *testing.T, ctx *testCtx, name string, supported bool, want map[string]interface{}) { 411 | t.Helper() 412 | defer ctx.buf.Reset() 413 | if !supported { 414 | return 415 | } 416 | t.Run(name, func(t *testing.T) { 417 | if !ctx.enabled { 418 | if ctx.buf.Len() != 0 { 419 | t.Errorf("wrote output while disabled: %v", ctx.buf.String()) 420 | } 421 | return 422 | } 423 | want["level"] = "info" 424 | for k, v := range ctx.context { 425 | want[k] = v 426 | } 427 | fixTypes(want) 428 | wj, _ := json.Marshal(want) 429 | want = map[string]interface{}{} 430 | json.Unmarshal(wj, &want) 431 | var got map[string]interface{} 432 | if err := json.Unmarshal(ctx.buf.Bytes(), &got); err != nil { 433 | t.Fatalf("invalid JSON output: %v: %v", err, ctx.buf.String()) 434 | } 435 | if eq, err := checkers.DeepEqual(got, want); !eq { 436 | t.Errorf("invalid output: %v\ngot: %s\nwant: %s", err, ctx.buf.String(), wj) 437 | } 438 | }) 439 | } 440 | 441 | func fixTypes(m map[string]interface{}) { 442 | for k, v := range m { 443 | switch v := v.(type) { 444 | case *obj: 445 | m[k] = v.jsonMap() 446 | case map[string]interface{}: 447 | fixTypes(v) 448 | case time.Time: 449 | m[k] = v.Unix() 450 | case []time.Time: 451 | v2 := make([]int64, len(v)) 452 | for i, t := range v { 453 | v2[i] = t.Unix() 454 | } 455 | m[k] = v2 456 | case time.Duration: 457 | m[k] = v / time.Millisecond 458 | case []time.Duration: 459 | v2 := make([]int64, len(v)) 460 | for i, d := range v { 461 | v2[i] = int64(d / time.Millisecond) 462 | } 463 | m[k] = v2 464 | case error: 465 | m[k] = v.Error() 466 | case []error: 467 | v2 := make([]string, len(v)) 468 | for i, e := range v { 469 | v2[i] = e.Error() 470 | } 471 | m[k] = v2 472 | } 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /slog_test.go: -------------------------------------------------------------------------------- 1 | package logbench 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log/slog" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func init() { 13 | tests["slog"] = slogTester{} 14 | } 15 | 16 | type slogTester struct { 17 | l slog.Logger 18 | } 19 | 20 | var ( 21 | _ logTesterArray = (*slogTester)(nil) 22 | ) 23 | 24 | func (slogTester) newLogger(out io.Writer, disabled bool) logTester { 25 | lvl := slog.LevelInfo 26 | testFixup := func(groups []string, a slog.Attr) slog.Attr { 27 | switch a.Key { 28 | case "time": 29 | return slog.Attr{} // discard timestamps 30 | case "replaceTime-time": 31 | return slog.Attr{Key: "time", Value: slog.Int64Value(a.Value.Time().Unix())} 32 | case "replaceTimes-time": 33 | anyTimes := a.Value.Any().([]time.Time) 34 | anyUnixTimes := make([]int64, len(anyTimes)) 35 | for i, anyTime := range anyTimes { 36 | anyUnixTimes[i] = anyTime.Unix() 37 | } 38 | return slog.Attr{Key: "time", Value: slog.AnyValue(anyUnixTimes)} 39 | case "duration": 40 | anyValue := a.Value.Any() 41 | switch anyValue := anyValue.(type) { 42 | case time.Duration: 43 | return slog.Attr{Key: "duration", Value: slog.IntValue(int(anyValue.Milliseconds()))} 44 | case []time.Duration: 45 | anyDurations := anyValue 46 | anyIntDurations := make([]int, len(anyDurations)) 47 | for i, anyTime := range anyDurations { 48 | anyIntDurations[i] = int(anyTime.Milliseconds()) 49 | } 50 | return slog.Attr{Key: "duration", Value: slog.AnyValue(anyIntDurations)} 51 | default: 52 | return a 53 | } 54 | case "level": 55 | lowerValue := slog.StringValue(strings.ToLower(a.Value.String())) 56 | return slog.Attr{Key: a.Key, Value: lowerValue} 57 | case "msg": 58 | return slog.Attr{Key: "message", Value: a.Value} 59 | case "errors": 60 | valueErrors := a.Value.Any().([]error) 61 | errorStrings := make([]string, len(valueErrors)) 62 | for i, err := range valueErrors { 63 | errorStrings[i] = err.Error() 64 | } 65 | return slog.Attr{Key: "errors", Value: slog.AnyValue(errorStrings)} 66 | default: 67 | return a 68 | } 69 | } 70 | var handler slog.Handler = slog.NewJSONHandler(out, &slog.HandlerOptions{Level: lvl, ReplaceAttr: testFixup}) 71 | if disabled { 72 | handler = disabledHandler{} 73 | } 74 | return slogTester{*slog.New(handler)} 75 | } 76 | 77 | func (t slogTester) logMsg(msg string) { 78 | t.l.Info(msg) 79 | } 80 | 81 | func (t slogTester) logFormat(format string, v ...interface{}) bool { 82 | t.l.Info(fmt.Sprintf(format, v...)) 83 | return true 84 | } 85 | 86 | func (t slogTester) withContext(context map[string]interface{}) (logTester, bool) { 87 | var args = make([]any, len(context)*2) 88 | index := 0 89 | for s, i := range context { 90 | args[index] = s 91 | args[index+1] = i 92 | index += 2 93 | } 94 | return slogTester{*t.l.With(args...)}, true 95 | } 96 | 97 | func (t slogTester) logBool(msg, key string, value bool) bool { 98 | t.l.Info(msg, key, value) 99 | return true 100 | } 101 | 102 | func (t slogTester) logInt(msg, key string, value int) bool { 103 | t.l.Info(msg, key, value) 104 | return true 105 | } 106 | 107 | func (t slogTester) logFloat32(msg, key string, value float32) bool { 108 | t.l.Info(msg, key, value) 109 | return true 110 | } 111 | 112 | func (t slogTester) logFloat64(msg, key string, value float64) bool { 113 | t.l.Info(msg, key, value) 114 | return true 115 | } 116 | 117 | func (t slogTester) logTime(msg, key string, value time.Time) bool { 118 | t.l.Info(msg, "replaceTime-"+key, value) 119 | return true 120 | } 121 | 122 | func (t slogTester) logDuration(msg, key string, value time.Duration) bool { 123 | t.l.Info(msg, key, value) 124 | return true 125 | } 126 | 127 | func (t slogTester) logError(msg, key string, value error) bool { 128 | t.l.Info(msg, key, value) 129 | return true 130 | } 131 | 132 | func (t slogTester) logString(msg, key string, value string) bool { 133 | t.l.Info(msg, key, value) 134 | return true 135 | } 136 | 137 | func (t slogTester) logObject(msg, key string, value *obj) bool { 138 | t.l.Info(msg, key, value) 139 | return true 140 | } 141 | 142 | func (t slogTester) logBools(msg, key string, value []bool) bool { 143 | t.l.Info(msg, key, value) 144 | return true 145 | } 146 | 147 | func (t slogTester) logInts(msg, key string, value []int) bool { 148 | t.l.Info(msg, key, value) 149 | return true 150 | } 151 | 152 | func (t slogTester) logFloats32(msg, key string, value []float32) bool { 153 | t.l.Info(msg, key, value) 154 | return true 155 | } 156 | 157 | func (t slogTester) logFloats64(msg, key string, value []float64) bool { 158 | t.l.Info(msg, key, value) 159 | return true 160 | } 161 | 162 | func (t slogTester) logTimes(msg, key string, value []time.Time) bool { 163 | t.l.Info(msg, "replaceTimes-"+key, value) 164 | return true 165 | } 166 | 167 | func (t slogTester) logDurations(msg, key string, value []time.Duration) bool { 168 | t.l.Info(msg, key, value) 169 | return true 170 | } 171 | 172 | func (t slogTester) logErrors(msg, key string, value []error) bool { 173 | t.l.Info(msg, key, value) 174 | return true 175 | } 176 | 177 | func (t slogTester) logStrings(msg, key string, value []string) bool { 178 | t.l.Info(msg, key, value) 179 | return true 180 | } 181 | 182 | type disabledHandler struct { 183 | } 184 | 185 | func (d disabledHandler) Enabled(ctx context.Context, level slog.Level) bool { 186 | return false 187 | } 188 | 189 | func (d disabledHandler) Handle(ctx context.Context, record slog.Record) error { 190 | return nil 191 | } 192 | 193 | func (d disabledHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 194 | return d 195 | } 196 | 197 | func (d disabledHandler) WithGroup(name string) slog.Handler { 198 | return d 199 | } 200 | -------------------------------------------------------------------------------- /zap_test.go: -------------------------------------------------------------------------------- 1 | package logbench 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "time" 8 | 9 | "go.uber.org/zap" 10 | "go.uber.org/zap/zapcore" 11 | "go.uber.org/zap/zaptest" 12 | ) 13 | 14 | func init() { 15 | tests["Zap"] = zapTester{} 16 | } 17 | 18 | func (o *obj) MarshalLogObject(enc zapcore.ObjectEncoder) error { 19 | enc.AddString("name", o.Name) 20 | enc.AddInt("count", o.Count) 21 | enc.AddBool("enabled", o.Enabled) 22 | return nil 23 | } 24 | 25 | type zapTester struct { 26 | l *zap.Logger 27 | } 28 | 29 | var ( 30 | _ logTesterArray = (*zapTester)(nil) 31 | ) 32 | 33 | func zapMillisecondDurationEncoder(d time.Duration, enc zapcore.PrimitiveArrayEncoder) { 34 | enc.AppendFloat64(float64(d) / float64(time.Millisecond)) 35 | } 36 | 37 | func zapEncoder() zapcore.Encoder { 38 | ec := zap.NewProductionEncoderConfig() 39 | ec.EncodeDuration = zapMillisecondDurationEncoder 40 | ec.EncodeTime = zapcore.EpochTimeEncoder 41 | ec.TimeKey = "" 42 | ec.MessageKey = "message" 43 | return zapcore.NewJSONEncoder(ec) 44 | } 45 | 46 | func (zapTester) newLogger(out io.Writer, disabled bool) logTester { 47 | lvl := zap.DebugLevel 48 | if disabled { 49 | lvl = zap.FatalLevel 50 | } 51 | var w zapcore.WriteSyncer = &zaptest.Discarder{} 52 | if out != ioutil.Discard { 53 | w = zapcore.AddSync(out) 54 | } 55 | return zapTester{zap.New(zapcore.NewCore(zapEncoder(), w, lvl))} 56 | } 57 | 58 | func (t zapTester) logMsg(msg string) { 59 | t.l.Info(msg) 60 | } 61 | 62 | func (t zapTester) logFormat(format string, v ...interface{}) bool { 63 | return false 64 | } 65 | 66 | func (t zapTester) withContext(context map[string]interface{}) (logTester, bool) { 67 | l := t.l 68 | for k, v := range context { 69 | switch v := v.(type) { 70 | case int: 71 | l = l.With(zap.Int(k, v)) 72 | case string: 73 | l = l.With(zap.String(k, v)) 74 | case error: 75 | l = l.With(zap.NamedError(k, v)) 76 | case time.Time: 77 | l = l.With(zap.Time(k, v)) 78 | case *obj: 79 | l = l.With(zap.Object(k, v)) 80 | default: 81 | panic(fmt.Sprintf("zap: unsupported context field type: %v", v)) 82 | } 83 | } 84 | return zapTester{l}, true 85 | } 86 | 87 | func (t zapTester) logBool(msg, key string, value bool) bool { 88 | t.l.Info(msg, zap.Bool(key, value)) 89 | return true 90 | } 91 | 92 | func (t zapTester) logInt(msg, key string, value int) bool { 93 | t.l.Info(msg, zap.Int(key, value)) 94 | return true 95 | } 96 | 97 | func (t zapTester) logFloat32(msg, key string, value float32) bool { 98 | t.l.Info(msg, zap.Float32(key, value)) 99 | return true 100 | } 101 | 102 | func (t zapTester) logFloat64(msg, key string, value float64) bool { 103 | t.l.Info(msg, zap.Float64(key, value)) 104 | return true 105 | } 106 | 107 | func (t zapTester) logTime(msg, key string, value time.Time) bool { 108 | t.l.Info(msg, zap.Time(key, value)) 109 | return true 110 | } 111 | 112 | func (t zapTester) logDuration(msg, key string, value time.Duration) bool { 113 | t.l.Info(msg, zap.Duration(key, value)) 114 | return true 115 | } 116 | 117 | func (t zapTester) logError(msg, key string, value error) bool { 118 | t.l.Info(msg, zap.NamedError(key, value)) 119 | return true 120 | } 121 | 122 | func (t zapTester) logString(msg, key string, value string) bool { 123 | t.l.Info(msg, zap.String(key, value)) 124 | return true 125 | } 126 | 127 | func (t zapTester) logObject(msg, key string, value *obj) bool { 128 | t.l.Info(msg, zap.Object(key, value)) 129 | return true 130 | } 131 | 132 | func (t zapTester) logBools(msg, key string, value []bool) bool { 133 | t.l.Info(msg, zap.Bools(key, value)) 134 | return true 135 | } 136 | 137 | func (t zapTester) logInts(msg, key string, value []int) bool { 138 | t.l.Info(msg, zap.Ints(key, value)) 139 | return true 140 | } 141 | 142 | func (t zapTester) logFloats32(msg, key string, value []float32) bool { 143 | t.l.Info(msg, zap.Float32s(key, value)) 144 | return true 145 | } 146 | 147 | func (t zapTester) logFloats64(msg, key string, value []float64) bool { 148 | t.l.Info(msg, zap.Float64s(key, value)) 149 | return true 150 | } 151 | 152 | func (t zapTester) logTimes(msg, key string, value []time.Time) bool { 153 | t.l.Info(msg, zap.Times(key, value)) 154 | return true 155 | } 156 | 157 | func (t zapTester) logDurations(msg, key string, value []time.Duration) bool { 158 | t.l.Info(msg, zap.Durations(key, value)) 159 | return true 160 | } 161 | 162 | func (t zapTester) logErrors(msg, key string, value []error) bool { 163 | return false 164 | } 165 | 166 | func (t zapTester) logStrings(msg, key string, value []string) bool { 167 | t.l.Info(msg, zap.Strings(key, value)) 168 | return true 169 | } 170 | -------------------------------------------------------------------------------- /zapsugar_test.go: -------------------------------------------------------------------------------- 1 | package logbench 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "time" 8 | 9 | "go.uber.org/zap" 10 | "go.uber.org/zap/zapcore" 11 | "go.uber.org/zap/zaptest" 12 | ) 13 | 14 | func init() { 15 | tests["ZapSugar"] = zapSugarTester{} 16 | } 17 | 18 | type zapSugarTester struct { 19 | l *zap.SugaredLogger 20 | } 21 | 22 | var ( 23 | _ logTesterArray = (*zapSugarTester)(nil) 24 | ) 25 | 26 | func (zapSugarTester) newLogger(out io.Writer, disabled bool) logTester { 27 | // TOFIX: use out 28 | lvl := zap.DebugLevel 29 | if disabled { 30 | lvl = zap.FatalLevel 31 | } 32 | var w zapcore.WriteSyncer = &zaptest.Discarder{} 33 | if out != ioutil.Discard { 34 | w = zapcore.AddSync(out) 35 | } 36 | return zapSugarTester{zap.New(zapcore.NewCore(zapEncoder(), w, lvl)).Sugar()} 37 | } 38 | 39 | func (t zapSugarTester) logMsg(msg string) { 40 | t.l.Info(msg) 41 | } 42 | 43 | func (t zapSugarTester) logFormat(format string, v ...interface{}) bool { 44 | t.l.Infof(format, v...) 45 | return true 46 | } 47 | 48 | func (t zapSugarTester) withContext(context map[string]interface{}) (logTester, bool) { 49 | l := t.l 50 | for k, v := range context { 51 | switch v := v.(type) { 52 | case int: 53 | l = l.With(zap.Int(k, v)) 54 | case string: 55 | l = l.With(zap.String(k, v)) 56 | case error: 57 | l = l.With(zap.NamedError(k, v)) 58 | case time.Time: 59 | l = l.With(zap.Time(k, v)) 60 | case *obj: 61 | l = l.With(zap.Object(k, v)) 62 | default: 63 | panic(fmt.Sprintf("zap: unsupported context field type: %v", v)) 64 | } 65 | } 66 | return zapSugarTester{l}, true 67 | } 68 | 69 | func (t zapSugarTester) logBool(msg, key string, value bool) bool { 70 | t.l.Infow(msg, key, value) 71 | return true 72 | } 73 | 74 | func (t zapSugarTester) logInt(msg, key string, value int) bool { 75 | t.l.Infow(msg, key, value) 76 | return true 77 | } 78 | 79 | func (t zapSugarTester) logFloat32(msg, key string, value float32) bool { 80 | t.l.Infow(msg, key, value) 81 | return true 82 | } 83 | 84 | func (t zapSugarTester) logFloat64(msg, key string, value float64) bool { 85 | t.l.Infow(msg, key, value) 86 | return true 87 | } 88 | 89 | func (t zapSugarTester) logTime(msg, key string, value time.Time) bool { 90 | t.l.Infow(msg, key, value) 91 | return true 92 | } 93 | 94 | func (t zapSugarTester) logDuration(msg, key string, value time.Duration) bool { 95 | t.l.Infow(msg, key, value) 96 | return true 97 | } 98 | 99 | func (t zapSugarTester) logError(msg, key string, value error) bool { 100 | t.l.Infow(msg, key, value) 101 | return true 102 | } 103 | 104 | func (t zapSugarTester) logString(msg, key string, value string) bool { 105 | t.l.Infow(msg, key, value) 106 | return true 107 | } 108 | 109 | func (t zapSugarTester) logObject(msg, key string, value *obj) bool { 110 | t.l.Infow(msg, key, value) 111 | return true 112 | } 113 | 114 | func (t zapSugarTester) logBools(msg, key string, value []bool) bool { 115 | t.l.Infow(msg, key, value) 116 | return true 117 | } 118 | 119 | func (t zapSugarTester) logInts(msg, key string, value []int) bool { 120 | t.l.Infow(msg, key, value) 121 | return true 122 | } 123 | 124 | func (t zapSugarTester) logFloats32(msg, key string, value []float32) bool { 125 | t.l.Infow(msg, key, value) 126 | return true 127 | } 128 | 129 | func (t zapSugarTester) logFloats64(msg, key string, value []float64) bool { 130 | t.l.Infow(msg, key, value) 131 | return true 132 | } 133 | 134 | func (t zapSugarTester) logTimes(msg, key string, value []time.Time) bool { 135 | t.l.Infow(msg, key, value) 136 | return true 137 | } 138 | 139 | func (t zapSugarTester) logDurations(msg, key string, value []time.Duration) bool { 140 | t.l.Infow(msg, key, value) 141 | return true 142 | } 143 | 144 | func (t zapSugarTester) logErrors(msg, key string, value []error) bool { 145 | return false 146 | } 147 | 148 | func (t zapSugarTester) logStrings(msg, key string, value []string) bool { 149 | t.l.Infow(msg, key, value) 150 | return true 151 | } 152 | -------------------------------------------------------------------------------- /zerolog_test.go: -------------------------------------------------------------------------------- 1 | package logbench 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/rs/zerolog" 8 | ) 9 | 10 | func init() { 11 | zerolog.TimeFieldFormat = "" 12 | zerolog.DurationFieldInteger = true 13 | zerolog.MessageFieldName = "message" 14 | 15 | tests["Zerolog"] = zerologTester{} 16 | } 17 | 18 | func (o obj) MarshalZerologObject(e *zerolog.Event) { 19 | e.Str("name", o.Name). 20 | Int("count", o.Count). 21 | Bool("enabled", o.Enabled) 22 | } 23 | 24 | type zerologTester struct { 25 | l zerolog.Logger 26 | } 27 | 28 | var ( 29 | _ logTesterArray = (*zerologTester)(nil) 30 | ) 31 | 32 | func (zerologTester) newLogger(out io.Writer, disabled bool) logTester { 33 | lvl := zerolog.DebugLevel 34 | if disabled { 35 | lvl = zerolog.Disabled 36 | } 37 | return zerologTester{zerolog.New(out).Level(lvl)} 38 | } 39 | 40 | func (t zerologTester) logMsg(msg string) { 41 | t.l.Info().Msg(msg) 42 | } 43 | 44 | func (t zerologTester) logFormat(format string, v ...interface{}) bool { 45 | t.l.Info().Msgf(format, v...) 46 | return true 47 | } 48 | 49 | func (t zerologTester) withContext(context map[string]interface{}) (logTester, bool) { 50 | return zerologTester{t.l.With().Fields(context).Logger()}, true 51 | } 52 | 53 | func (t zerologTester) logBool(msg, key string, value bool) bool { 54 | t.l.Info().Bool(key, value).Msg(msg) 55 | return true 56 | } 57 | 58 | func (t zerologTester) logInt(msg, key string, value int) bool { 59 | t.l.Info().Int(key, value).Msg(msg) 60 | return true 61 | } 62 | 63 | func (t zerologTester) logFloat32(msg, key string, value float32) bool { 64 | t.l.Info().Float32(key, value).Msg(msg) 65 | return true 66 | } 67 | 68 | func (t zerologTester) logFloat64(msg, key string, value float64) bool { 69 | t.l.Info().Float64(key, value).Msg(msg) 70 | return true 71 | } 72 | 73 | func (t zerologTester) logTime(msg, key string, value time.Time) bool { 74 | t.l.Info().Time(key, value).Msg(msg) 75 | return true 76 | } 77 | 78 | func (t zerologTester) logDuration(msg, key string, value time.Duration) bool { 79 | t.l.Info().Dur(key, value).Msg(msg) 80 | return true 81 | } 82 | 83 | func (t zerologTester) logError(msg, key string, value error) bool { 84 | t.l.Info().AnErr(key, value).Msg(msg) 85 | return true 86 | } 87 | 88 | func (t zerologTester) logString(msg, key string, value string) bool { 89 | t.l.Info().Str(key, value).Msg(msg) 90 | return true 91 | } 92 | 93 | func (t zerologTester) logObject(msg, key string, value *obj) bool { 94 | t.l.Info().Object(key, value).Msg(msg) 95 | return true 96 | } 97 | 98 | func (t zerologTester) logBools(msg, key string, value []bool) bool { 99 | t.l.Info().Bools(key, value).Msg(msg) 100 | return true 101 | } 102 | 103 | func (t zerologTester) logInts(msg, key string, value []int) bool { 104 | t.l.Info().Ints(key, value).Msg(msg) 105 | return true 106 | } 107 | 108 | func (t zerologTester) logFloats32(msg, key string, value []float32) bool { 109 | t.l.Info().Floats32(key, value).Msg(msg) 110 | return true 111 | } 112 | 113 | func (t zerologTester) logFloats64(msg, key string, value []float64) bool { 114 | t.l.Info().Floats64(key, value).Msg(msg) 115 | return true 116 | } 117 | 118 | func (t zerologTester) logTimes(msg, key string, value []time.Time) bool { 119 | t.l.Info().Times(key, value).Msg(msg) 120 | return true 121 | } 122 | 123 | func (t zerologTester) logDurations(msg, key string, value []time.Duration) bool { 124 | t.l.Info().Durs(key, value).Msg(msg) 125 | return true 126 | } 127 | 128 | func (t zerologTester) logErrors(msg, key string, value []error) bool { 129 | t.l.Info().Errs(key, value).Msg(msg) 130 | return true 131 | } 132 | 133 | func (t zerologTester) logStrings(msg, key string, value []string) bool { 134 | t.l.Info().Strs(key, value).Msg(msg) 135 | return true 136 | } 137 | --------------------------------------------------------------------------------