├── .travis.yml
├── LICENSE
├── README.md
├── benchmark
├── benchmark.go
├── benchmark_test.go
└── memoryWatcher.go
├── benchmark_results_visualization.numbers
├── flagList.go
├── go.mod
├── go.sum
├── logrus
└── logrus.go
├── main.go
├── main_test.go
├── phuslog
└── phuslog.go
├── printStatistics.go
├── results_graphs.png
├── zap
├── memorySink.go
└── zap.go
└── zerolog
└── zerolog.go
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - master
5 | - "1.13"
6 |
7 | install: true
8 |
9 | matrix:
10 | allow_failures:
11 | - go: master
12 | fast_finish: true
13 |
14 | notifications:
15 | email: true
16 |
17 | before_script:
18 | - GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
19 | - go get golang.org/x/lint/golint # Linter
20 | - go get golang.org/x/tools/cmd/cover
21 | - go get github.com/mattn/goveralls
22 | - go get github.com/go-playground/overalls
23 | - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0
24 |
25 | script:
26 | # go vet
27 | - go vet ./...
28 |
29 | # golangci-link
30 | - golangci-lint run ./...
31 |
32 | # golint
33 | - golint -set_exit_status $(go list ./...)
34 |
35 | # Run all the tests with the race detector enabled
36 | - overalls -project=github.com/globusdigital/logbench -covermode=atomic -debug -- -race -v -coverpkg=./...
37 | - $HOME/gopath/bin/goveralls -coverprofile=overalls.coverprofile -service=travis-ci -repotoken=$COVERALLS_TOKEN
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Magazine zum Globus AG
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Go - Structured Logging Benchmark
13 |
14 | by Magazine zum Globus
15 |
16 |
17 | A structured JSON logging performance benchmark providing realistic performance metrics for the latest versions of:
18 | - [sirupsen/logrus](https://github.com/sirupsen/logrus)
19 | - [uber/zap](https://github.com/uber-go/zap)
20 | - [rs/zerolog](https://github.com/rs/zerolog)
21 |
22 | Performance is measured by the following main criteria:
23 | - `total alloc` - Total size of allocated memory.
24 | - `num-gc` - Total number of GC cycles.
25 | - `mallocs` - Total number of allocated heap objects.
26 | - `total pause` - Total duration of GC pauses.
27 | - Average and total time of execution (by operation).
28 |
29 |
30 |
31 | 
32 | _i7-8569U @ 2.80GHz_
33 |
34 | ## Getting started
35 |
36 | 1. Install the benchmark:
37 | ```
38 | go get github.com/globusdigital/logbench
39 | ```
40 |
41 | 2. Run it:
42 | ```
43 | logbench -w 8 -t 1_000_000 -o_all -l zerolog
44 | ```
45 |
46 | - `-w `: defines the number of concurrently writing goroutines.
47 | - `-l `: enables a logger.
48 | You can enable multiple loggers by specifying multiple flags: `-l zerolog -l zap -l logrus`.
49 | - `-o `: enables an operation.
50 | You can enable multiple operations by specifying multiple flags: `-o info -o error -o info_with_3`.
51 | - `-t `: defines the number of logs to be written for each operation.
52 | - `-o_all`: enables all operations ignoring all specified `-o` flags
53 | - `-memprof `: specifies the output file path for the memory profile (disabled when not set)
54 | - `-mi `: memory inspection interval
55 |
56 | ## How-to
57 | ### Adding a new logger to the benchmark
58 | - 1. Define the logger in a sub-package.
59 | - 2. Provide a `Setup() benchmark.Setup` function in your logger's sub-package.
60 | - 3. Implement all benchmark operations:
61 | - `FnInfo func(msg string)`
62 | - `FnInfoFmt func(msg string, data int)`
63 | - `FnError func(msg string)`
64 | - `FnInfoWithErrorStack func(msg string, err error)`
65 | - `FnInfoWith3 func(msg string, fields *benchmark.Fields3)`
66 | - `FnInfoWith10 func(msg string, fields *benchmark.Fields10)`
67 | - `FnInfoWith10Exist func(msg string)`
68 | - 4. Add your setup to [`setups`](https://github.com/globusdigital/logbench/blob/eff659cfb1eb06b1d139db6735b2b2ce6944632c/main.go#L21).
69 | - 5. Run the tests with `go test -v -race ./...` and make sure everything's working.
70 |
--------------------------------------------------------------------------------
/benchmark/benchmark.go:
--------------------------------------------------------------------------------
1 | package benchmark
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "reflect"
8 | "sync"
9 | "sync/atomic"
10 | "time"
11 |
12 | "github.com/pkg/errors"
13 | )
14 |
15 | const (
16 | // LevelInfo represents the name of the info-level
17 | LevelInfo = "info"
18 |
19 | // LevelError represents the name of the error-level
20 | LevelError = "error"
21 |
22 | // LevelDebug represents the name of the debug-level
23 | LevelDebug = "debug"
24 |
25 | // FieldTime represents the name of the time field
26 | FieldTime = "time"
27 |
28 | // FieldLevel represents the name of the level field
29 | FieldLevel = "level"
30 |
31 | // FieldError represents the name of the error field
32 | FieldError = "error"
33 |
34 | // FieldMessage represents the name of the message field
35 | FieldMessage = "message"
36 |
37 | // LogOperationInfo represents the name of an info-log operation
38 | LogOperationInfo = "info"
39 |
40 | // LogOperationInfoFmt represents the name of an info-log operation
41 | // involving formatting
42 | LogOperationInfoFmt = "info_fmt"
43 |
44 | // LogOperationInfoWithErrorStack represents the name of an info-log
45 | // operation involving a stack-traced error value
46 | LogOperationInfoWithErrorStack = "info_with_error_stack"
47 |
48 | // LogOperationInfoWith3 represents the name of an info-log operation
49 | // involving 3 newly appended fields
50 | LogOperationInfoWith3 = "info_with_3"
51 |
52 | // LogOperationInfoWith10 represents the name of an info-log operation
53 | // involving 10 newly appended fields
54 | LogOperationInfoWith10 = "info_with_10"
55 |
56 | // LogOperationError represents the name of an error-log operation
57 | LogOperationError = "error"
58 |
59 | // LogOperationInfoWith10Exist represents the name of an info-log operation
60 | // involving 10 previously appended fields
61 | LogOperationInfoWith10Exist = "info_with_10_exist"
62 |
63 | // TimeFormat defines the time logging format
64 | TimeFormat = "2006-01-02T15:04:05.999999999-07:00"
65 | )
66 |
67 | // Fields3 is a list of 3 fields and their according values
68 | type Fields3 struct {
69 | Name1 string
70 | Name2 string
71 | Name3 string
72 |
73 | Value1 string
74 | Value2 int
75 | Value3 float64
76 | }
77 |
78 | // Fields10 is a list of 10 fields and their according values
79 | type Fields10 struct {
80 | Name1 string
81 | Name2 string
82 | Name3 string
83 | Name4 string
84 | Name5 string
85 | Name6 string
86 | Name7 string
87 | Name8 string
88 | Name9 string
89 | Name10 string
90 |
91 | Value1 string
92 | Value2 string
93 | Value3 string
94 | Value4 bool
95 | Value5 string
96 | Value6 int
97 | Value7 float64
98 | Value8 []string
99 | Value9 []int
100 | Value10 []float64
101 | }
102 |
103 | // NewFields3 creates a new instance of a set of 3 fields
104 | func NewFields3() *Fields3 {
105 | return &Fields3{
106 | Name1: "field1", Value1: "some textual value",
107 | Name2: "field_2_int", Value2: 42,
108 | Name3: "field_3_float_64", Value3: 42.5,
109 | }
110 | }
111 |
112 | // NewFields10 creates a new instance of a set of 10 fields
113 | func NewFields10() *Fields10 {
114 | return &Fields10{
115 | Name1: "field1", Value1: "",
116 | Name2: "field2", Value2: "some textual value",
117 | Name3: "field3", Value3: "and another textual value",
118 | Name4: "field4", Value4: true,
119 | Name5: "field5", Value5: "an even longer textual value",
120 | Name6: "field_6_int", Value6: 42,
121 | Name7: "field_7_float_64", Value7: 42.5,
122 | Name8: "field_8_multipleStrings", Value8: []string{
123 | "first",
124 | "second",
125 | "third",
126 | },
127 | Name9: "field_9_multipleIntegers", Value9: []int{
128 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
129 | },
130 | Name10: "field_10_multipleFloat64s", Value10: []float64{
131 | 11.5, 24.9, 99.99, 50.5001, 1000.11,
132 | },
133 | }
134 | }
135 |
136 | // FnInfo represents an info logging callback function
137 | type FnInfo func(msg string)
138 |
139 | // FnInfoFmt represents a formatted info logging callback function
140 | type FnInfoFmt func(msg string, data int)
141 |
142 | // FnError represents an error logging callback function
143 | type FnError func(msg string)
144 |
145 | // FnInfoWithErrorStack represents an info logging callback function
146 | // with a stack-traced error attached
147 | type FnInfoWithErrorStack func(msg string, err error)
148 |
149 | // FnInfoWith3 represents an info logging callback function
150 | // with 3 data fields attached
151 | type FnInfoWith3 func(msg string, fields *Fields3)
152 |
153 | // FnInfoWith10 represents an info logging callback function
154 | // with 10 data fields attached
155 | type FnInfoWith10 func(msg string, fields *Fields10)
156 |
157 | // FnInfoWith10Exist represents an info logging callback function
158 | // with 10 previously attached data fields
159 | type FnInfoWith10Exist func(msg string)
160 |
161 | // Setup defines the callback functions for all benchmarked cases
162 | type Setup struct {
163 | Info func(io.ReadWriter) (FnInfo, error)
164 | InfoFmt func(io.ReadWriter) (FnInfoFmt, error)
165 | Error func(io.ReadWriter) (FnError, error)
166 | InfoWithErrorStack func(io.ReadWriter) (FnInfoWithErrorStack, error)
167 | InfoWith3 func(io.ReadWriter) (FnInfoWith3, error)
168 | InfoWith10 func(io.ReadWriter) (FnInfoWith10, error)
169 | InfoWith10Exist func(io.ReadWriter) (FnInfoWith10Exist, error)
170 | }
171 |
172 | func checkSetupImplementation(setup Setup) error {
173 | vl := reflect.ValueOf(setup)
174 | tp := reflect.TypeOf(setup)
175 | for i := 0; i < vl.NumField(); i++ {
176 | if vl.Field(i).IsNil() {
177 | return fmt.Errorf(
178 | "missing implementation for Setup.%s",
179 | tp.Field(i).Name,
180 | )
181 | }
182 | }
183 | return nil
184 | }
185 |
186 | // New creates a new benchmark instance also initializing the logger
187 | func New(
188 | out io.ReadWriter,
189 | operation string,
190 | setup Setup,
191 | ) (*Benchmark, error) {
192 | if out == nil {
193 | out = os.Stdout
194 | }
195 |
196 | if err := checkSetupImplementation(setup); err != nil {
197 | return nil, fmt.Errorf("invalid logger implementation: %w", err)
198 | }
199 |
200 | bench := new(Benchmark)
201 |
202 | switch operation {
203 | case LogOperationInfo:
204 | fn, err := setup.Info(out)
205 | if err != nil {
206 | return nil, err
207 | }
208 | bench.writeLog = func() { fn("information") }
209 |
210 | case LogOperationInfoFmt:
211 | fn, err := setup.InfoFmt(out)
212 | if err != nil {
213 | return nil, err
214 | }
215 | bench.writeLog = func() { fn("information %d", 42) }
216 |
217 | case LogOperationInfoWithErrorStack:
218 | fn, err := setup.InfoWithErrorStack(out)
219 | if err != nil {
220 | return nil, err
221 | }
222 | errVal := errors.New("error with stack trace")
223 | bench.writeLog = func() { fn("information", errVal) }
224 |
225 | case LogOperationError:
226 | fn, err := setup.Error(out)
227 | if err != nil {
228 | return nil, err
229 | }
230 | bench.writeLog = func() { fn("error message") }
231 |
232 | case LogOperationInfoWith10Exist:
233 | fn, err := setup.InfoWith10Exist(out)
234 | if err != nil {
235 | return nil, err
236 | }
237 | bench.writeLog = func() { fn("information") }
238 |
239 | case LogOperationInfoWith3:
240 | fn, err := setup.InfoWith3(out)
241 | if err != nil {
242 | return nil, err
243 | }
244 | fields := NewFields3()
245 | bench.writeLog = func() { fn("information", fields) }
246 |
247 | case LogOperationInfoWith10:
248 | fn, err := setup.InfoWith10(out)
249 | if err != nil {
250 | return nil, err
251 | }
252 | fields := NewFields10()
253 | bench.writeLog = func() { fn("information", fields) }
254 |
255 | default:
256 | return nil, fmt.Errorf("unsupported operation: %q", operation)
257 | }
258 |
259 | return bench, nil
260 | }
261 |
262 | // Benchmark is a log benchmark
263 | type Benchmark struct {
264 | writeLog func()
265 | }
266 |
267 | // Statistics are the statistics of the execution of a benchmark
268 | type Statistics struct {
269 | TotalLogsWritten uint64
270 | TotalTime time.Duration
271 | }
272 |
273 | // Run runs the benchmark
274 | func (bench *Benchmark) Run(
275 | target uint64,
276 | concurrentWriters uint,
277 | stopped func() bool,
278 | ) Statistics {
279 | if stopped == nil {
280 | stopped = func() bool { return false }
281 | }
282 |
283 | // Execute benchmark
284 | start := time.Now()
285 | logsWritten := uint64(0)
286 |
287 | wg := sync.WaitGroup{}
288 | wg.Add(int(concurrentWriters))
289 | for wk := uint(0); wk < concurrentWriters; wk++ {
290 | go func() {
291 | defer wg.Done()
292 | for {
293 | if stopped() {
294 | break
295 | }
296 | if atomic.AddUint64(&logsWritten, 1) > target {
297 | break
298 | }
299 | // Write a log
300 | bench.writeLog()
301 | }
302 | }()
303 | }
304 | wg.Wait()
305 |
306 | timeTotal := time.Since(start)
307 |
308 | stats := Statistics{
309 | TotalLogsWritten: atomic.LoadUint64(&logsWritten),
310 | TotalTime: timeTotal,
311 | }
312 | stats.TotalLogsWritten -= uint64(concurrentWriters)
313 |
314 | return stats
315 | }
316 |
--------------------------------------------------------------------------------
/benchmark/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package benchmark_test
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/globusdigital/logbench/benchmark"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestSetupFunctionTypes(t *testing.T) {
13 | tp := reflect.TypeOf(benchmark.Setup{})
14 | for i := 0; i < tp.NumField(); i++ {
15 | fl := tp.Field(i)
16 | require.Equal(
17 | t,
18 | reflect.Func,
19 | fl.Type.Kind(),
20 | "Setup.%s type is not a function",
21 | fl.Name,
22 | )
23 |
24 | require.Equal(
25 | t,
26 | 1,
27 | fl.Type.NumIn(),
28 | "Setup.%s must accept io.ReadWriter as its first argument",
29 | fl.Name,
30 | )
31 |
32 | require.Equal(
33 | t,
34 | 1,
35 | fl.Type.NumIn(),
36 | "Setup.%s must accept io.ReadWriter as its first argument",
37 | fl.Name,
38 | )
39 |
40 | in1 := fl.Type.In(0)
41 | require.True(
42 | t,
43 | fmt.Sprintf("%s.%s", in1.PkgPath(), in1.Name()) == "io.ReadWriter",
44 | "Setup.%s must accept io.ReadWriter as its first argument",
45 | fl.Name,
46 | )
47 |
48 | require.Equal(
49 | t,
50 | 2,
51 | fl.Type.NumOut(),
52 | "Setup.%s must return (func(...), error)",
53 | fl.Name,
54 | )
55 |
56 | out1 := fl.Type.Out(0)
57 | require.True(
58 | t,
59 | reflect.Func == out1.Kind() && out1.NumOut() == 0,
60 | "Setup.%s must return (func(...), error)",
61 | fl.Name,
62 | )
63 |
64 | out2 := fl.Type.Out(1)
65 | require.True(
66 | t,
67 | out2.Kind() == reflect.Interface && out2.Name() == "error",
68 | "Setup.%s must return (func(...), error)",
69 | fl.Name,
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/benchmark/memoryWatcher.go:
--------------------------------------------------------------------------------
1 | package benchmark
2 |
3 | import (
4 | "context"
5 | "runtime"
6 | "time"
7 | )
8 |
9 | // MemStats represents memory related statistics
10 | type MemStats struct {
11 | StatSamples uint
12 | HeapAllocInc uint64
13 | HeapObjectsInc uint64
14 | HeapSysInc uint64
15 | MaxHeapAlloc uint64
16 | MaxHeapObjects uint64
17 | MaxHeapSys uint64
18 | }
19 |
20 | // StartMemoryWatcher starts a memory watcher goroutine
21 | // which periodically writes memory statistics to the returned channel
22 | func StartMemoryWatcher(
23 | ctx context.Context,
24 | interval time.Duration,
25 | ) (read chan MemStats) {
26 | read = make(chan MemStats)
27 | go func() {
28 | ticker := time.NewTicker(interval)
29 | defer ticker.Stop()
30 |
31 | var stats MemStats
32 | var runtimeStats runtime.MemStats
33 | for {
34 | // Wait for the next inspection
35 | select {
36 | case <-ctx.Done():
37 | return
38 | case <-ticker.C:
39 | stats.StatSamples++
40 |
41 | // Inspect memory usage
42 | runtime.ReadMemStats(&runtimeStats)
43 |
44 | // Update stats if necessary
45 | if runtimeStats.HeapAlloc > stats.MaxHeapAlloc {
46 | stats.HeapAllocInc++
47 | stats.MaxHeapAlloc = runtimeStats.HeapAlloc
48 | }
49 | if runtimeStats.HeapObjects > stats.MaxHeapObjects {
50 | stats.HeapObjectsInc++
51 | stats.MaxHeapObjects = runtimeStats.HeapObjects
52 | }
53 | if runtimeStats.HeapSys > stats.MaxHeapSys {
54 | stats.HeapSysInc++
55 | stats.MaxHeapSys = runtimeStats.HeapSys
56 | }
57 |
58 | // Try to pass stats to reader if any
59 | select {
60 | case read <- stats:
61 | default:
62 | }
63 | }
64 | }
65 | }()
66 | return
67 | }
68 |
--------------------------------------------------------------------------------
/benchmark_results_visualization.numbers:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globusdigital/logbench/7b4944e816f0e8315c2a7b89563d13331ca405c7/benchmark_results_visualization.numbers
--------------------------------------------------------------------------------
/flagList.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type flagList struct {
4 | name string
5 | vals []string
6 | }
7 |
8 | func (l *flagList) String() string { return l.name }
9 |
10 | func (l *flagList) Set(value string) error {
11 | l.vals = append(l.vals, value)
12 | return nil
13 | }
14 |
15 | func (l *flagList) RemoveDuplicates() {
16 | nw := make([]string, 0, len(l.vals))
17 | reg := make(map[string]struct{})
18 | for _, loggerName := range l.vals {
19 | if _, ok := reg[loggerName]; !ok {
20 | nw = append(nw, loggerName)
21 | reg[loggerName] = struct{}{}
22 | }
23 | }
24 | l.vals = nw
25 | }
26 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/globusdigital/logbench
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/dustin/go-humanize v1.0.1
7 | github.com/olekukonko/tablewriter v0.0.5
8 | github.com/phuslu/log v1.0.83
9 | github.com/pkg/errors v0.9.1
10 | github.com/rs/zerolog v1.29.0
11 | github.com/sirupsen/logrus v1.9.0
12 | github.com/stretchr/testify v1.8.0
13 | go.uber.org/zap v1.24.0
14 | golang.org/x/text v0.8.0
15 | )
16 |
17 | require (
18 | github.com/davecgh/go-spew v1.1.1 // indirect
19 | github.com/mattn/go-colorable v0.1.13 // indirect
20 | github.com/mattn/go-isatty v0.0.17 // indirect
21 | github.com/mattn/go-runewidth v0.0.14 // indirect
22 | github.com/pmezard/go-difflib v1.0.0 // indirect
23 | github.com/rivo/uniseg v0.4.4 // indirect
24 | go.uber.org/atomic v1.10.0 // indirect
25 | go.uber.org/multierr v1.9.0 // indirect
26 | golang.org/x/sys v0.6.0 // indirect
27 | gopkg.in/yaml.v3 v3.0.1 // indirect
28 | )
29 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
2 | github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
7 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
8 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
9 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
10 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
11 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
12 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
13 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
14 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
15 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
16 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
17 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
18 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
19 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
20 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
21 | github.com/phuslu/log v1.0.83 h1:zfqz5tfFPLF8w0jEscpDxE2aFg1Y1kcbORDPliKdIbU=
22 | github.com/phuslu/log v1.0.83/go.mod h1:yAZh4pv6KxAsJDmJIcVSMxkMiUF7mJbpFN3vROkf0dc=
23 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
24 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
25 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
26 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
27 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
28 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
29 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
30 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
31 | github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
32 | github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
33 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
34 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
36 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
37 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
38 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
39 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
40 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
41 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
42 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
43 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
44 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
45 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
46 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
47 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
48 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
49 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
50 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
51 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
52 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
53 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
54 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
55 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
56 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
57 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
58 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
59 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
60 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
61 |
--------------------------------------------------------------------------------
/logrus/logrus.go:
--------------------------------------------------------------------------------
1 | package logrus
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/globusdigital/logbench/benchmark"
7 | "github.com/sirupsen/logrus"
8 | )
9 |
10 | func newLogger(out io.ReadWriter) *logrus.Logger {
11 | l := logrus.New()
12 | l.SetFormatter(&logrus.JSONFormatter{
13 | TimestampFormat: benchmark.TimeFormat,
14 | FieldMap: logrus.FieldMap{
15 | "msg": "message",
16 | },
17 | })
18 | l.SetOutput(out)
19 | l.SetLevel(logrus.InfoLevel)
20 | return l
21 | }
22 |
23 | func newInfo(out io.ReadWriter) (benchmark.FnInfo, error) {
24 | l := newLogger(out)
25 | return func(msg string) {
26 | l.Info(msg)
27 | }, nil
28 | }
29 |
30 | func newInfoFmt(out io.ReadWriter) (benchmark.FnInfoFmt, error) {
31 | l := newLogger(out)
32 | return func(msg string, data int) {
33 | l.Infof(msg, data)
34 | }, nil
35 | }
36 |
37 | func newInfoWithErrorStack(out io.ReadWriter) (
38 | benchmark.FnInfoWithErrorStack,
39 | error,
40 | ) {
41 | l := newLogger(out)
42 | return func(msg string, err error) {
43 | l.WithError(err).Info(msg)
44 | }, nil
45 | }
46 |
47 | func newError(out io.ReadWriter) (benchmark.FnError, error) {
48 | l := newLogger(out)
49 | return func(msg string) {
50 | l.Error(msg)
51 | }, nil
52 | }
53 |
54 | func newInfoWith3(out io.ReadWriter) (benchmark.FnInfoWith3, error) {
55 | l := newLogger(out)
56 | return func(msg string, fields *benchmark.Fields3) {
57 | l.WithFields(logrus.Fields{
58 | fields.Name1: fields.Value1,
59 | fields.Name2: fields.Value2,
60 | fields.Name3: fields.Value3,
61 | }).Info(msg)
62 | }, nil
63 | }
64 |
65 | func newInfoWith10(out io.ReadWriter) (benchmark.FnInfoWith10, error) {
66 | l := newLogger(out)
67 | return func(msg string, fields *benchmark.Fields10) {
68 | l.WithFields(logrus.Fields{
69 | fields.Name1: fields.Value1,
70 | fields.Name2: fields.Value2,
71 | fields.Name3: fields.Value3,
72 | fields.Name4: fields.Value4,
73 | fields.Name5: fields.Value5,
74 | fields.Name6: fields.Value6,
75 | fields.Name7: fields.Value7,
76 | fields.Name8: fields.Value8,
77 | fields.Name9: fields.Value9,
78 | fields.Name10: fields.Value10,
79 | }).Info(msg)
80 | }, nil
81 | }
82 |
83 | func newInfoWith10Exist(out io.ReadWriter) (
84 | benchmark.FnInfoWith10Exist,
85 | error,
86 | ) {
87 | l := newLogger(out)
88 | fields := benchmark.NewFields10()
89 | e := l.WithFields(logrus.Fields{
90 | fields.Name1: fields.Value1,
91 | fields.Name2: fields.Value2,
92 | fields.Name3: fields.Value3,
93 | fields.Name4: fields.Value4,
94 | fields.Name5: fields.Value5,
95 | fields.Name6: fields.Value6,
96 | fields.Name7: fields.Value7,
97 | fields.Name8: fields.Value8,
98 | fields.Name9: fields.Value9,
99 | fields.Name10: fields.Value10,
100 | })
101 | return func(msg string) {
102 | e.Info(msg)
103 | }, nil
104 | }
105 |
106 | // Setup defines the logrus logger setup
107 | func Setup() benchmark.Setup {
108 | return benchmark.Setup{
109 | Info: newInfo,
110 | InfoFmt: newInfoFmt,
111 | InfoWithErrorStack: newInfoWithErrorStack,
112 | Error: newError,
113 | InfoWith3: newInfoWith3,
114 | InfoWith10: newInfoWith10,
115 | InfoWith10Exist: newInfoWith10Exist,
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "runtime"
10 | "runtime/pprof"
11 | "sync/atomic"
12 | "syscall"
13 | "time"
14 |
15 | "github.com/globusdigital/logbench/benchmark"
16 | "github.com/globusdigital/logbench/logrus"
17 | "github.com/globusdigital/logbench/phuslog"
18 | "github.com/globusdigital/logbench/zap"
19 | "github.com/globusdigital/logbench/zerolog"
20 | )
21 |
22 | var setups = map[string]benchmark.Setup{
23 | "zap": zap.Setup(),
24 | "zerolog": zerolog.Setup(),
25 | "logrus": logrus.Setup(),
26 | "phuslog": phuslog.Setup(),
27 | }
28 |
29 | func setupTermSigInterceptor() func() bool {
30 | stop := int32(0)
31 | sigChan := make(chan os.Signal, 1)
32 | signal.Notify(sigChan, os.Interrupt)
33 | go func() {
34 | for {
35 | sig := <-sigChan
36 | if sig == syscall.SIGTERM || sig == syscall.SIGINT {
37 | // Halt process
38 | atomic.StoreInt32(&stop, 1)
39 | break
40 | }
41 | }
42 | }()
43 | return func() bool { return atomic.LoadInt32(&stop) == 1 }
44 | }
45 |
46 | func main() {
47 | // Declare and parse flags
48 | flagLoggers := &flagList{name: "loggers"}
49 | flag.Var(flagLoggers, "l", "loggers")
50 |
51 | flagOperations := &flagList{name: "operations"}
52 | flag.Var(flagOperations, "o", "operations")
53 |
54 | flagTarget := flag.Uint64(
55 | "t",
56 | 1_000_000,
57 | "target number of logs to be written",
58 | )
59 | flagMemCheckInterval := flag.Duration(
60 | "mi",
61 | 2*time.Millisecond,
62 | "memory inspection interval",
63 | )
64 | flagConcWriters := flag.Uint(
65 | "w",
66 | 1,
67 | "number of concurrently writing goroutines",
68 | )
69 | flagMemoryProfile := flag.String(
70 | "memprof",
71 | "", // Disabled by default
72 | "memory profile output file (disabled when empty)",
73 | )
74 | flagOperationsAll := flag.Bool("o_all", false, "run all operations")
75 |
76 | flag.Parse()
77 |
78 | if *flagOperationsAll {
79 | flagOperations.vals = []string{
80 | benchmark.LogOperationInfo,
81 | benchmark.LogOperationInfoFmt,
82 | benchmark.LogOperationInfoWithErrorStack,
83 | benchmark.LogOperationInfoWith3,
84 | benchmark.LogOperationInfoWith10,
85 | benchmark.LogOperationInfoWith10Exist,
86 | benchmark.LogOperationError,
87 | }
88 | }
89 |
90 | // Prepare
91 | stopped := setupTermSigInterceptor()
92 | memStatChan := benchmark.StartMemoryWatcher(
93 | context.Background(),
94 | *flagMemCheckInterval,
95 | )
96 |
97 | if len(flagLoggers.vals) < 1 {
98 | log.Fatal("no loggers selected")
99 | }
100 |
101 | if len(flagOperations.vals) < 1 {
102 | log.Fatal("no operations selected")
103 | }
104 |
105 | flagLoggers.RemoveDuplicates()
106 |
107 | stats := make(
108 | map[string]map[string]benchmark.Statistics,
109 | len(flagLoggers.vals),
110 | )
111 |
112 | start := time.Now()
113 | for _, loggerName := range flagLoggers.vals {
114 | setupInit, setupExists := setups[loggerName]
115 | if !setupExists {
116 | log.Fatalf("no setup for logger %q", loggerName)
117 | }
118 |
119 | for _, operation := range flagOperations.vals {
120 | bench, err := benchmark.New(os.Stdout, operation, setupInit)
121 | if err != nil {
122 | log.Fatalf("setup %q init: %s", loggerName, err)
123 | }
124 |
125 | if s := stats[loggerName]; s == nil {
126 | stats[loggerName] = make(
127 | map[string]benchmark.Statistics,
128 | len(flagOperations.vals),
129 | )
130 | }
131 | stats[loggerName][operation] = bench.Run(
132 | *flagTarget,
133 | *flagConcWriters,
134 | stopped,
135 | )
136 | }
137 | }
138 | timeTotal := time.Since(start)
139 |
140 | printStatistics(
141 | timeTotal,
142 | *flagTarget,
143 | *flagConcWriters,
144 | memStatChan,
145 | flagLoggers.vals,
146 | flagOperations.vals,
147 | stats,
148 | )
149 |
150 | // Write memory profile
151 | if *flagMemoryProfile != "" {
152 | runtime.GC()
153 | memProfile, err := os.Create(*flagMemoryProfile)
154 | if err != nil {
155 | log.Fatal(err)
156 | }
157 | defer memProfile.Close()
158 | if err := pprof.WriteHeapProfile(memProfile); err != nil {
159 | log.Fatal(err)
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "reflect"
8 | "sync"
9 | "testing"
10 | "time"
11 |
12 | "github.com/globusdigital/logbench/benchmark"
13 | "github.com/stretchr/testify/require"
14 | )
15 |
16 | // SyncBuffer is a thread-safe buffer implementing the io.ReadWriter interface
17 | type SyncBuffer struct {
18 | m sync.Mutex
19 | b bytes.Buffer
20 | }
21 |
22 | func (b *SyncBuffer) Read(p []byte) (n int, err error) {
23 | b.m.Lock()
24 | defer b.m.Unlock()
25 | return b.b.Read(p)
26 | }
27 |
28 | func (b *SyncBuffer) Write(p []byte) (n int, err error) {
29 | b.m.Lock()
30 | defer b.m.Unlock()
31 | return b.b.Write(p)
32 | }
33 |
34 | func (b *SyncBuffer) String() string {
35 | b.m.Lock()
36 | defer b.m.Unlock()
37 | return b.b.String()
38 | }
39 |
40 | // FV represents a mapping between field names
41 | // and the according list validators
42 | type FV map[string]func(interface{}) error
43 |
44 | func expectString(actual interface{}) (string, error) {
45 | val, ok := actual.(string)
46 | if !ok {
47 | return "", fmt.Errorf(
48 | "unexpected field type (expected: string; got: %s)",
49 | reflect.TypeOf(actual),
50 | )
51 | }
52 | return val, nil
53 | }
54 |
55 | func expectBool(actual interface{}) (bool, error) {
56 | val, ok := actual.(bool)
57 | if !ok {
58 | return false, fmt.Errorf(
59 | "unexpected field type (expected: bool; got: %s)",
60 | reflect.TypeOf(actual),
61 | )
62 | }
63 | return val, nil
64 | }
65 |
66 | func expectFloat64(actual interface{}) (float64, error) {
67 | val, ok := actual.(float64)
68 | if !ok {
69 | return 0, fmt.Errorf(
70 | "unexpected field type (expected: int; got: %s)",
71 | reflect.TypeOf(actual),
72 | )
73 | }
74 | return val, nil
75 | }
76 |
77 | func validateTime(actual interface{}) error {
78 | val, err := expectString(actual)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | _, err = time.Parse(time.RFC3339, val)
84 | return err
85 | }
86 |
87 | func newValidatorLevel(expected string) func(interface{}) error {
88 | return func(actual interface{}) error {
89 | val, err := expectString(actual)
90 | if err != nil {
91 | return err
92 | }
93 | if val != expected {
94 | return fmt.Errorf(
95 | "mismatching level: (expected: %q, got: %q)",
96 | expected,
97 | val,
98 | )
99 | }
100 | return nil
101 | }
102 | }
103 |
104 | func newValidatorText(expected string) func(interface{}) error {
105 | return func(actual interface{}) error {
106 | val, err := expectString(actual)
107 | if err != nil {
108 | return err
109 | }
110 | if val != expected {
111 | return fmt.Errorf(
112 | "mismatching text: (expected: %q, got: %q)",
113 | expected,
114 | val,
115 | )
116 | }
117 | return nil
118 | }
119 | }
120 |
121 | func newValidatorBool(expected bool) func(interface{}) error {
122 | return func(actual interface{}) error {
123 | val, err := expectBool(actual)
124 | if err != nil {
125 | return err
126 | }
127 | if val != expected {
128 | return fmt.Errorf("mismatching bool: (expected: %t)", expected)
129 | }
130 | return nil
131 | }
132 | }
133 |
134 | func newValidatorInt(expected int) func(interface{}) error {
135 | return func(actual interface{}) error {
136 | val, err := expectFloat64(actual)
137 | if err != nil {
138 | return err
139 | }
140 | if val != float64(expected) {
141 | return fmt.Errorf(
142 | "mismatching int: (expected: %d, got: %f)",
143 | expected,
144 | val,
145 | )
146 | }
147 | return nil
148 | }
149 | }
150 |
151 | func newValidatorFloat64(expected float64) func(interface{}) error {
152 | return func(actual interface{}) error {
153 | val, err := expectFloat64(actual)
154 | if err != nil {
155 | return err
156 | }
157 | if val != expected {
158 | return fmt.Errorf(
159 | "mismatching float64: (expected: %f, got: %f)",
160 | expected,
161 | val,
162 | )
163 | }
164 | return nil
165 | }
166 | }
167 |
168 | func newValidatorStrings(expected []string) func(interface{}) error {
169 | return func(actual interface{}) error {
170 | val, ok := actual.([]interface{})
171 | if !ok {
172 | return fmt.Errorf(
173 | "unexpected field type (expected: []string; got: %s)",
174 | reflect.TypeOf(actual),
175 | )
176 | }
177 | for i, val := range val {
178 | val, err := expectString(val)
179 | if err != nil {
180 | return err
181 | }
182 | expected := expected[i]
183 | if expected != val {
184 | return fmt.Errorf(
185 | "mismatching array item: (expected: %s, got: %s)",
186 | expected,
187 | val,
188 | )
189 | }
190 | }
191 | return nil
192 | }
193 | }
194 |
195 | func newValidatorInts(expected []int) func(interface{}) error {
196 | return func(actual interface{}) error {
197 | val, ok := actual.([]interface{})
198 | if !ok {
199 | return fmt.Errorf(
200 | "unexpected field type (expected: []int; got: %s)",
201 | reflect.TypeOf(actual),
202 | )
203 | }
204 | for i, val := range val {
205 | val, err := expectFloat64(val)
206 | if err != nil {
207 | return err
208 | }
209 | expected := expected[i]
210 |
211 | if float64(expected) != val {
212 | return fmt.Errorf(
213 | "mismatching array item: (expected: %d, got: %f)",
214 | expected,
215 | val,
216 | )
217 | }
218 | }
219 | return nil
220 | }
221 | }
222 |
223 | func newValidatorFloat64s(expected []float64) func(interface{}) error {
224 | return func(actual interface{}) error {
225 | val, ok := actual.([]interface{})
226 | if !ok {
227 | return fmt.Errorf(
228 | "unexpected field type (expected: []interface{}; got: %s)",
229 | reflect.TypeOf(actual),
230 | )
231 | }
232 | for i, val := range val {
233 | val, err := expectFloat64(val)
234 | if err != nil {
235 | return err
236 | }
237 | expected := expected[i]
238 |
239 | if float64(expected) != val {
240 | return fmt.Errorf(
241 | "mismatching array item: (expected: %f, got: %f)",
242 | expected,
243 | val,
244 | )
245 | }
246 | }
247 | return nil
248 | }
249 | }
250 |
251 | func TestFormat(t *testing.T) {
252 | fields3 := benchmark.NewFields3()
253 | fields10 := benchmark.NewFields10()
254 |
255 | fieldValidators := map[string]FV{
256 | benchmark.LogOperationInfo: {
257 | benchmark.FieldTime: validateTime,
258 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelInfo),
259 | benchmark.FieldMessage: newValidatorText("information"),
260 | },
261 | benchmark.LogOperationInfoFmt: {
262 | benchmark.FieldTime: validateTime,
263 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelInfo),
264 | benchmark.FieldMessage: newValidatorText("information 42"),
265 | },
266 | benchmark.LogOperationInfoWithErrorStack: {
267 | benchmark.FieldTime: validateTime,
268 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelInfo),
269 | benchmark.FieldMessage: newValidatorText("information"),
270 | benchmark.FieldError: newValidatorText("error with stack trace"),
271 | },
272 | benchmark.LogOperationError: {
273 | benchmark.FieldTime: validateTime,
274 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelError),
275 | benchmark.FieldMessage: newValidatorText("error message"),
276 | },
277 | benchmark.LogOperationInfoWith3: {
278 | benchmark.FieldTime: validateTime,
279 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelInfo),
280 | benchmark.FieldMessage: newValidatorText("information"),
281 | fields3.Name1: newValidatorText(fields3.Value1),
282 | fields3.Name2: newValidatorInt(fields3.Value2),
283 | fields3.Name3: newValidatorFloat64(fields3.Value3),
284 | },
285 | benchmark.LogOperationInfoWith10: {
286 | benchmark.FieldTime: validateTime,
287 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelInfo),
288 | benchmark.FieldMessage: newValidatorText("information"),
289 | fields10.Name1: newValidatorText(fields10.Value1),
290 | fields10.Name2: newValidatorText(fields10.Value2),
291 | fields10.Name3: newValidatorText(fields10.Value3),
292 | fields10.Name4: newValidatorBool(fields10.Value4),
293 | fields10.Name5: newValidatorText(fields10.Value5),
294 | fields10.Name6: newValidatorInt(fields10.Value6),
295 | fields10.Name7: newValidatorFloat64(fields10.Value7),
296 | fields10.Name8: newValidatorStrings(fields10.Value8),
297 | fields10.Name9: newValidatorInts(fields10.Value9),
298 | fields10.Name10: newValidatorFloat64s(fields10.Value10),
299 | },
300 | benchmark.LogOperationInfoWith10Exist: {
301 | benchmark.FieldTime: validateTime,
302 | benchmark.FieldLevel: newValidatorLevel(benchmark.LevelInfo),
303 | benchmark.FieldMessage: newValidatorText("information"),
304 | fields10.Name1: newValidatorText(fields10.Value1),
305 | fields10.Name2: newValidatorText(fields10.Value2),
306 | fields10.Name3: newValidatorText(fields10.Value3),
307 | fields10.Name4: newValidatorBool(fields10.Value4),
308 | fields10.Name5: newValidatorText(fields10.Value5),
309 | fields10.Name6: newValidatorInt(fields10.Value6),
310 | fields10.Name7: newValidatorFloat64(fields10.Value7),
311 | fields10.Name8: newValidatorStrings(fields10.Value8),
312 | fields10.Name9: newValidatorInts(fields10.Value9),
313 | fields10.Name10: newValidatorFloat64s(fields10.Value10),
314 | },
315 | }
316 |
317 | for loggerName, initFn := range setups {
318 | t.Run(loggerName, func(t *testing.T) {
319 | for operationName, validators := range fieldValidators {
320 | t.Run(operationName, func(t *testing.T) {
321 | buf := new(SyncBuffer)
322 | bench, err := benchmark.New(buf, operationName, initFn)
323 | require.NoError(t, err)
324 | stats := bench.Run(1, 1, nil)
325 | require.Equal(t, uint64(1), stats.TotalLogsWritten)
326 |
327 | var fields map[string]interface{}
328 | dec := json.NewDecoder(buf)
329 | require.NoError(
330 | t,
331 | dec.Decode(&fields),
332 | "decoding JSON for logger %q",
333 | loggerName,
334 | )
335 |
336 | for requiredField, validate := range validators {
337 | require.Contains(t, fields, requiredField)
338 | require.NoError(
339 | t,
340 | validate(fields[requiredField]),
341 | "invalid value for field %q of logger %q",
342 | requiredField,
343 | loggerName,
344 | )
345 | }
346 | })
347 | }
348 | })
349 | }
350 | }
351 |
--------------------------------------------------------------------------------
/phuslog/phuslog.go:
--------------------------------------------------------------------------------
1 | package phuslog
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/globusdigital/logbench/benchmark"
7 | phuslog "github.com/phuslu/log"
8 | )
9 |
10 | func init() {
11 | }
12 |
13 | func newLogger(out io.ReadWriter) phuslog.Logger {
14 | // Initialize logger
15 | return phuslog.Logger{
16 | Level: phuslog.InfoLevel,
17 | Writer: &phuslog.IOWriter{out},
18 | }
19 | }
20 |
21 | func newInfo(out io.ReadWriter) (benchmark.FnInfo, error) {
22 | l := newLogger(out)
23 | return func(msg string) {
24 | l.Info().Msg(msg)
25 | }, nil
26 | }
27 |
28 | func newInfoFmt(out io.ReadWriter) (benchmark.FnInfoFmt, error) {
29 | l := newLogger(out)
30 | return func(msg string, data int) {
31 | l.Info().Msgf(msg, data)
32 | }, nil
33 | }
34 |
35 | func newInfoWithErrorStack(out io.ReadWriter) (
36 | benchmark.FnInfoWithErrorStack, error,
37 | ) {
38 | l := newLogger(out)
39 | return func(msg string, err error) {
40 | l.Context = phuslog.NewContext(l.Context[:0]).Err(err).Value()
41 | l.Info().Msg(msg)
42 | }, nil
43 | }
44 |
45 | func newError(out io.ReadWriter) (benchmark.FnError, error) {
46 | l := newLogger(out)
47 | return func(msg string) {
48 | l.Error().Msg(msg)
49 | }, nil
50 | }
51 |
52 | func newInfoWith3(out io.ReadWriter) (benchmark.FnInfoWith3, error) {
53 | l := newLogger(out)
54 | return func(msg string, fields *benchmark.Fields3) {
55 | l.Context = phuslog.NewContext(l.Context[:0]).
56 | Str(fields.Name1, fields.Value1).
57 | Int(fields.Name2, fields.Value2).
58 | Float64(fields.Name3, fields.Value3).
59 | Value()
60 | l.Info().Msg(msg)
61 | }, nil
62 | }
63 |
64 | func newInfoWith10(out io.ReadWriter) (benchmark.FnInfoWith10, error) {
65 | l := newLogger(out)
66 | return func(msg string, fields *benchmark.Fields10) {
67 | l.Context = phuslog.NewContext(l.Context[:0]).
68 | Str(fields.Name1, fields.Value1).
69 | Str(fields.Name2, fields.Value2).
70 | Str(fields.Name3, fields.Value3).
71 | Bool(fields.Name4, fields.Value4).
72 | Str(fields.Name5, fields.Value5).
73 | Int(fields.Name6, fields.Value6).
74 | Float64(fields.Name7, fields.Value7).
75 | Strs(fields.Name8, fields.Value8).
76 | Ints(fields.Name9, fields.Value9).
77 | Floats64(fields.Name10, fields.Value10).
78 | Value()
79 | l.Info().Msg(msg)
80 | }, nil
81 | }
82 |
83 | func newInfoWith10Exist(out io.ReadWriter) (
84 | benchmark.FnInfoWith10Exist,
85 | error,
86 | ) {
87 | fields := benchmark.NewFields10()
88 | l := newLogger(out)
89 | l.Context = phuslog.NewContext(l.Context[:0]).
90 | Str(fields.Name1, fields.Value1).
91 | Str(fields.Name2, fields.Value2).
92 | Str(fields.Name3, fields.Value3).
93 | Bool(fields.Name4, fields.Value4).
94 | Str(fields.Name5, fields.Value5).
95 | Int(fields.Name6, fields.Value6).
96 | Float64(fields.Name7, fields.Value7).
97 | Strs(fields.Name8, fields.Value8).
98 | Ints(fields.Name9, fields.Value9).
99 | Floats64(fields.Name10, fields.Value10).
100 | Value()
101 | return func(msg string) {
102 | l.Info().Msg(msg)
103 | }, nil
104 | }
105 |
106 | // Setup initializes the phuslog based logger
107 | func Setup() benchmark.Setup {
108 | return benchmark.Setup{
109 | Info: newInfo,
110 | InfoFmt: newInfoFmt,
111 | InfoWithErrorStack: newInfoWithErrorStack,
112 | Error: newError,
113 | InfoWith3: newInfoWith3,
114 | InfoWith10: newInfoWith10,
115 | InfoWith10Exist: newInfoWith10Exist,
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/printStatistics.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "runtime"
6 | "time"
7 |
8 | "github.com/dustin/go-humanize"
9 | "github.com/globusdigital/logbench/benchmark"
10 | "github.com/olekukonko/tablewriter"
11 | "golang.org/x/text/language"
12 | "golang.org/x/text/message"
13 | )
14 |
15 | func printStatistics(
16 | timeTotal time.Duration,
17 | target uint64,
18 | concWriters uint,
19 | memStatChan chan benchmark.MemStats,
20 | loggerOrder []string,
21 | operationsOrder []string,
22 | stats map[string]map[string]benchmark.Statistics,
23 | ) {
24 | numPrint := message.NewPrinter(language.English)
25 |
26 | memStats := <-memStatChan
27 |
28 | var m runtime.MemStats
29 | runtime.ReadMemStats(&m)
30 |
31 | totalConcWriters := numPrint.Sprintf("%d", concWriters)
32 | totalGC := numPrint.Sprintf("%d", m.NumGC)
33 | totalMallocs := numPrint.Sprintf("%d", m.Mallocs)
34 | maxHeapObj := numPrint.Sprintf("%d", memStats.MaxHeapObjects)
35 | heapAllocInc := numPrint.Sprintf("%d", memStats.HeapAllocInc)
36 | heapObjInc := numPrint.Sprintf("%d", memStats.HeapObjectsInc)
37 | memStatSamples := numPrint.Sprintf("%d", memStats.StatSamples)
38 |
39 | // Print main table
40 | {
41 | tbMain := tablewriter.NewWriter(os.Stdout)
42 | tbMain.SetAlignment(tablewriter.ALIGN_LEFT)
43 |
44 | dr := func(k, v string) { tbMain.Append([]string{k, v}) }
45 | dr("target", numPrint.Sprintf("%d", target))
46 | dr("conc. writers", totalConcWriters)
47 | dr("", "")
48 | dr("time total", timeTotal.String())
49 | dr("max heap", humanize.Bytes(memStats.MaxHeapAlloc))
50 | dr("max heap obj", maxHeapObj)
51 | dr("mem samples", memStatSamples)
52 | dr("heap inc", heapAllocInc)
53 | dr("heap obj in", heapObjInc)
54 | dr("heap sys", humanize.Bytes(memStats.MaxHeapSys))
55 | dr("total alloc", humanize.Bytes(m.TotalAlloc))
56 | dr("mallocs", totalMallocs)
57 | dr("num-gc", totalGC)
58 | dr("total pause", time.Duration(m.PauseTotalNs).String())
59 | tbMain.Render()
60 | }
61 |
62 | // Print comparisons table
63 | {
64 | tbMain := tablewriter.NewWriter(os.Stdout)
65 | tbMain.SetHeader([]string{
66 | "logger",
67 | "operation",
68 | "time total",
69 | "time avg.",
70 | "written",
71 | })
72 | tbMain.SetAlignment(tablewriter.ALIGN_LEFT)
73 |
74 | for _, loggerName := range loggerOrder {
75 | for _, operation := range operationsOrder {
76 | stats := stats[loggerName][operation]
77 | tbMain.Append([]string{
78 | loggerName,
79 | operation,
80 | stats.TotalTime.String(),
81 | (stats.TotalTime / time.Duration(target)).String(),
82 | numPrint.Sprintf("%d", stats.TotalLogsWritten),
83 | })
84 | }
85 | }
86 | tbMain.Render()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/results_graphs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globusdigital/logbench/7b4944e816f0e8315c2a7b89563d13331ca405c7/results_graphs.png
--------------------------------------------------------------------------------
/zap/memorySink.go:
--------------------------------------------------------------------------------
1 | package zap
2 |
3 | import (
4 | "io"
5 | "net/url"
6 | "sync"
7 |
8 | "go.uber.org/zap"
9 | )
10 |
11 | // MemorySink implements zap.Sink by writing all messages to a buffer.
12 | type MemorySink struct {
13 | writer io.ReadWriter
14 | lock sync.Mutex
15 | registered bool
16 | }
17 |
18 | // Close implements the Sink interface
19 | func (s *MemorySink) Close() error { return nil }
20 |
21 | // Sync implements the Sink interface
22 | func (s *MemorySink) Sync() error { return nil }
23 |
24 | // Write implements the Sink interface
25 | func (s *MemorySink) Write(data []byte) (int, error) {
26 | zapSink.lock.Lock()
27 | defer zapSink.lock.Unlock()
28 | return s.writer.Write(data)
29 | }
30 |
31 | // Read implements the io.ReadWriter interface
32 | func (s *MemorySink) Read(data []byte) (int, error) {
33 | zapSink.lock.Lock()
34 | defer zapSink.lock.Unlock()
35 | return s.writer.Read(data)
36 | }
37 |
38 | // SetOut atomically sets the output read-writer
39 | func (s *MemorySink) SetOut(out io.ReadWriter) error {
40 | s.lock.Lock()
41 | defer s.lock.Unlock()
42 |
43 | s.writer = out
44 | if !s.registered {
45 | s.registered = true
46 | return zap.RegisterSink("memory", func(*url.URL) (zap.Sink, error) {
47 | return s, nil
48 | })
49 | }
50 |
51 | return nil
52 | }
53 |
54 | var _ io.ReadWriter = new(MemorySink)
55 | var zapSink = new(MemorySink)
56 |
--------------------------------------------------------------------------------
/zap/zap.go:
--------------------------------------------------------------------------------
1 | package zap
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "time"
8 |
9 | "github.com/globusdigital/logbench/benchmark"
10 | "go.uber.org/zap"
11 | "go.uber.org/zap/zapcore"
12 | )
13 |
14 | func defaultConfig() zap.Config {
15 | return zap.Config{
16 | Level: zap.NewAtomicLevelAt(zap.DebugLevel),
17 | Encoding: "json",
18 | DisableStacktrace: true,
19 | EncoderConfig: zapcore.EncoderConfig{
20 | MessageKey: "message",
21 | LevelKey: "level",
22 | TimeKey: "time",
23 | NameKey: "name",
24 | CallerKey: "caller",
25 | StacktraceKey: "stack",
26 | EncodeLevel: func(
27 | l zapcore.Level,
28 | enc zapcore.PrimitiveArrayEncoder,
29 | ) {
30 | switch l {
31 | case zapcore.DebugLevel:
32 | enc.AppendString("debug")
33 | case zapcore.InfoLevel:
34 | enc.AppendString("info")
35 | case zapcore.WarnLevel:
36 | enc.AppendString("warning")
37 | case zapcore.ErrorLevel:
38 | enc.AppendString("error")
39 | case zapcore.DPanicLevel:
40 | enc.AppendString("dpanic")
41 | case zapcore.PanicLevel:
42 | enc.AppendString("panic")
43 | case zapcore.FatalLevel:
44 | enc.AppendString("fatal")
45 | }
46 | },
47 | EncodeTime: func(
48 | tm time.Time,
49 | enc zapcore.PrimitiveArrayEncoder,
50 | ) {
51 | enc.AppendString(tm.Format(benchmark.TimeFormat))
52 | },
53 | EncodeCaller: zapcore.ShortCallerEncoder,
54 | },
55 | }
56 | }
57 |
58 | func newLogger(out io.ReadWriter, conf zap.Config) (*zap.Logger, error) {
59 | if err := zapSink.SetOut(out); err != nil {
60 | return nil, fmt.Errorf("setting sink output: %w", err)
61 | }
62 |
63 | if out == os.Stdout {
64 | conf.OutputPaths = []string{"stdout"}
65 | } else {
66 | conf.OutputPaths = []string{"memory://"}
67 | }
68 |
69 | l, err := conf.Build()
70 | if err != nil {
71 | return nil, fmt.Errorf("building zap config: %w", err)
72 | }
73 | return l, nil
74 | }
75 |
76 | func newInfo(out io.ReadWriter) (benchmark.FnInfo, error) {
77 | l, err := newLogger(out, defaultConfig())
78 | if err != nil {
79 | return nil, err
80 | }
81 | return func(msg string) {
82 | l.Info(msg)
83 | }, nil
84 | }
85 |
86 | func newInfoFmt(out io.ReadWriter) (benchmark.FnInfoFmt, error) {
87 | l, err := newLogger(out, defaultConfig())
88 | if err != nil {
89 | return nil, err
90 | }
91 | return func(msg string, data int) {
92 | l.Info(fmt.Sprintf(msg, data))
93 | }, nil
94 | }
95 |
96 | func newInfoWithErrorStack(out io.ReadWriter) (
97 | benchmark.FnInfoWithErrorStack,
98 | error,
99 | ) {
100 | l, err := newLogger(out, defaultConfig())
101 | if err != nil {
102 | return nil, err
103 | }
104 | return func(msg string, err error) {
105 | l.Info(msg, zap.Error(err))
106 | }, nil
107 | }
108 |
109 | func newError(out io.ReadWriter) (benchmark.FnError, error) {
110 | l, err := newLogger(out, defaultConfig())
111 | if err != nil {
112 | return nil, err
113 | }
114 | return func(msg string) {
115 | l.Error(msg)
116 | }, nil
117 | }
118 |
119 | func newInfoWith3(out io.ReadWriter) (benchmark.FnInfoWith3, error) {
120 | l, err := newLogger(out, defaultConfig())
121 | if err != nil {
122 | return nil, err
123 | }
124 | return func(msg string, fields *benchmark.Fields3) {
125 | l.Info(msg,
126 | zap.String(fields.Name1, fields.Value1),
127 | zap.Int(fields.Name2, fields.Value2),
128 | zap.Float64(fields.Name3, fields.Value3),
129 | )
130 | }, nil
131 | }
132 |
133 | func newInfoWith10(out io.ReadWriter) (benchmark.FnInfoWith10, error) {
134 | l, err := newLogger(out, defaultConfig())
135 | if err != nil {
136 | return nil, err
137 | }
138 | return func(msg string, fields *benchmark.Fields10) {
139 | l.Info(msg,
140 | zap.String(fields.Name1, fields.Value1),
141 | zap.String(fields.Name2, fields.Value2),
142 | zap.String(fields.Name3, fields.Value3),
143 | zap.Bool(fields.Name4, fields.Value4),
144 | zap.String(fields.Name5, fields.Value5),
145 | zap.Int(fields.Name6, fields.Value6),
146 | zap.Float64(fields.Name7, fields.Value7),
147 | zap.Strings(fields.Name8, fields.Value8),
148 | zap.Ints(fields.Name9, fields.Value9),
149 | zap.Float64s(fields.Name10, fields.Value10),
150 | )
151 | }, nil
152 | }
153 |
154 | func newInfoWith10Exist(out io.ReadWriter) (
155 | benchmark.FnInfoWith10Exist,
156 | error,
157 | ) {
158 | l, err := newLogger(out, defaultConfig())
159 | if err != nil {
160 | return nil, err
161 | }
162 | fields := benchmark.NewFields10()
163 | l = l.With(
164 | zap.String(fields.Name1, fields.Value1),
165 | zap.String(fields.Name2, fields.Value2),
166 | zap.String(fields.Name3, fields.Value3),
167 | zap.Bool(fields.Name4, fields.Value4),
168 | zap.String(fields.Name5, fields.Value5),
169 | zap.Int(fields.Name6, fields.Value6),
170 | zap.Float64(fields.Name7, fields.Value7),
171 | zap.Strings(fields.Name8, fields.Value8),
172 | zap.Ints(fields.Name9, fields.Value9),
173 | zap.Float64s(fields.Name10, fields.Value10),
174 | )
175 | return func(msg string) {
176 | l.Info(msg)
177 | }, nil
178 | }
179 |
180 | // Setup defines the zap logger setup
181 | func Setup() benchmark.Setup {
182 | return benchmark.Setup{
183 | Info: newInfo,
184 | InfoFmt: newInfoFmt,
185 | InfoWithErrorStack: newInfoWithErrorStack,
186 | Error: newError,
187 | InfoWith3: newInfoWith3,
188 | InfoWith10: newInfoWith10,
189 | InfoWith10Exist: newInfoWith10Exist,
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/zerolog/zerolog.go:
--------------------------------------------------------------------------------
1 | package zerolog
2 |
3 | import (
4 | "io"
5 | "time"
6 |
7 | "github.com/globusdigital/logbench/benchmark"
8 | "github.com/rs/zerolog"
9 | )
10 |
11 | func init() {
12 | zerolog.TimeFieldFormat = time.RFC3339Nano
13 | }
14 |
15 | func newLogger(out io.ReadWriter) zerolog.Logger {
16 | // Initialize logger
17 | return zerolog.New(out).With().Timestamp().Logger()
18 | }
19 |
20 | func newInfo(out io.ReadWriter) (benchmark.FnInfo, error) {
21 | l := newLogger(out)
22 | return func(msg string) {
23 | l.Info().Msg(msg)
24 | }, nil
25 | }
26 |
27 | func newInfoFmt(out io.ReadWriter) (benchmark.FnInfoFmt, error) {
28 | l := newLogger(out)
29 | return func(msg string, data int) {
30 | l.Info().Msgf(msg, data)
31 | }, nil
32 | }
33 |
34 | func newInfoWithErrorStack(out io.ReadWriter) (
35 | benchmark.FnInfoWithErrorStack, error,
36 | ) {
37 | l := newLogger(out)
38 | return func(msg string, err error) {
39 | l := l.With().Err(err).Logger()
40 | l.Info().Msg(msg)
41 | }, nil
42 | }
43 |
44 | func newError(out io.ReadWriter) (benchmark.FnError, error) {
45 | l := newLogger(out)
46 | return func(msg string) {
47 | l.Error().Msg(msg)
48 | }, nil
49 | }
50 |
51 | func newInfoWith3(out io.ReadWriter) (benchmark.FnInfoWith3, error) {
52 | l := newLogger(out)
53 | return func(msg string, fields *benchmark.Fields3) {
54 | l := l.With().
55 | Str(fields.Name1, fields.Value1).
56 | Int(fields.Name2, fields.Value2).
57 | Float64(fields.Name3, fields.Value3).
58 | Logger()
59 | l.Info().Msg(msg)
60 | }, nil
61 | }
62 |
63 | func newInfoWith10(out io.ReadWriter) (benchmark.FnInfoWith10, error) {
64 | l := newLogger(out)
65 | return func(msg string, fields *benchmark.Fields10) {
66 | l := l.With().
67 | Str(fields.Name1, fields.Value1).
68 | Str(fields.Name2, fields.Value2).
69 | Str(fields.Name3, fields.Value3).
70 | Bool(fields.Name4, fields.Value4).
71 | Str(fields.Name5, fields.Value5).
72 | Int(fields.Name6, fields.Value6).
73 | Float64(fields.Name7, fields.Value7).
74 | Strs(fields.Name8, fields.Value8).
75 | Ints(fields.Name9, fields.Value9).
76 | Floats64(fields.Name10, fields.Value10).
77 | Logger()
78 | l.Info().Msg(msg)
79 | }, nil
80 | }
81 |
82 | func newInfoWith10Exist(out io.ReadWriter) (
83 | benchmark.FnInfoWith10Exist,
84 | error,
85 | ) {
86 | fields := benchmark.NewFields10()
87 | l := newLogger(out)
88 | l = l.With().
89 | Str(fields.Name1, fields.Value1).
90 | Str(fields.Name2, fields.Value2).
91 | Str(fields.Name3, fields.Value3).
92 | Bool(fields.Name4, fields.Value4).
93 | Str(fields.Name5, fields.Value5).
94 | Int(fields.Name6, fields.Value6).
95 | Float64(fields.Name7, fields.Value7).
96 | Strs(fields.Name8, fields.Value8).
97 | Ints(fields.Name9, fields.Value9).
98 | Floats64(fields.Name10, fields.Value10).
99 | Logger()
100 | return func(msg string) {
101 | l.Info().Msg(msg)
102 | }, nil
103 | }
104 |
105 | // Setup initializes the zerolog based logger
106 | func Setup() benchmark.Setup {
107 | return benchmark.Setup{
108 | Info: newInfo,
109 | InfoFmt: newInfoFmt,
110 | InfoWithErrorStack: newInfoWithErrorStack,
111 | Error: newError,
112 | InfoWith3: newInfoWith3,
113 | InfoWith10: newInfoWith10,
114 | InfoWith10Exist: newInfoWith10Exist,
115 | }
116 | }
117 |
--------------------------------------------------------------------------------