├── .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 | GoReportCard 3 | 4 | 5 | Travis CI: build status 6 | 7 | 8 | Coverage Status 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 | ![Benchmark Results](https://github.com/globusdigital/logbench/blob/master/results_graphs.png?raw=true) 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 | --------------------------------------------------------------------------------