├── .gitignore ├── .travis.yml ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── benchmarks ├── Makefile └── onelog_bench_test.go ├── entry.go ├── entry_test.go ├── go.mod ├── go.sum ├── levels.go ├── log └── main.go ├── logger.go ├── logger_test.go └── onelog.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | *.out -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10.x" 5 | - master 6 | 7 | script: 8 | - go get github.com/stretchr/testify 9 | - go get github.com/francoispqt/gojay 10 | - go test -race -coverprofile=coverage.txt -covermode=atomic 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" 6 | name = "github.com/davecgh/go-spew" 7 | packages = ["spew"] 8 | pruneopts = "" 9 | revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" 10 | version = "v1.1.1" 11 | 12 | [[projects]] 13 | digest = "1:86c70c372aeec190cdccda2156f61e40c18c721f988b4a05fd2b1a1101b36580" 14 | name = "github.com/francoispqt/gojay" 15 | packages = ["."] 16 | pruneopts = "" 17 | revision = "f2cc13a668caf474b5d5806c7f1adbbe4ce28524" 18 | version = "1.2.7" 19 | 20 | [[projects]] 21 | digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" 22 | name = "github.com/pmezard/go-difflib" 23 | packages = ["difflib"] 24 | pruneopts = "" 25 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 26 | version = "v1.0.0" 27 | 28 | [[projects]] 29 | digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75" 30 | name = "github.com/stretchr/testify" 31 | packages = ["assert"] 32 | pruneopts = "" 33 | revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" 34 | version = "v1.2.2" 35 | 36 | [solve-meta] 37 | analyzer-name = "dep" 38 | analyzer-version = 1 39 | input-imports = [ 40 | "github.com/francoispqt/gojay", 41 | "github.com/stretchr/testify/assert", 42 | ] 43 | solver-name = "gps-cdcl" 44 | solver-version = 1 45 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | name = "github.com/francoispqt/gojay" 26 | version = "1.2.7" 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 onelog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test -race -run=^Test -v 4 | 5 | .PHONY: cover 6 | cover: 7 | go test -coverprofile=coverage.out -covermode=atomic 8 | 9 | .PHONY: coverhtml 10 | coverhtml: 11 | go tool cover -html=coverage.out -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build Status](https://travis-ci.org/francoispqt/onelog.svg?branch=master)](https://travis-ci.org/francoispqt/onelog) 3 | [![codecov](https://codecov.io/gh/francoispqt/onelog/branch/master/graph/badge.svg)](https://codecov.io/gh/francoispqt/onelog) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/francoispqt/onelog)](https://goreportcard.com/report/github.com/francoispqt/onelog) 5 | [![Go doc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square 6 | )](https://godoc.org/github.com/francoispqt/onelog) 7 | ![MIT License](https://img.shields.io/badge/license-mit-blue.svg?style=flat-square) 8 | 9 | # Onelog 10 | Onelog is a dead simple but very efficient JSON logger. 11 | It is one of the fastest JSON logger out there. Also, it is one of the logger with the lowest allocation. 12 | 13 | It gives more control over log levels enabled by using bitwise operation for setting levels on a logger. 14 | 15 | It is also modular as you can add a custom hook, define level text values, level and message keys. 16 | 17 | Go 1.9 is required as it uses a type alias over gojay.Encoder. 18 | 19 | It is named onelog as a reference to zerolog and because it sounds like `One Love` song from Bob Marley :) 20 | 21 | ## Get Started 22 | 23 | ```bash 24 | go get github.com/francoispqt/onelog 25 | ``` 26 | 27 | Basic usage: 28 | 29 | ```go 30 | import "github.com/francoispqt/onelog" 31 | 32 | func main() { 33 | // create a new Logger 34 | // first argument is an io.Writer 35 | // second argument is the level, which is an integer 36 | logger := onelog.New( 37 | os.Stdout, 38 | onelog.ALL, // shortcut for onelog.DEBUG|onelog.INFO|onelog.WARN|onelog.ERROR|onelog.FATAL, 39 | ) 40 | logger.Info("hello world !") // {"level":"info","message":"hello world"} 41 | } 42 | ``` 43 | 44 | 45 | ## Levels 46 | 47 | Levels are ints mapped to a string. The logger will check if level is enabled with an efficient bitwise &(AND), if disabled, it returns right away which makes onelog the fastest when running disabled logging with 0 allocs and less than 1ns/op. [See benchmarks](#benchmarks) 48 | 49 | When creating a logger you must use the `|` operator with different levels to toggle bytes. 50 | 51 | Example if you want levels INFO and WARN: 52 | ```go 53 | logger := onelog.New( 54 | os.Stdout, 55 | onelog.INFO|onelog.WARN, 56 | ) 57 | ``` 58 | 59 | This allows you to have a logger with different levels, for example you can do: 60 | ```go 61 | var logger *onelog.Logger 62 | 63 | func init() { 64 | // if we are in debug mode, enable DEBUG lvl 65 | if os.Getenv("DEBUG") != "" { 66 | logger = onelog.New( 67 | os.Stdout, 68 | onelog.ALL, // shortcut for onelog.DEBUG|onelog.INFO|onelog.WARN|onelog.ERROR|onelog.FATAL 69 | ) 70 | return 71 | } 72 | logger = onelog.New( 73 | os.Stdout, 74 | onelog.INFO|onelog.WARN|onelog.ERROR|onelog.FATAL, 75 | ) 76 | } 77 | ``` 78 | 79 | Available levels: 80 | - onelog.DEBUG 81 | - onelog.INFO 82 | - onelog.WARN 83 | - onelog.ERROR 84 | - onelog.FATAL 85 | 86 | You can change their textual values by doing, do this only once at runtime as it is not thread safe: 87 | ```go 88 | onelog.LevelText(onelog.INFO, "INFO") 89 | ``` 90 | 91 | ## Hook 92 | 93 | You can define a hook which will be run for every log message. 94 | 95 | Example: 96 | ```go 97 | logger := onelog.New( 98 | os.Stdout, 99 | onelog.ALL, 100 | ) 101 | logger.Hook(func(e onelog.Entry) { 102 | e.String("time", time.Now().Format(time.RFC3339)) 103 | }) 104 | logger.Info("hello world !") // {"level":"info","message":"hello world","time":"2018-05-06T02:21:01+08:00"} 105 | ``` 106 | 107 | ## Context 108 | 109 | Context allows enforcing a grouping format where all logs fields key-values pairs from all logging methods (With, Info, Debug, InfoWith, InfoWithEntry, ...etc) except 110 | for values from using `logger.Hook`, will be enclosed in giving context name provided as it's key. For example using a context key "params" as below 111 | 112 | 113 | ```go 114 | logger := onelog.NewContext( 115 | os.Stdout, 116 | onelog.INFO|onelog.WARN, 117 | "params" 118 | ) 119 | 120 | logger.InfoWithFields("breaking news !", func(e onelog.Entry) { 121 | e.String("userID", "123455") 122 | }) 123 | 124 | // {"level":"info","message":"breaking news !", "params":{"userID":"123456"}} 125 | ``` 126 | 127 | This principle also applies when inheriting from a previous created logger as below 128 | 129 | ```go 130 | parentLogger := onelog.New( 131 | os.Stdout, 132 | onelog.INFO|onelog.WARN, 133 | ) 134 | 135 | 136 | logger := parentLogger.WithContext("params") 137 | logger.InfoWithFields("breaking news !", func(e onelog.Entry) { 138 | e.String("userID", "123455") 139 | }) 140 | 141 | // {"level":"info","message":"breaking news !", "params":{"userID":"123456"}} 142 | ``` 143 | 144 | 145 | You can always reset the context by calling `WithContext("")` to create a no-context logger from a 146 | context logger parent. 147 | 148 | 149 | ## Logging 150 | 151 | ### Without extra fields 152 | Logging without extra fields is easy as: 153 | ```go 154 | logger := onelog.New( 155 | os.Stdout, 156 | onelog.ALL, 157 | ) 158 | logger.Debug("i'm not sure what's going on") // {"level":"debug","message":"i'm not sure what's going on"} 159 | logger.Info("breaking news !") // {"level":"info","message":"breaking news !"} 160 | logger.Warn("beware !") // {"level":"warn","message":"beware !"} 161 | logger.Error("my printer is on fire") // {"level":"error","message":"my printer is on fire"} 162 | logger.Fatal("oh my...") // {"level":"fatal","message":"oh my..."} 163 | ``` 164 | 165 | ### With extra fields 166 | Logging with extra fields is quite simple, specially if you have used gojay: 167 | ```go 168 | logger := onelog.New( 169 | os.Stdout, 170 | onelog.ALL, 171 | ) 172 | 173 | logger.DebugWithFields("i'm not sure what's going on", func(e onelog.Entry) { 174 | e.String("string", "foobar") 175 | e.Int("int", 12345) 176 | e.Int64("int64", 12345) 177 | e.Float("float64", 0.15) 178 | e.Bool("bool", true) 179 | e.Err("err", errors.New("someError")) 180 | e.ObjectFunc("user", func(e Entry) { 181 | e.String("name", "somename") 182 | }) 183 | }) 184 | // {"level":"debug","message":"i'm not sure what's going on","string":"foobar","int":12345,"int64":12345,"float64":0.15,"bool":true,"err":"someError","user":{"name":"somename"}} 185 | 186 | logger.InfoWithFields("breaking news !", func(e onelog.Entry) { 187 | e.String("userID", "123455") 188 | }) 189 | // {"level":"info","message":"breaking news !","userID":"123456"} 190 | 191 | logger.WarnWithFields("beware !", func(e onelog.Entry) { 192 | e.String("userID", "123455") 193 | }) 194 | // {"level":"warn","message":"beware !","userID":"123456"} 195 | 196 | logger.ErrorWithFields("my printer is on fire", func(e onelog.Entry) { 197 | e.String("userID", "123455") 198 | }) 199 | // {"level":"error","message":"my printer is on fire","userID":"123456"} 200 | 201 | logger.FatalWithFields("oh my...", func(e onelog.Entry) { 202 | e.String("userID", "123455") 203 | }) 204 | // {"level":"fatal","message":"oh my...","userID":"123456"} 205 | ``` 206 | 207 | Alternatively, you can use the chain syntax: 208 | ```go 209 | logger.InfoWith("foo bar"). 210 | Int("testInt", 1). 211 | Int64("testInt64", 2). 212 | Float("testFloat", 1.15234). 213 | String("testString", "string"). 214 | Bool("testBool", true). 215 | ObjectFunc("testObj", func(e Entry) { 216 | e.Int("testInt", 100) 217 | }). 218 | Object("testObj2", testObj). // implementation of gojay.MarshalerJSONObject 219 | Array("testArr", testArr). // implementation of gojay.MarshalerJSONArray 220 | Err("testErr", errors.New("my printer is on fire !")). 221 | Write() // don't forget to call this method! 222 | ``` 223 | 224 | ## Accumulate context 225 | You can create get a logger with some accumulated context that will be included on all logs created by this logger. 226 | 227 | To do that, you must call the `With` method on a logger. 228 | Internally it creates a copy of the current logger and returns it. 229 | 230 | Example: 231 | ```go 232 | logger := onelog.New( 233 | os.Stdout, 234 | onelog.ALL, 235 | ).With(func(e onelog.Entry) { 236 | e.String("userID", "123456") 237 | }) 238 | 239 | logger.Info("user logged in") // {"level":"info","message":"user logged in","userID":"123456"} 240 | 241 | logger.Debug("wtf?") // {"level":"debug","message":"wtf?","userID":"123456"} 242 | 243 | logger.ErrorWithFields("Oops", func(e onelog.Entry) { 244 | e.String("error_code", "ROFL") 245 | }) // {"level":"error","message":"oops","userID":"123456","error_code":"ROFL"} 246 | ``` 247 | 248 | ## Change levels txt values, message and/or level keys 249 | You can change globally the levels values by calling the function: 250 | ```go 251 | onelog.LevelText(onelog.INFO, "INFO") 252 | ``` 253 | 254 | You can change the key of the message by calling the function: 255 | ```go 256 | onelog.MsgKey("msg") 257 | ``` 258 | 259 | You can change the key of the level by calling the function: 260 | ```go 261 | onelog.LevelKey("lvl") 262 | ``` 263 | 264 | Beware, these changes are global (affects all instances of the logger). Also, these function should be called only once at runtime to avoid any data race issue. 265 | 266 | # Benchmarks 267 | 268 | For thorough benchmarks please see the results in the bench suite created by the author of zerolog here: https://github.com/rs/logbench 269 | 270 | The benchmarks data presented below is the one from Uber's benchmark suite where we added onelog. 271 | 272 | Benchmarks are here: https://github.com/francoispqt/zap/tree/onelog-bench/benchmarks 273 | 274 | ## Disabled Logging 275 | | | ns/op | bytes/op | allocs/op | 276 | |-------------|-------|--------------|-----------| 277 | | Zap | 8.73 | 0 | 0 | 278 | | zerolog | 2.45 | 0 | 0 | 279 | | logrus | 12.1 | 16 | 1 | 280 | | onelog | 0.74 | 0 | 0 | 281 | 282 | ## Disabled with fields 283 | | | ns/op | bytes/op | allocs/op | 284 | |-------------|-------|--------------|-----------| 285 | | Zap | 208 | 768 | 5 | 286 | | zerolog | 68.7 | 128 | 4 | 287 | | logrus | 721 | 1493 | 12 | 288 | | onelog | 1.31 | 0 | 0 | 289 | | onelog-chain| 68.2 | 0 | 0 | 290 | 291 | ## Logging basic message 292 | | | ns/op | bytes/op | allocs/op | 293 | |-------------|-------|--------------|-----------| 294 | | Zap | 205 | 0 | 0 | 295 | | zerolog | 135 | 0 | 0 | 296 | | logrus | 1256 | 1554 | 24 | 297 | | onelog | 84.8 | 0 | 0 | 298 | 299 | ## Logging basic message and accumulated context 300 | | | ns/op | bytes/op | allocs/op | 301 | |-------------|-------|--------------|-----------| 302 | | Zap | 276 | 0 | 0 | 303 | | zerolog | 141 | 0 | 0 | 304 | | logrus | 1256 | 1554 | 24 | 305 | | onelog | 82.4 | 0 | 0 | 306 | 307 | ## Logging message with extra fields 308 | | | ns/op | bytes/op | allocs/op | 309 | |-------------|-------|--------------|-----------| 310 | | Zap | 1764 | 770 | 5 | 311 | | zerolog | 1210 | 128 | 4 | 312 | | logrus | 13211 | 13584 | 129 | 313 | | onelog | 971 | 128 | 4 | 314 | | onelog-chain| 1030 | 128 | 4 | 315 | -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: benchtrace 2 | benchtrace: 3 | go test -c & GODEBUG=allocfreetrace=1 ./benchmarks.test -test.run=none -test.bench=^BenchmarkOneLogTrace -test.benchtime=10ms 2>trace.log 4 | 5 | .PHONY: benchonelog 6 | benchonelog: 7 | go test -benchmem -run=^BenchmarkOnelog.* -bench=^BenchmarkOnelog.* -benchtime=30ms 8 | 9 | .PHONY: benchonelograce 10 | benchonelograce: 11 | go test -race -benchmem -run=^BenchmarkOnelog -bench=^BenchmarkOnelog -benchtime=30ms 12 | 13 | .PHONY: benchonelogcpu 14 | benchonelogcpu: 15 | go test -benchmem -run=^BenchmarkOnelog -bench=^BenchmarkOnelog -benchtime=30ms -cpuprofile cpu.out 16 | -------------------------------------------------------------------------------- /benchmarks/onelog_bench_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/francoispqt/onelog" 10 | ) 11 | 12 | func TestPrint(t *testing.T) { 13 | logger := onelog.New(os.Stdout, onelog.ALL). 14 | Hook(func(e onelog.Entry) { 15 | e.Int64("time", time.Now().Unix()) 16 | }) 17 | logger.InfoWith("message"). 18 | String("test", "test"). 19 | String("test", "test"). 20 | String("test", "test"). 21 | String("test", "test"). 22 | String("test", "test"). 23 | String("test", "test"). 24 | String("test", "test"). 25 | Write() 26 | } 27 | 28 | func BenchmarkOnelog(b *testing.B) { 29 | b.Run("with-fields", func(b *testing.B) { 30 | logger := onelog.New(ioutil.Discard, onelog.ALL). 31 | Hook(func(e onelog.Entry) { 32 | e.Int64("time", time.Now().Unix()) 33 | }) 34 | s := struct { 35 | i int 36 | }{i: 0} 37 | b.ResetTimer() 38 | b.RunParallel(func(pb *testing.PB) { 39 | for pb.Next() { 40 | logger.InfoWithFields("message", func(e onelog.Entry) { 41 | e.Int("test", s.i) 42 | e.String("test", "test") 43 | e.String("test", "test") 44 | e.String("test", "test") 45 | e.String("test", "test") 46 | e.String("test", "test") 47 | e.String("test", "test") 48 | e.String("test", "test") 49 | }) 50 | } 51 | }) 52 | }) 53 | b.Run("message-only", func(b *testing.B) { 54 | logger := onelog.New(ioutil.Discard, onelog.ALL). 55 | Hook(func(e onelog.Entry) { 56 | e.Int64("time", time.Now().Unix()) 57 | }) 58 | b.ResetTimer() 59 | b.RunParallel(func(pb *testing.PB) { 60 | for pb.Next() { 61 | logger.Info("message") 62 | } 63 | }) 64 | }) 65 | b.Run("entry-message-only", func(b *testing.B) { 66 | logger := onelog.New(ioutil.Discard, onelog.ALL). 67 | Hook(func(e onelog.Entry) { 68 | e.Int64("time", time.Now().Unix()) 69 | }) 70 | b.ResetTimer() 71 | b.RunParallel(func(pb *testing.PB) { 72 | for pb.Next() { 73 | logger.InfoWith("message").Write() 74 | } 75 | }) 76 | }) 77 | b.Run("entry-fields", func(b *testing.B) { 78 | logger := onelog.New(ioutil.Discard, onelog.ALL). 79 | Hook(func(e onelog.Entry) { 80 | e.Int64("time", time.Now().Unix()) 81 | }) 82 | b.ResetTimer() 83 | b.RunParallel(func(pb *testing.PB) { 84 | for pb.Next() { 85 | logger.InfoWith("message"). 86 | String("test", "test"). 87 | String("test", "test"). 88 | String("test", "test"). 89 | String("test", "test"). 90 | String("test", "test"). 91 | String("test", "test"). 92 | String("test", "test"). 93 | Write() 94 | } 95 | }) 96 | }) 97 | 98 | b.Run("accumulated context", func(b *testing.B) { 99 | logger := onelog.New(ioutil.Discard, onelog.ALL). 100 | With(func(e onelog.Entry) { 101 | e.Int("int", 1) 102 | }) 103 | b.ResetTimer() 104 | b.RunParallel(func(pb *testing.PB) { 105 | for pb.Next() { 106 | logger.Info("message") 107 | } 108 | }) 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | package onelog 2 | 3 | import ( 4 | "github.com/francoispqt/gojay" 5 | ) 6 | 7 | // Entry is the structure wrapping a pointer to the current encoder. 8 | // It provides easy API to work with GoJay's encoder. 9 | type Entry struct { 10 | enc *Encoder 11 | l *Logger 12 | Level uint8 13 | Message string 14 | } 15 | 16 | // String adds a string to the log entry. 17 | func (e Entry) String(k, v string) Entry { 18 | e.enc.StringKey(k, v) 19 | return e 20 | } 21 | 22 | // Int adds an int to the log entry. 23 | func (e Entry) Int(k string, v int) Entry { 24 | e.enc.IntKey(k, v) 25 | return e 26 | } 27 | 28 | // Int64 adds an int64 to the log entry. 29 | func (e Entry) Int64(k string, v int64) Entry { 30 | e.enc.Int64Key(k, v) 31 | return e 32 | } 33 | 34 | // Float adds a float64 to the log entry. 35 | func (e Entry) Float(k string, v float64) Entry { 36 | e.enc.FloatKey(k, v) 37 | return e 38 | } 39 | 40 | // Bool adds a bool to the log entry. 41 | func (e Entry) Bool(k string, v bool) Entry { 42 | e.enc.BoolKey(k, v) 43 | return e 44 | } 45 | 46 | // Err adds an error to the log entry. 47 | func (e Entry) Err(k string, v error) Entry { 48 | if v != nil { 49 | e.enc.StringKey(k, v.Error()) 50 | } 51 | return e 52 | } 53 | 54 | // ObjectFunc adds an object to the log entry by calling a function. 55 | func (e Entry) ObjectFunc(k string, v func(Entry)) Entry { 56 | e.enc.ObjectKey(k, Object(func(enc *Encoder) { 57 | v(e) 58 | })) 59 | return e 60 | } 61 | 62 | // Object adds an object to the log entry by passing an implementation of gojay.MarshalerJSONObject. 63 | func (e Entry) Object(k string, obj gojay.MarshalerJSONObject) Entry { 64 | e.enc.ObjectKey(k, obj) 65 | return e 66 | } 67 | 68 | // Array adds an object to the log entry by passing an implementation of gojay.MarshalerJSONObject. 69 | func (e Entry) Array(k string, obj gojay.MarshalerJSONArray) Entry { 70 | e.enc.ArrayKey(k, obj) 71 | return e 72 | } 73 | 74 | // ChainEntry is for chaining calls to the entry. 75 | type ChainEntry struct { 76 | Entry 77 | disabled bool 78 | exit bool 79 | } 80 | 81 | // Info logs an entry with INFO level. 82 | func (e ChainEntry) Write() { 83 | if e.disabled { 84 | return 85 | } 86 | // first find writer for level 87 | // if none, stop 88 | e.Entry.l.closeEntry(e.Entry) 89 | e.Entry.l.finalizeIfContext(e.Entry) 90 | e.Entry.enc.Release() 91 | 92 | if e.exit { 93 | e.Entry.l.exit(1) 94 | } 95 | } 96 | 97 | // String adds a string to the log entry. 98 | func (e ChainEntry) String(k, v string) ChainEntry { 99 | if e.disabled { 100 | return e 101 | } 102 | e.enc.StringKey(k, v) 103 | return e 104 | } 105 | 106 | // Int adds an int to the log entry. 107 | func (e ChainEntry) Int(k string, v int) ChainEntry { 108 | if e.disabled { 109 | return e 110 | } 111 | e.enc.IntKey(k, v) 112 | return e 113 | } 114 | 115 | // Int64 adds an int64 to the log entry. 116 | func (e ChainEntry) Int64(k string, v int64) ChainEntry { 117 | if e.disabled { 118 | return e 119 | } 120 | e.enc.Int64Key(k, v) 121 | return e 122 | } 123 | 124 | // Float adds a float64 to the log entry. 125 | func (e ChainEntry) Float(k string, v float64) ChainEntry { 126 | if e.disabled { 127 | return e 128 | } 129 | e.enc.FloatKey(k, v) 130 | return e 131 | } 132 | 133 | // Bool adds a bool to the log entry. 134 | func (e ChainEntry) Bool(k string, v bool) ChainEntry { 135 | if e.disabled { 136 | return e 137 | } 138 | e.enc.BoolKey(k, v) 139 | return e 140 | } 141 | 142 | // Err adds an error to the log entry. 143 | func (e ChainEntry) Err(k string, v error) ChainEntry { 144 | if e.disabled { 145 | return e 146 | } 147 | if v != nil { 148 | e.enc.StringKey(k, v.Error()) 149 | } 150 | return e 151 | } 152 | 153 | // ObjectFunc adds an object to the log entry by calling a function. 154 | func (e ChainEntry) ObjectFunc(k string, v func(Entry)) ChainEntry { 155 | if e.disabled { 156 | return e 157 | } 158 | e.enc.ObjectKey(k, Object(func(enc *Encoder) { 159 | v(e.Entry) 160 | })) 161 | return e 162 | } 163 | 164 | // Object adds an object to the log entry by passing an implementation of gojay.MarshalerJSONObject. 165 | func (e ChainEntry) Object(k string, obj gojay.MarshalerJSONObject) ChainEntry { 166 | if e.disabled { 167 | return e 168 | } 169 | e.enc.ObjectKey(k, obj) 170 | return e 171 | } 172 | 173 | // Array adds an object to the log entry by passing an implementation of gojay.MarshalerJSONObject. 174 | func (e ChainEntry) Array(k string, obj gojay.MarshalerJSONArray) ChainEntry { 175 | if e.disabled { 176 | return e 177 | } 178 | e.enc.ArrayKey(k, obj) 179 | return e 180 | } 181 | 182 | // Any adds anything stuff to the log entry based on it's type 183 | func (e ChainEntry) Any(k string, obj interface{}) ChainEntry { 184 | if e.disabled { 185 | return e 186 | } 187 | e.enc.AddInterfaceKey(k, obj) 188 | return e 189 | } 190 | -------------------------------------------------------------------------------- /entry_test.go: -------------------------------------------------------------------------------- 1 | package onelog 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEntry(t *testing.T) { 12 | t.Run("basic-info-entry", func(t *testing.T) { 13 | w := newWriter() 14 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 15 | logger.InfoWith("hello").Int("test", 1).Write() 16 | json := `{"level":"info","message":"hello","test":1}` + "\n" 17 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 18 | }) 19 | t.Run("basic-info-entry-hook", func(t *testing.T) { 20 | w := newWriter() 21 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL).Hook(func(e Entry) { 22 | e.String("hello", "world") 23 | }) 24 | logger.InfoWith("hello").Int("test", 1).Write() 25 | json := `{"level":"info","message":"hello","hello":"world","test":1}` + "\n" 26 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 27 | }) 28 | t.Run("basic-info-entry-disabled", func(t *testing.T) { 29 | w := newWriter() 30 | logger := New(w, DEBUG) 31 | logger.InfoWith("hello").Int("test", 1).Write() 32 | json := `` 33 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 34 | }) 35 | t.Run("basic-debug-entry", func(t *testing.T) { 36 | w := newWriter() 37 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 38 | logger.DebugWith("hello").Int("test", 1).Write() 39 | json := `{"level":"debug","message":"hello","test":1}` + "\n" 40 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 41 | }) 42 | t.Run("basic-debug-entry-hook", func(t *testing.T) { 43 | w := newWriter() 44 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL).Hook(func(e Entry) { 45 | e.String("hello", "world") 46 | }) 47 | logger.DebugWith("hello").Int("test", 1).Write() 48 | json := `{"level":"debug","message":"hello","hello":"world","test":1}` + "\n" 49 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 50 | }) 51 | t.Run("basic-debug-entry-disabled", func(t *testing.T) { 52 | w := newWriter() 53 | logger := New(w, INFO|WARN|ERROR|FATAL) 54 | logger.DebugWith("hello").Int("test", 1).Write() 55 | json := `` 56 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 57 | }) 58 | t.Run("basic-warn-entry", func(t *testing.T) { 59 | w := newWriter() 60 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 61 | logger.WarnWith("hello").Int("test", 1).Write() 62 | json := `{"level":"warn","message":"hello","test":1}` + "\n" 63 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 64 | }) 65 | t.Run("basic-warn-entry-hook", func(t *testing.T) { 66 | w := newWriter() 67 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL).Hook(func(e Entry) { 68 | e.String("hello", "world") 69 | }) 70 | logger.WarnWith("hello").Int("test", 1).Write() 71 | json := `{"level":"warn","message":"hello","hello":"world","test":1}` + "\n" 72 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 73 | }) 74 | t.Run("basic-warn-entry-disabled", func(t *testing.T) { 75 | w := newWriter() 76 | logger := New(w, INFO|ERROR|FATAL) 77 | logger.WarnWith("hello").Int("test", 1).Write() 78 | json := `` 79 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 80 | }) 81 | t.Run("basic-error-entry", func(t *testing.T) { 82 | w := newWriter() 83 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 84 | logger.ErrorWith("hello").Int("test", 1).Write() 85 | json := `{"level":"error","message":"hello","test":1}` + "\n" 86 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 87 | }) 88 | t.Run("basic-error-entry-hook", func(t *testing.T) { 89 | w := newWriter() 90 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL).Hook(func(e Entry) { 91 | e.String("hello", "world") 92 | }) 93 | logger.ErrorWith("hello").Int("test", 1).Write() 94 | json := `{"level":"error","message":"hello","hello":"world","test":1}` + "\n" 95 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 96 | }) 97 | t.Run("basic-error-entry-disabled", func(t *testing.T) { 98 | w := newWriter() 99 | logger := New(w, INFO|WARN|FATAL) 100 | logger.ErrorWith("hello").Int("test", 1).Write() 101 | json := `` 102 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 103 | }) 104 | t.Run("basic-fatal-entry", func(t *testing.T) { 105 | w := newWriter() 106 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 107 | logger.FatalWith("hello").Int("test", 1).Write() 108 | json := `{"level":"fatal","message":"hello","test":1}` + "\n" 109 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 110 | }) 111 | t.Run("basic-fatal-entry-hook", func(t *testing.T) { 112 | w := newWriter() 113 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL).Hook(func(e Entry) { 114 | e.String("hello", "world") 115 | }) 116 | logger.FatalWith("hello").Int("test", 1).Write() 117 | json := `{"level":"fatal","message":"hello","hello":"world","test":1}` + "\n" 118 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 119 | }) 120 | t.Run("basic-fatal-entry-disabled", func(t *testing.T) { 121 | w := newWriter() 122 | logger := New(w, DEBUG|INFO|WARN|ERROR) 123 | logger.FatalWith("hello").Int("test", 1).Write() 124 | json := `` 125 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 126 | }) 127 | } 128 | 129 | func TestEntryFields(t *testing.T) { 130 | json := `{"level":"%s","message":"hello","testInt":1,"testInt64":2,"testFloat":1.15234,` + 131 | `"testString":"string","testBool":true,"testObj":{"testInt":100},` + 132 | `"testObj2":{"foo":"bar"},"testArr":[{"foo":"bar"},{"foo":"bar"}],` + 133 | `"testAnyString":"bar",` + 134 | `"testAnyFloat":10.1,` + 135 | `"testAnyInt":10,` + 136 | `"testErr":"my printer is on fire"}` + "\n" 137 | testCases := []struct { 138 | level uint8 139 | disabled uint8 140 | levelString string 141 | entryFunc func(*Logger) ChainEntry 142 | }{ 143 | { 144 | level: INFO, 145 | disabled: DEBUG, 146 | levelString: "info", 147 | entryFunc: func(l *Logger) ChainEntry { 148 | return l.InfoWith("hello") 149 | }, 150 | }, 151 | { 152 | level: DEBUG, 153 | disabled: INFO, 154 | levelString: "debug", 155 | entryFunc: func(l *Logger) ChainEntry { 156 | return l.DebugWith("hello") 157 | }, 158 | }, 159 | { 160 | level: WARN, 161 | disabled: ERROR, 162 | levelString: "warn", 163 | entryFunc: func(l *Logger) ChainEntry { 164 | return l.WarnWith("hello") 165 | }, 166 | }, 167 | { 168 | level: ERROR, 169 | disabled: WARN, 170 | levelString: "error", 171 | entryFunc: func(l *Logger) ChainEntry { 172 | return l.ErrorWith("hello") 173 | }, 174 | }, 175 | { 176 | level: FATAL, 177 | disabled: ERROR, 178 | levelString: "fatal", 179 | entryFunc: func(l *Logger) ChainEntry { 180 | return l.FatalWith("hello") 181 | }, 182 | }, 183 | } 184 | 185 | for _, testCase := range testCases { 186 | t.Run(fmt.Sprintf("test-%s-entry-all-fields-enabled", testCase.levelString), func(t *testing.T) { 187 | w := newWriter() 188 | logger := New(w, testCase.level) 189 | testObj := &TestObj{"bar"} 190 | testArr := TestObjArr{testObj, testObj} 191 | testCase.entryFunc(logger). 192 | Int("testInt", 1). 193 | Int64("testInt64", 2). 194 | Float("testFloat", 1.15234). 195 | String("testString", "string"). 196 | Bool("testBool", true). 197 | ObjectFunc("testObj", func(e Entry) { 198 | e.Int("testInt", 100) 199 | }). 200 | Object("testObj2", testObj). 201 | Array("testArr", testArr). 202 | Any("testAnyString", "bar"). 203 | Any("testAnyFloat", 10.1). 204 | Any("testAnyInt", 10). 205 | Err("testErr", errors.New("my printer is on fire")). 206 | Write() 207 | assert.Equal(t, fmt.Sprintf(json, testCase.levelString), string(w.b), "bytes written to the writer dont equal expected result") 208 | }) 209 | t.Run(fmt.Sprintf("test-%s-entry-all-fields-disabled", testCase.levelString), func(t *testing.T) { 210 | w := newWriter() 211 | logger := New(w, testCase.disabled) 212 | testObj := &TestObj{"bar"} 213 | testArr := TestObjArr{testObj, testObj} 214 | testCase.entryFunc(logger). 215 | Int("testInt", 1). 216 | Int64("testInt64", 2). 217 | Float("testFloat", 1.15234). 218 | String("testString", "string"). 219 | Bool("testBool", true). 220 | ObjectFunc("testObj", func(e Entry) { 221 | e.Int("testInt", 100) 222 | }). 223 | Object("testObj2", testObj). 224 | Array("testArr", testArr). 225 | Any("testAnyString", "bar"). 226 | Any("testAnyFloat", 10.0). 227 | Any("testAnyInt", 10). 228 | Err("testErr", errors.New("my printer is on fire")). 229 | Write() 230 | assert.Equal(t, ``, string(w.b), "bytes written to the writer dont equal expected result") 231 | 232 | }) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/francoispqt/onelog 2 | 3 | require ( 4 | github.com/davecgh/go-spew v1.1.1 // indirect 5 | github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca 6 | github.com/pmezard/go-difflib v1.0.0 // indirect 7 | github.com/stretchr/testify v1.2.2 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca h1:F2BD6Vhei4w0rtm4eNpzylNsB07CcCbpYA+xlqMx3mA= 4 | github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca/go.mod h1:H8Wgri1Asi1VevY3ySdpIK5+KCpqzToVswNq8g2xZj4= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 8 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 9 | -------------------------------------------------------------------------------- /levels.go: -------------------------------------------------------------------------------- 1 | package onelog 2 | 3 | const ( 4 | // INFO is the numeric code for INFO log level 5 | INFO = uint8(0x1) 6 | // DEBUG is the numeric code for DEBUG log level 7 | DEBUG = uint8(0x2) 8 | // WARN is the numeric code for WARN log level 9 | WARN = uint8(0x4) 10 | // ERROR is the numeric code for ERROR log level 11 | ERROR = uint8(0x8) 12 | // FATAL is the numeric code for FATAL log level 13 | FATAL = uint8(0x10) 14 | ) 15 | 16 | // ALL is a shortcut to INFO | DEBUG | WARN | ERROR | FATAL to enable all logging levels 17 | var ALL = uint8(INFO | DEBUG | WARN | ERROR | FATAL) 18 | 19 | // Levels is the mapping between int log levels and their string value 20 | var Levels = make([]string, 256) 21 | var levelsJSON = make([][]byte, 256) 22 | var levelKey = "level" 23 | 24 | func init() { 25 | Levels[INFO] = "info" 26 | Levels[DEBUG] = "debug" 27 | Levels[WARN] = "warn" 28 | Levels[ERROR] = "error" 29 | Levels[FATAL] = "fatal" 30 | genLevelSlices() 31 | } 32 | 33 | func genLevelSlices() { 34 | levelsJSON[INFO] = []byte(`{"` + levelKey + `":"` + Levels[INFO] + `","` + msgKey + `":`) 35 | levelsJSON[DEBUG] = []byte(`{"` + levelKey + `":"` + Levels[DEBUG] + `","` + msgKey + `":`) 36 | levelsJSON[WARN] = []byte(`{"` + levelKey + `":"` + Levels[WARN] + `","` + msgKey + `":`) 37 | levelsJSON[ERROR] = []byte(`{"` + levelKey + `":"` + Levels[ERROR] + `","` + msgKey + `":`) 38 | levelsJSON[FATAL] = []byte(`{"` + levelKey + `":"` + Levels[FATAL] + `","` + msgKey + `":`) 39 | } 40 | -------------------------------------------------------------------------------- /log/main.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/francoispqt/onelog" 8 | ) 9 | 10 | var logger = onelog.New(os.Stdout, onelog.ALL).Hook(func(e onelog.Entry) { 11 | e.Int64("time", time.Now().Unix()) 12 | }) 13 | 14 | // Info prints a message with log level Info. 15 | func Info(msg string) { 16 | logger.Info(msg) 17 | } 18 | 19 | // InfoWithFields prints a message with log level INFO and fields. 20 | func InfoWithFields(msg string, fields func(e onelog.Entry)) { 21 | logger.InfoWithFields(msg, fields) 22 | } 23 | 24 | // Debug prints a message with log level DEBUG. 25 | func Debug(msg string) { 26 | logger.Debug(msg) 27 | } 28 | 29 | // DebugWithFields prints a message with log level DEBUG and fields. 30 | func DebugWithFields(msg string, fields func(e onelog.Entry)) { 31 | logger.DebugWithFields(msg, fields) 32 | } 33 | 34 | // Warn prints a message with log level INFO. 35 | func Warn(msg string) { 36 | logger.Warn(msg) 37 | } 38 | 39 | // WarnWithFields prints a message with log level WARN and fields. 40 | func WarnWithFields(msg string, fields func(e onelog.Entry)) { 41 | logger.WarnWithFields(msg, fields) 42 | } 43 | 44 | // Error prints a message with log level ERROR. 45 | func Error(msg string) { 46 | logger.Error(msg) 47 | } 48 | 49 | // ErrorWithFields prints a message with log level ERROR and fields. 50 | func ErrorWithFields(msg string, fields func(e onelog.Entry)) { 51 | logger.ErrorWithFields(msg, fields) 52 | } 53 | 54 | // Fatal prints a message with log level FATAL. 55 | func Fatal(msg string) { 56 | logger.Fatal(msg) 57 | } 58 | 59 | // FatalWithFields prints a message with log level FATAL and fields. 60 | func FatalWithFields(msg string, fields func(e onelog.Entry)) { 61 | logger.FatalWithFields(msg, fields) 62 | } 63 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package onelog 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "runtime" 8 | "strconv" 9 | 10 | "github.com/francoispqt/gojay" 11 | ) 12 | 13 | var ( 14 | logOpen = []byte("{") 15 | logClose = []byte("}\n") 16 | logCloseOnly = []byte("}") 17 | msgKey = "message" 18 | ) 19 | 20 | // LevelText personalises the text for a specific level. 21 | func LevelText(level uint8, txt string) { 22 | Levels[level] = txt 23 | genLevelSlices() 24 | } 25 | 26 | // MsgKey sets the key for the message field. 27 | func MsgKey(s string) { 28 | msgKey = s 29 | genLevelSlices() 30 | } 31 | 32 | // LevelKey sets the key for the level field. 33 | func LevelKey(s string) { 34 | levelKey = s 35 | genLevelSlices() 36 | } 37 | 38 | // Encoder is an alias to gojay.Encoder. 39 | type Encoder = gojay.Encoder 40 | 41 | // Object is an alias to gojay.EncodeObjectFunc. 42 | type Object = gojay.EncodeObjectFunc 43 | 44 | // ExitFunc is used to exit the app, `os.Exit()` is set as default on `New()` 45 | type ExitFunc func(int) 46 | 47 | // Logger is the type representing a logger. 48 | type Logger struct { 49 | hook func(Entry) 50 | w io.Writer 51 | levels uint8 52 | ctx []func(Entry) 53 | ExitFn ExitFunc 54 | contextName string 55 | } 56 | 57 | // New returns a fresh onelog Logger with default values. 58 | func New(w io.Writer, levels uint8) *Logger { 59 | if w == nil { 60 | w = ioutil.Discard 61 | } 62 | 63 | return &Logger{ 64 | w: w, 65 | levels: levels, 66 | ExitFn: os.Exit, 67 | } 68 | } 69 | 70 | // NewContext returns a fresh onelog Logger with default values and 71 | // context name set to provided contextName value. 72 | func NewContext(w io.Writer, levels uint8, contextName string) *Logger { 73 | if w == nil { 74 | w = ioutil.Discard 75 | } 76 | 77 | return &Logger{ 78 | w: w, 79 | levels: levels, 80 | contextName: contextName, 81 | ExitFn: os.Exit, 82 | } 83 | } 84 | 85 | // Hook sets a hook to run for all log entries to add generic fields 86 | func (l *Logger) Hook(h func(Entry)) *Logger { 87 | l.hook = h 88 | return l 89 | } 90 | 91 | func (l *Logger) copy(ctxName string) *Logger { 92 | nL := &Logger{ 93 | levels: l.levels, 94 | w: l.w, 95 | hook: l.hook, 96 | contextName: ctxName, 97 | ExitFn: l.ExitFn, 98 | } 99 | if len(l.ctx) > 0 { 100 | var ctx = make([]func(e Entry), len(l.ctx)) 101 | copy(ctx, l.ctx) 102 | nL.ctx = ctx 103 | } 104 | return nL 105 | } 106 | 107 | // With copies the current Logger and adds it a given context by running func f. 108 | func (l *Logger) With(f func(Entry)) *Logger { 109 | nL := l.copy(l.contextName) 110 | 111 | if len(nL.ctx) == 0 { 112 | nL.ctx = make([]func(Entry), 0, 1) 113 | } 114 | 115 | nL.ctx = append(nL.ctx, f) 116 | return nL 117 | } 118 | 119 | // WithContext copies current logger enforcing all entry fields to be 120 | // set into a map with the contextName set as the key name for giving map. 121 | // This allows allocating all future uses of the logging methods to 122 | // follow such formatting. The only exception are values provided by 123 | // added hooks which will remain within the root level of generated json. 124 | func (l *Logger) WithContext(contextName string) *Logger { 125 | nl := l.copy(contextName) 126 | return nl 127 | } 128 | 129 | // Info logs an entry with INFO level. 130 | func (l *Logger) Info(msg string) { 131 | // first find writer for level 132 | // if none, stop 133 | if INFO&l.levels == 0 { 134 | return 135 | } 136 | e := Entry{Level: INFO, Message: msg} 137 | 138 | enc := gojay.BorrowEncoder(l.w) 139 | e.enc = enc 140 | 141 | // if we do not require a context then we 142 | // format with formatter and return. 143 | if l.contextName == "" { 144 | l.beginEntry(e.Level, msg, e) 145 | l.runHook(e) 146 | } else { 147 | l.openEntry(enc) 148 | } 149 | 150 | l.closeEntry(e) 151 | l.finalizeIfContext(e) 152 | 153 | enc.Release() 154 | } 155 | 156 | // InfoWith return an ChainEntry with INFO level. 157 | func (l *Logger) InfoWith(msg string) ChainEntry { 158 | // first find writer for level 159 | // if none, stop 160 | e := ChainEntry{ 161 | Entry: Entry{ 162 | l: l, 163 | Level: INFO, 164 | Message: msg, 165 | }, 166 | } 167 | e.disabled = INFO&e.l.levels == 0 168 | if e.disabled { 169 | return e 170 | } 171 | 172 | e.Entry.enc = gojay.BorrowEncoder(l.w) 173 | 174 | // if we do not require a context then we 175 | // format with formatter and return. 176 | if l.contextName == "" { 177 | l.beginEntry(e.Level, msg, e.Entry) 178 | l.runHook(e.Entry) 179 | return e 180 | } 181 | 182 | l.openEntry(e.Entry.enc) 183 | return e 184 | } 185 | 186 | // InfoWithFields logs an entry with INFO level and custom fields. 187 | func (l *Logger) InfoWithFields(msg string, fields func(Entry)) { 188 | // first find writer for level 189 | // if none, stop 190 | if INFO&l.levels == 0 { 191 | return 192 | } 193 | e := Entry{Level: INFO, Message: msg} 194 | 195 | e.enc = gojay.BorrowEncoder(l.w) 196 | 197 | // if we do not require a context then we 198 | // format with formatter and return. 199 | if l.contextName == "" { 200 | l.beginEntry(e.Level, msg, e) 201 | l.runHook(e) 202 | } else { 203 | l.openEntry(e.enc) 204 | } 205 | 206 | fields(e) 207 | l.closeEntry(e) 208 | l.finalizeIfContext(e) 209 | 210 | e.enc.Release() 211 | } 212 | 213 | // Debug logs an entry with DEBUG level. 214 | func (l *Logger) Debug(msg string) { 215 | // check if level is in config 216 | // if not, return 217 | if DEBUG&l.levels == 0 { 218 | return 219 | } 220 | e := Entry{Level: DEBUG, Message: msg} 221 | 222 | e.enc = gojay.BorrowEncoder(l.w) 223 | 224 | // if we do not require a context then we 225 | // format with formatter and return. 226 | if l.contextName == "" { 227 | l.beginEntry(e.Level, msg, e) 228 | l.runHook(e) 229 | } else { 230 | l.openEntry(e.enc) 231 | } 232 | 233 | l.closeEntry(e) 234 | l.finalizeIfContext(e) 235 | 236 | e.enc.Release() 237 | } 238 | 239 | // DebugWith return ChainEntry with DEBUG level. 240 | func (l *Logger) DebugWith(msg string) ChainEntry { 241 | // first find writer for level 242 | // if none, stop 243 | e := ChainEntry{ 244 | Entry: Entry{ 245 | l: l, 246 | Level: DEBUG, 247 | Message: msg, 248 | }, 249 | } 250 | e.disabled = DEBUG&e.l.levels == 0 251 | if e.disabled { 252 | return e 253 | } 254 | 255 | e.Entry.enc = gojay.BorrowEncoder(l.w) 256 | 257 | // if we do not require a context then we 258 | // format with formatter and return. 259 | if l.contextName == "" { 260 | l.beginEntry(e.Level, msg, e.Entry) 261 | l.runHook(e.Entry) 262 | return e 263 | } 264 | 265 | l.openEntry(e.Entry.enc) 266 | return e 267 | } 268 | 269 | // DebugWithFields logs an entry with DEBUG level and custom fields. 270 | func (l *Logger) DebugWithFields(msg string, fields func(Entry)) { 271 | // check if level is in config 272 | // if not, return 273 | if DEBUG&l.levels == 0 { 274 | return 275 | } 276 | e := Entry{Level: DEBUG, Message: msg} 277 | 278 | e.enc = gojay.BorrowEncoder(l.w) 279 | 280 | // if we do not require a context then we 281 | // format with formatter and return. 282 | if l.contextName == "" { 283 | l.beginEntry(e.Level, msg, e) 284 | l.runHook(e) 285 | } else { 286 | l.openEntry(e.enc) 287 | } 288 | 289 | fields(e) 290 | l.closeEntry(e) 291 | l.finalizeIfContext(e) 292 | 293 | e.enc.Release() 294 | } 295 | 296 | // Warn logs an entry with WARN level. 297 | func (l *Logger) Warn(msg string) { 298 | // check if level is in config 299 | // if not, return 300 | if WARN&l.levels == 0 { 301 | return 302 | } 303 | e := Entry{Level: WARN, Message: msg} 304 | 305 | e.enc = gojay.BorrowEncoder(l.w) 306 | 307 | // if we do not require a context then we 308 | // format with formatter and return. 309 | if l.contextName == "" { 310 | l.beginEntry(e.Level, msg, e) 311 | l.runHook(e) 312 | } else { 313 | l.openEntry(e.enc) 314 | } 315 | 316 | l.closeEntry(e) 317 | l.finalizeIfContext(e) 318 | 319 | e.enc.Release() 320 | } 321 | 322 | // WarnWith returns a ChainEntry with WARN level 323 | func (l *Logger) WarnWith(msg string) ChainEntry { 324 | // first find writer for level 325 | // if none, stop 326 | e := ChainEntry{ 327 | Entry: Entry{ 328 | l: l, 329 | Level: WARN, 330 | Message: msg, 331 | }, 332 | } 333 | e.disabled = WARN&e.l.levels == 0 334 | if e.disabled { 335 | return e 336 | } 337 | 338 | e.Entry.enc = gojay.BorrowEncoder(l.w) 339 | 340 | // if we do not require a context then we 341 | // format with formatter and return. 342 | if l.contextName == "" { 343 | l.beginEntry(e.Level, msg, e.Entry) 344 | l.runHook(e.Entry) 345 | return e 346 | } 347 | 348 | l.openEntry(e.Entry.enc) 349 | return e 350 | } 351 | 352 | // WarnWithFields logs an entry with WARN level and custom fields. 353 | func (l *Logger) WarnWithFields(msg string, fields func(Entry)) { 354 | if WARN&l.levels == 0 { 355 | return 356 | } 357 | e := Entry{ 358 | Level: WARN, 359 | Message: msg, 360 | } 361 | 362 | e.enc = gojay.BorrowEncoder(l.w) 363 | 364 | // if we do not require a context then we 365 | // format with formatter and return. 366 | if l.contextName == "" { 367 | l.beginEntry(e.Level, msg, e) 368 | l.runHook(e) 369 | } else { 370 | l.openEntry(e.enc) 371 | } 372 | 373 | fields(e) 374 | l.closeEntry(e) 375 | l.finalizeIfContext(e) 376 | 377 | e.enc.Release() 378 | } 379 | 380 | // Error logs an entry with ERROR level 381 | func (l *Logger) Error(msg string) { 382 | if ERROR&l.levels == 0 { 383 | return 384 | } 385 | e := Entry{ 386 | Level: ERROR, 387 | Message: msg, 388 | } 389 | 390 | e.enc = gojay.BorrowEncoder(l.w) 391 | 392 | // if we do not require a context then we 393 | // format with formatter and return. 394 | if l.contextName == "" { 395 | l.beginEntry(e.Level, msg, e) 396 | l.runHook(e) 397 | } else { 398 | l.openEntry(e.enc) 399 | } 400 | 401 | l.closeEntry(e) 402 | l.finalizeIfContext(e) 403 | 404 | e.enc.Release() 405 | } 406 | 407 | // ErrorWith returns a ChainEntry with ERROR level. 408 | func (l *Logger) ErrorWith(msg string) ChainEntry { 409 | // first find writer for level 410 | // if none, stop 411 | e := ChainEntry{ 412 | Entry: Entry{ 413 | l: l, 414 | Level: ERROR, 415 | Message: msg, 416 | }, 417 | } 418 | e.disabled = ERROR&e.l.levels == 0 419 | if e.disabled { 420 | return e 421 | } 422 | 423 | e.Entry.enc = gojay.BorrowEncoder(l.w) 424 | 425 | // if we do not require a context then we 426 | // format with formatter and return. 427 | if l.contextName == "" { 428 | l.beginEntry(e.Level, msg, e.Entry) 429 | l.runHook(e.Entry) 430 | return e 431 | } 432 | 433 | l.openEntry(e.Entry.enc) 434 | return e 435 | } 436 | 437 | // ErrorWithFields logs an entry with ERROR level and custom fields. 438 | func (l *Logger) ErrorWithFields(msg string, fields func(Entry)) { 439 | if ERROR&l.levels == 0 { 440 | return 441 | } 442 | e := Entry{ 443 | Level: ERROR, 444 | Message: msg, 445 | } 446 | 447 | e.enc = gojay.BorrowEncoder(l.w) 448 | 449 | // if we do not require a context then we 450 | // format with formatter and return. 451 | if l.contextName == "" { 452 | l.beginEntry(e.Level, msg, e) 453 | l.runHook(e) 454 | } else { 455 | l.openEntry(e.enc) 456 | } 457 | 458 | fields(e) 459 | l.closeEntry(e) 460 | l.finalizeIfContext(e) 461 | e.enc.Release() 462 | } 463 | 464 | // Fatal logs an entry with FATAL level. 465 | func (l *Logger) Fatal(msg string) { 466 | if FATAL&l.levels == 0 { 467 | return 468 | } 469 | e := Entry{ 470 | Level: FATAL, 471 | Message: msg, 472 | } 473 | 474 | e.enc = gojay.BorrowEncoder(l.w) 475 | 476 | // if we do not require a context then we 477 | // format with formatter and return. 478 | if l.contextName == "" { 479 | l.beginEntry(e.Level, msg, e) 480 | l.runHook(e) 481 | } else { 482 | l.openEntry(e.enc) 483 | } 484 | 485 | l.closeEntry(e) 486 | l.finalizeIfContext(e) 487 | 488 | e.enc.Release() 489 | 490 | l.exit(1) 491 | } 492 | 493 | // FatalWith returns a ChainEntry with FATAL level. 494 | func (l *Logger) FatalWith(msg string) ChainEntry { 495 | // first find writer for level 496 | // if none, stop 497 | e := ChainEntry{ 498 | Entry: Entry{ 499 | l: l, 500 | Level: FATAL, 501 | Message: msg, 502 | }, 503 | } 504 | e.disabled = FATAL&e.l.levels == 0 505 | if e.disabled { 506 | return e 507 | } 508 | 509 | e.Entry.enc = gojay.BorrowEncoder(l.w) 510 | 511 | // if we do not require a context then we 512 | // format with formatter and return. 513 | if l.contextName == "" { 514 | l.beginEntry(e.Level, msg, e.Entry) 515 | l.runHook(e.Entry) 516 | return e 517 | } 518 | 519 | l.openEntry(e.Entry.enc) 520 | e.exit = true 521 | return e 522 | } 523 | 524 | // FatalWithFields logs an entry with FATAL level and custom fields. 525 | func (l *Logger) FatalWithFields(msg string, fields func(Entry)) { 526 | if FATAL&l.levels == 0 { 527 | return 528 | } 529 | 530 | e := Entry{ 531 | Level: FATAL, 532 | Message: msg, 533 | } 534 | 535 | e.enc = gojay.BorrowEncoder(l.w) 536 | 537 | // if we do not require a context then we 538 | // format with formatter and return. 539 | if l.contextName == "" { 540 | l.beginEntry(e.Level, msg, e) 541 | l.runHook(e) 542 | } else { 543 | l.openEntry(e.enc) 544 | } 545 | 546 | fields(e) 547 | l.closeEntry(e) 548 | l.finalizeIfContext(e) 549 | 550 | e.enc.Release() 551 | l.exit(1) 552 | } 553 | 554 | func (l *Logger) openEntry(enc *Encoder) { 555 | enc.AppendBytes(logOpen) 556 | } 557 | 558 | func (l *Logger) beginEntry(level uint8, msg string, e Entry) { 559 | e.enc.AppendBytes(levelsJSON[level]) 560 | e.enc.AppendString(msg) 561 | 562 | if l.ctx != nil && l.contextName == "" { 563 | for _, c := range l.ctx { 564 | c(e) 565 | } 566 | } 567 | } 568 | 569 | func (l Logger) runHook(e Entry) { 570 | if l.hook == nil { 571 | return 572 | } 573 | l.hook(e) 574 | } 575 | 576 | func (l *Logger) finalizeIfContext(entry Entry) { 577 | if l.contextName == "" { 578 | return 579 | } 580 | 581 | embeddedEnc := entry.enc 582 | 583 | // create a new encoder for the final output. 584 | entryEnc := gojay.BorrowEncoder(l.w) 585 | defer entryEnc.Release() 586 | 587 | entry.enc = entryEnc 588 | 589 | // create dummy entry for applying hooks. 590 | l.beginEntry(entry.Level, entry.Message, entry) 591 | l.runHook(entry) 592 | 593 | // Add entry's encoded data into new encoder. 594 | var embeddedJSON = gojay.EmbeddedJSON(embeddedEnc.Buf()) 595 | entryEnc.AddEmbeddedJSONKey(l.contextName, &embeddedJSON) 596 | 597 | // close new encoder context for proper json. 598 | entryEnc.AppendBytes(logClose) 599 | 600 | // we need to manually write output as logger 601 | // has context. 602 | entryEnc.Write() 603 | } 604 | 605 | func (l *Logger) closeEntry(e Entry) { 606 | if l.contextName == "" { 607 | e.enc.AppendBytes(logClose) 608 | } else { 609 | if l.ctx != nil { 610 | for _, c := range l.ctx { 611 | c(e) 612 | } 613 | } 614 | e.enc.AppendBytes(logCloseOnly) 615 | } 616 | 617 | if l.contextName == "" { 618 | e.enc.Write() 619 | } 620 | } 621 | 622 | func (l *Logger) exit(code int) { 623 | if l.ExitFn == nil { 624 | // fallback to os.Exit to prevent panic incase set as nil. 625 | os.Exit(code) 626 | } 627 | l.ExitFn(code) 628 | } 629 | 630 | // Caller returns the caller in the stack trace, skipped n times. 631 | func (l *Logger) Caller(n int) string { 632 | _, f, fl, _ := runtime.Caller(n) 633 | flStr := strconv.Itoa(fl) 634 | return f + ":" + flStr 635 | } 636 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | package onelog 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/francoispqt/gojay" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | type TestWriter struct { 15 | b []byte 16 | called bool 17 | } 18 | 19 | func (t *TestWriter) Write(b []byte) (int, error) { 20 | t.called = true 21 | if len(t.b) < len(b) { 22 | t.b = make([]byte, len(b)) 23 | } 24 | copy(t.b, b) 25 | return len(t.b), nil 26 | } 27 | 28 | func newWriter() *TestWriter { 29 | return &TestWriter{make([]byte, 0, 512), false} 30 | } 31 | 32 | type TestObj struct { 33 | foo string 34 | } 35 | 36 | func (t *TestObj) MarshalJSONObject(enc *gojay.Encoder) { 37 | enc.AddStringKey("foo", t.foo) 38 | } 39 | 40 | func (t *TestObj) IsNil() bool { 41 | return t == nil 42 | } 43 | 44 | type TestObjArr []*TestObj 45 | 46 | func (t TestObjArr) MarshalJSONArray(enc *gojay.Encoder) { 47 | for _, o := range t { 48 | enc.AddObject(o) 49 | } 50 | } 51 | 52 | func (t TestObjArr) IsNil() bool { 53 | return len(t) == 0 54 | } 55 | 56 | func TestOnelogFeature(t *testing.T) { 57 | t.Run("custom-msg-key", func(t *testing.T) { 58 | w := newWriter() 59 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 60 | MsgKey("test") 61 | logger.Info("message") 62 | assert.Equal(t, `{"level":"info","test":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 63 | MsgKey("message") 64 | }) 65 | t.Run("custom-lvl-key", func(t *testing.T) { 66 | w := newWriter() 67 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 68 | LevelKey("test") 69 | logger.Info("message") 70 | assert.Equal(t, `{"test":"info","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 71 | LevelKey("level") 72 | }) 73 | t.Run("custom-lvl-text", func(t *testing.T) { 74 | w := newWriter() 75 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 76 | LevelText(DEBUG, "DEBUG") 77 | logger.Debug("message") 78 | assert.Equal(t, `{"level":"DEBUG","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 79 | LevelText(DEBUG, "debug") 80 | }) 81 | t.Run("caller", func(t *testing.T) { 82 | logger := New(nil, DEBUG|INFO|WARN|ERROR|FATAL) 83 | str := logger.Caller(1) 84 | strs := strings.Split(str, "/") 85 | assert.Equal(t, "logger_test.go:83", strs[len(strs)-1], "file should be logger_test.go:81") 86 | }) 87 | } 88 | func TestOnelogWithoutFields(t *testing.T) { 89 | t.Run("basic-message-info", func(t *testing.T) { 90 | w := newWriter() 91 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 92 | logger.Info("message") 93 | assert.Equal(t, `{"level":"info","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 94 | }) 95 | t.Run("basic-message-debug", func(t *testing.T) { 96 | w := newWriter() 97 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 98 | logger.Debug("message") 99 | assert.Equal(t, `{"level":"debug","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 100 | }) 101 | t.Run("basic-message-warn", func(t *testing.T) { 102 | w := newWriter() 103 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 104 | logger.Warn("message") 105 | assert.Equal(t, `{"level":"warn","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 106 | }) 107 | t.Run("basic-message-error", func(t *testing.T) { 108 | w := newWriter() 109 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 110 | logger.Error("message") 111 | assert.Equal(t, `{"level":"error","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 112 | }) 113 | t.Run("basic-message-fatal", func(t *testing.T) { 114 | w := newWriter() 115 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 116 | logger.ExitFn = func(c int) { 117 | panic("os.Exit called") 118 | } 119 | defer func() { 120 | assert.Equal(t, `{"level":"fatal","message":"message"}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 121 | r := recover() 122 | if r == nil { 123 | t.Errorf("logger.Fatal() recover = %v", r) 124 | } 125 | }() 126 | logger.Fatal("message") 127 | }) 128 | t.Run("basic-message-disabled-level-info", func(t *testing.T) { 129 | w := newWriter() 130 | logger := New(w, DEBUG|WARN|ERROR|FATAL) 131 | logger.Info("message") 132 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 133 | }) 134 | t.Run("basic-message-disabled-level-debug", func(t *testing.T) { 135 | w := newWriter() 136 | logger := New(w, INFO|WARN|ERROR|FATAL) 137 | logger.Debug("message") 138 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 139 | }) 140 | t.Run("basic-message-disabled-level-warn", func(t *testing.T) { 141 | w := newWriter() 142 | logger := New(w, INFO|DEBUG|ERROR|FATAL) 143 | logger.Warn("message") 144 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 145 | }) 146 | t.Run("basic-message-disabled-level-error", func(t *testing.T) { 147 | w := newWriter() 148 | logger := New(w, INFO|WARN|DEBUG|FATAL) 149 | logger.Error("message") 150 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 151 | }) 152 | t.Run("basic-message-disabled-level-fatal", func(t *testing.T) { 153 | w := newWriter() 154 | logger := New(w, INFO|WARN|ERROR|DEBUG) 155 | logger.Fatal("message") 156 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 157 | }) 158 | } 159 | 160 | func TestOnelogContextWithoutFields(t *testing.T) { 161 | t.Run("basic-message-info", func(t *testing.T) { 162 | w := newWriter() 163 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 164 | logger.Info("message") 165 | assert.Equal(t, `{"level":"info","message":"message","params":{}}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 166 | }) 167 | t.Run("basic-message-debug", func(t *testing.T) { 168 | w := newWriter() 169 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 170 | logger.Debug("message") 171 | assert.Equal(t, `{"level":"debug","message":"message","params":{}}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 172 | }) 173 | t.Run("basic-message-warn", func(t *testing.T) { 174 | w := newWriter() 175 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 176 | logger.Warn("message") 177 | assert.Equal(t, `{"level":"warn","message":"message","params":{}}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 178 | }) 179 | t.Run("basic-message-error", func(t *testing.T) { 180 | w := newWriter() 181 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 182 | logger.Error("message") 183 | assert.Equal(t, `{"level":"error","message":"message","params":{}}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 184 | }) 185 | t.Run("basic-message-fatal", func(t *testing.T) { 186 | w := newWriter() 187 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 188 | logger.ExitFn = func(c int) { 189 | panic("os.Exit called") 190 | } 191 | defer func() { 192 | assert.Equal(t, `{"level":"fatal","message":"message","params":{}}`+"\n", string(w.b), "bytes written to the writer dont equal expected result") 193 | r := recover() 194 | if r == nil { 195 | t.Errorf("logger.Fatal() recover = %v", r) 196 | } 197 | }() 198 | logger.Fatal("message") 199 | }) 200 | t.Run("basic-message-disabled-level-info", func(t *testing.T) { 201 | w := newWriter() 202 | logger := New(w, DEBUG|WARN|ERROR|FATAL) 203 | logger.Info("message") 204 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 205 | }) 206 | t.Run("basic-message-disabled-level-debug", func(t *testing.T) { 207 | w := newWriter() 208 | logger := New(w, INFO|WARN|ERROR|FATAL) 209 | logger.Debug("message") 210 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 211 | }) 212 | t.Run("basic-message-disabled-level-warn", func(t *testing.T) { 213 | w := newWriter() 214 | logger := New(w, INFO|DEBUG|ERROR|FATAL) 215 | logger.Warn("message") 216 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 217 | }) 218 | t.Run("basic-message-disabled-level-error", func(t *testing.T) { 219 | w := newWriter() 220 | logger := New(w, INFO|WARN|DEBUG|FATAL) 221 | logger.Error("message") 222 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 223 | }) 224 | t.Run("basic-message-disabled-level-fatal", func(t *testing.T) { 225 | w := newWriter() 226 | logger := New(w, INFO|WARN|ERROR|DEBUG) 227 | logger.ExitFn = func(c int) { 228 | panic("os.Exit called") 229 | } 230 | defer func() { 231 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 232 | r := recover() 233 | if r != nil { 234 | t.Errorf("logger.Fatal() recover = %v", r) 235 | } 236 | }() 237 | logger.Fatal("message") 238 | }) 239 | } 240 | 241 | func TestOnelogWithFields(t *testing.T) { 242 | t.Run("fields-info", func(t *testing.T) { 243 | testObj := &TestObj{foo: "bar"} 244 | testArr := TestObjArr{testObj} 245 | w := newWriter() 246 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 247 | logger.InfoWithFields("message", func(e Entry) { 248 | e.String("userID", "123456") 249 | e.String("action", "login") 250 | e.String("result", "success") 251 | e.Int("count", 100) 252 | e.Int64("int64", 100) 253 | e.Float("float64", 0.15) 254 | e.Bool("done", true) 255 | e.Err("error", errors.New("some error")) 256 | e.ObjectFunc("user", func(e Entry) { 257 | e.String("name", "somename") 258 | }) 259 | e.Object("testObj", testObj) 260 | e.Array("testArr", testArr) 261 | }) 262 | json := `{"level":"info","message":"message","userID":"123456",` + 263 | `"action":"login","result":"success","count":100,"int64":100,"float64":0.15,"done":true,` + 264 | `"error":"some error","user":{"name":"somename"},"testObj":{"foo":"bar"},` + 265 | `"testArr":[{"foo":"bar"}]}` + "\n" 266 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 267 | }) 268 | t.Run("fields-debug", func(t *testing.T) { 269 | w := newWriter() 270 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 271 | logger.DebugWithFields("message", func(e Entry) { 272 | e.String("userID", "123456") 273 | e.String("action", "login") 274 | e.String("result", "success") 275 | }) 276 | json := `{"level":"debug","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 277 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 278 | }) 279 | t.Run("fields-warn", func(t *testing.T) { 280 | w := newWriter() 281 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 282 | logger.WarnWithFields("message", func(e Entry) { 283 | e.String("userID", "123456") 284 | e.String("action", "login") 285 | e.String("result", "success") 286 | }) 287 | json := `{"level":"warn","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 288 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 289 | }) 290 | t.Run("fields-error", func(t *testing.T) { 291 | w := newWriter() 292 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 293 | logger.ErrorWithFields("message", func(e Entry) { 294 | e.String("userID", "123456") 295 | e.String("action", "login") 296 | e.String("result", "success") 297 | }) 298 | json := `{"level":"error","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 299 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 300 | }) 301 | t.Run("fields-fatal", func(t *testing.T) { 302 | w := newWriter() 303 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 304 | logger.ExitFn = func(c int) { 305 | panic("os.Exit called") 306 | } 307 | defer func() { 308 | json := `{"level":"fatal","message":"message","userID":"123456","action":"login","result":"success","int64":120}` + "\n" 309 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 310 | r := recover() 311 | if r == nil { 312 | t.Errorf("logger.Fatal() recover = %v", r) 313 | } 314 | }() 315 | logger.FatalWithFields("message", func(e Entry) { 316 | e.String("userID", "123456") 317 | e.String("action", "login") 318 | e.String("result", "success") 319 | e.Int64("int64", 120) 320 | }) 321 | }) 322 | t.Run("fields-disabled-level-info", func(t *testing.T) { 323 | w := newWriter() 324 | logger := New(w, DEBUG|WARN|ERROR|FATAL) 325 | logger.InfoWithFields("message", func(e Entry) { 326 | e.String("userID", "123456") 327 | e.String("action", "login") 328 | e.String("result", "success") 329 | }) 330 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 331 | assert.False(t, w.called, "writer should not be called") 332 | }) 333 | t.Run("basic-message-disabled-level-debug", func(t *testing.T) { 334 | w := newWriter() 335 | logger := New(w, INFO|WARN|ERROR|FATAL) 336 | logger.DebugWithFields("message", func(e Entry) { 337 | e.String("userID", "123456") 338 | e.String("action", "login") 339 | e.String("result", "success") 340 | }) 341 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 342 | assert.False(t, w.called, "writer should not be called") 343 | }) 344 | t.Run("basic-message-disabled-level-warn", func(t *testing.T) { 345 | w := newWriter() 346 | logger := New(w, INFO|DEBUG|ERROR|FATAL) 347 | logger.WarnWithFields("message", func(e Entry) { 348 | e.String("userID", "123456") 349 | e.String("action", "login") 350 | e.String("result", "success") 351 | }) 352 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 353 | assert.False(t, w.called, "writer should not be called") 354 | }) 355 | t.Run("basic-message-disabled-level-error", func(t *testing.T) { 356 | w := newWriter() 357 | logger := New(w, INFO|WARN|DEBUG|FATAL) 358 | logger.ErrorWithFields("message", func(e Entry) { 359 | e.String("userID", "123456") 360 | e.String("action", "login") 361 | e.String("result", "success") 362 | }) 363 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 364 | assert.False(t, w.called, "writer should not be called") 365 | }) 366 | t.Run("basic-message-disabled-level-fatal", func(t *testing.T) { 367 | w := newWriter() 368 | logger := New(w, INFO|WARN|ERROR|DEBUG) 369 | logger.ExitFn = func(c int) { 370 | panic("os.Exit called") 371 | } 372 | defer func() { 373 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 374 | assert.False(t, w.called, "writer should not be called") 375 | r := recover() 376 | if r != nil { 377 | t.Errorf("logger.Fatal() recover = %v", r) 378 | } 379 | }() 380 | logger.FatalWithFields("message", func(e Entry) { 381 | e.String("userID", "123456") 382 | e.String("action", "login") 383 | e.String("result", "success") 384 | }) 385 | }) 386 | } 387 | 388 | func TestOnelogHook(t *testing.T) { 389 | t.Run("hook-basic-info", func(t *testing.T) { 390 | w := newWriter() 391 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 392 | logger.Hook(func(e Entry) { 393 | e.String("userID", "123456") 394 | e.String("action", "login") 395 | e.String("result", "success") 396 | }) 397 | logger.Info("message") 398 | json := `{"level":"info","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 399 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 400 | }) 401 | t.Run("hook-basic-debug", func(t *testing.T) { 402 | w := newWriter() 403 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 404 | logger.Hook(func(e Entry) { 405 | e.String("userID", "123456") 406 | e.String("action", "login") 407 | e.String("result", "success") 408 | }) 409 | logger.Debug("message") 410 | json := `{"level":"debug","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 411 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 412 | }) 413 | t.Run("hook-basic-warn", func(t *testing.T) { 414 | w := newWriter() 415 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 416 | logger.Hook(func(e Entry) { 417 | e.String("userID", "123456") 418 | e.String("action", "login") 419 | e.String("result", "success") 420 | }) 421 | logger.Warn("message") 422 | json := `{"level":"warn","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 423 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 424 | }) 425 | t.Run("hook-basic-error", func(t *testing.T) { 426 | w := newWriter() 427 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 428 | logger.Hook(func(e Entry) { 429 | e.String("userID", "123456") 430 | e.String("action", "login") 431 | e.String("result", "success") 432 | }) 433 | logger.Error("message") 434 | json := `{"level":"error","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 435 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 436 | }) 437 | t.Run("hook-basic-fatal", func(t *testing.T) { 438 | w := newWriter() 439 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 440 | logger.Hook(func(e Entry) { 441 | e.String("userID", "123456") 442 | e.String("action", "login") 443 | e.String("result", "success") 444 | }) 445 | logger.ExitFn = func(c int) { 446 | panic("os.Exit called") 447 | } 448 | defer func() { 449 | json := `{"level":"fatal","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 450 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 451 | r := recover() 452 | if r == nil { 453 | t.Errorf("logger.Fatal() recover = %v", r) 454 | } 455 | }() 456 | logger.Fatal("message") 457 | }) 458 | t.Run("hook-fields-info", func(t *testing.T) { 459 | w := newWriter() 460 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 461 | logger.Hook(func(e Entry) { 462 | e.String("userID", "123456") 463 | e.String("action", "login") 464 | e.String("result", "success") 465 | }) 466 | logger.InfoWithFields("message", func(e Entry) { 467 | e.String("field", "field") 468 | }) 469 | json := `{"level":"info","message":"message","userID":"123456","action":"login","result":"success","field":"field"}` + "\n" 470 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 471 | }) 472 | t.Run("hook-fields-debug", func(t *testing.T) { 473 | w := newWriter() 474 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 475 | logger.Hook(func(e Entry) { 476 | e.String("userID", "123456") 477 | e.String("action", "login") 478 | e.String("result", "success") 479 | }) 480 | logger.DebugWithFields("message", func(e Entry) { 481 | e.String("field", "field") 482 | }) 483 | json := `{"level":"debug","message":"message","userID":"123456","action":"login","result":"success","field":"field"}` + "\n" 484 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 485 | }) 486 | t.Run("hook-fields-warn", func(t *testing.T) { 487 | w := newWriter() 488 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 489 | logger.Hook(func(e Entry) { 490 | e.String("userID", "123456") 491 | e.String("action", "login") 492 | e.String("result", "success") 493 | }) 494 | logger.WarnWithFields("message", func(e Entry) { 495 | e.String("field", "field") 496 | }) 497 | json := `{"level":"warn","message":"message","userID":"123456","action":"login","result":"success","field":"field"}` + "\n" 498 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 499 | }) 500 | t.Run("hook-fields-error", func(t *testing.T) { 501 | w := newWriter() 502 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 503 | logger.Hook(func(e Entry) { 504 | e.String("userID", "123456") 505 | e.String("action", "login") 506 | e.String("result", "success") 507 | }) 508 | logger.ErrorWithFields("message", func(e Entry) { 509 | e.String("field", "field") 510 | }) 511 | json := `{"level":"error","message":"message","userID":"123456","action":"login","result":"success","field":"field"}` + "\n" 512 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 513 | }) 514 | t.Run("hook-fields-fatal", func(t *testing.T) { 515 | w := newWriter() 516 | logger := New(w, DEBUG|INFO|WARN|ERROR|FATAL) 517 | logger.ExitFn = func(c int) { 518 | panic("os.Exit called") 519 | } 520 | defer func() { 521 | json := `{"level":"fatal","message":"message","userID":"123456","action":"login","result":"success","field":"field"}` + "\n" 522 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 523 | r := recover() 524 | if r == nil { 525 | t.Errorf("logger.Fatal() recover = %v", r) 526 | } 527 | }() 528 | logger.Hook(func(e Entry) { 529 | e.String("userID", "123456") 530 | e.String("action", "login") 531 | e.String("result", "success") 532 | }) 533 | logger.FatalWithFields("message", func(e Entry) { 534 | e.String("field", "field") 535 | }) 536 | }) 537 | } 538 | 539 | func TestOnelogContext(t *testing.T) { 540 | t.Run("context-info-basic", func(t *testing.T) { 541 | w := newWriter() 542 | logger := New(w, ALL).With(func(e Entry) { 543 | e.String("test", "test") 544 | }) 545 | logger.Info("test") 546 | json := `{"level":"info","message":"test","test":"test"}` + "\n" 547 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 548 | 549 | }) 550 | t.Run("context-info-fields", func(t *testing.T) { 551 | w := newWriter() 552 | logger := New(w, ALL).With(func(e Entry) { 553 | e.String("test", "test") 554 | }) 555 | logger.InfoWithFields("test", func(e Entry) { 556 | e.String("field", "field") 557 | }) 558 | json := `{"level":"info","message":"test","test":"test","field":"field"}` + "\n" 559 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 560 | }) 561 | 562 | t.Run("context-debug-basic", func(t *testing.T) { 563 | w := newWriter() 564 | logger := New(w, ALL).With(func(e Entry) { 565 | e.String("test", "test") 566 | }) 567 | logger.Debug("test") 568 | json := `{"level":"debug","message":"test","test":"test"}` + "\n" 569 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 570 | 571 | }) 572 | t.Run("context-debug-fields", func(t *testing.T) { 573 | w := newWriter() 574 | logger := New(w, ALL).With(func(e Entry) { 575 | e.String("test", "test") 576 | }) 577 | logger.DebugWithFields("test", func(e Entry) { 578 | e.String("field", "field") 579 | }) 580 | json := `{"level":"debug","message":"test","test":"test","field":"field"}` + "\n" 581 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 582 | }) 583 | 584 | t.Run("context-warn-basic", func(t *testing.T) { 585 | w := newWriter() 586 | logger := New(w, ALL).With(func(e Entry) { 587 | e.String("test", "test") 588 | }) 589 | logger.Warn("test") 590 | json := `{"level":"warn","message":"test","test":"test"}` + "\n" 591 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 592 | 593 | }) 594 | t.Run("context-warn-fields", func(t *testing.T) { 595 | w := newWriter() 596 | logger := New(w, ALL).With(func(e Entry) { 597 | e.String("test", "test") 598 | }) 599 | logger.WarnWithFields("test", func(e Entry) { 600 | e.String("field", "field") 601 | }) 602 | json := `{"level":"warn","message":"test","test":"test","field":"field"}` + "\n" 603 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 604 | }) 605 | 606 | t.Run("context-error-basic", func(t *testing.T) { 607 | w := newWriter() 608 | logger := New(w, ALL).With(func(e Entry) { 609 | e.String("test", "test") 610 | }) 611 | logger.Error("test") 612 | json := `{"level":"error","message":"test","test":"test"}` + "\n" 613 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 614 | 615 | }) 616 | t.Run("context-error-fields", func(t *testing.T) { 617 | w := newWriter() 618 | logger := New(w, ALL).With(func(e Entry) { 619 | e.String("test", "test") 620 | }) 621 | logger.ErrorWithFields("test", func(e Entry) { 622 | e.String("field", "field") 623 | }) 624 | json := `{"level":"error","message":"test","test":"test","field":"field"}` + "\n" 625 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 626 | }) 627 | 628 | t.Run("context-fatal-basic", func(t *testing.T) { 629 | w := newWriter() 630 | logger := New(w, ALL).With(func(e Entry) { 631 | e.String("test", "test") 632 | }) 633 | logger.ExitFn = func(c int) { 634 | panic("os.Exit called") 635 | } 636 | defer func() { 637 | json := `{"level":"fatal","message":"test","test":"test"}` + "\n" 638 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 639 | r := recover() 640 | if r == nil { 641 | t.Errorf("logger.Fatal() recover = %v", r) 642 | } 643 | }() 644 | logger.Fatal("test") 645 | }) 646 | t.Run("context-fatal-fields", func(t *testing.T) { 647 | w := newWriter() 648 | logger := New(w, ALL).With(func(e Entry) { 649 | e.String("test", "test") 650 | }) 651 | logger.ExitFn = func(c int) { 652 | panic("os.Exit called") 653 | } 654 | defer func() { 655 | json := `{"level":"fatal","message":"test","test":"test","field":"field"}` + "\n" 656 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 657 | r := recover() 658 | if r == nil { 659 | t.Errorf("logger.Fatal() recover = %v", r) 660 | } 661 | }() 662 | logger.FatalWithFields("test", func(e Entry) { 663 | e.String("field", "field") 664 | }) 665 | }) 666 | } 667 | 668 | func TestNilOutput(t *testing.T) { 669 | t.Run("nil-logger", func(t *testing.T) { 670 | logger := New(nil, 0) 671 | assert.NotNil(t, logger.w, "Logger output should not be nil") 672 | logger.Error("Test output") 673 | t.Log("Successfully write to ioutil.Discard output") 674 | }) 675 | t.Run("nil-context-logger", func(t *testing.T) { 676 | logger := NewContext(nil, 0, "params") 677 | assert.NotNil(t, logger.w, "Logger output should not be nil") 678 | logger.Error("Test output") 679 | t.Log("Successfully write to ioutil.Discard output") 680 | }) 681 | } 682 | 683 | func TestOnelogHooksWithAndContext(t *testing.T) { 684 | t.Run("fields-info", func(t *testing.T) { 685 | testObj := &TestObj{foo: "bar"} 686 | testArr := TestObjArr{testObj} 687 | w := newWriter() 688 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 689 | logger := parent.Hook(func(e Entry) { e.Int("thunder_frequency", 1000) }) 690 | logger.InfoWithFields("message", func(e Entry) { 691 | e.String("userID", "123456") 692 | e.String("action", "login") 693 | e.String("result", "success") 694 | e.Int("count", 100) 695 | e.Int64("int64", 100) 696 | e.Float("float64", 0.15) 697 | e.Bool("done", true) 698 | e.Err("error", errors.New("some error")) 699 | e.ObjectFunc("user", func(e Entry) { 700 | e.String("name", "somename") 701 | }) 702 | e.Object("testObj", testObj) 703 | e.Array("testArr", testArr) 704 | }) 705 | json := `{"level":"info","message":"message","thunder_frequency":1000,"params":{"userID":"123456",` + 706 | `"action":"login","result":"success","count":100,"int64":100,"float64":0.15,"done":true,` + 707 | `"error":"some error","user":{"name":"somename"},"testObj":{"foo":"bar"},` + 708 | `"testArr":[{"foo":"bar"}]}}` + "\n" 709 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 710 | }) 711 | t.Run("fields-debug", func(t *testing.T) { 712 | w := newWriter() 713 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 714 | logger := parent.Hook(func(e Entry) { e.Int("thunder_frequency", 1000) }) 715 | logger.DebugWithFields("message", func(e Entry) { 716 | e.String("userID", "123456") 717 | e.String("action", "login") 718 | e.String("result", "success") 719 | }) 720 | json := `{"level":"debug","message":"message","thunder_frequency":1000,"params":{"userID":"123456","action":"login","result":"success"}}` + "\n" 721 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 722 | }) 723 | t.Run("fields-warn", func(t *testing.T) { 724 | w := newWriter() 725 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 726 | logger := parent.Hook(func(e Entry) { e.Int("thunder_frequency", 1000) }) 727 | logger.WarnWithFields("message", func(e Entry) { 728 | e.String("userID", "123456") 729 | e.String("action", "login") 730 | e.String("result", "success") 731 | }) 732 | json := `{"level":"warn","message":"message","thunder_frequency":1000,"params":{"userID":"123456","action":"login","result":"success"}}` + "\n" 733 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 734 | }) 735 | t.Run("fields-error", func(t *testing.T) { 736 | w := newWriter() 737 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 738 | logger := parent.Hook(func(e Entry) { e.Int("thunder_frequency", 1000) }) 739 | logger.ErrorWithFields("message", func(e Entry) { 740 | e.String("userID", "123456") 741 | e.String("action", "login") 742 | e.String("result", "success") 743 | }) 744 | json := `{"level":"error","message":"message","thunder_frequency":1000,"params":{"userID":"123456","action":"login","result":"success"}}` + "\n" 745 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 746 | }) 747 | t.Run("fields-fatal", func(t *testing.T) { 748 | w := newWriter() 749 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 750 | logger := parent.Hook(func(e Entry) { e.Int("thunder_frequency", 1000) }) 751 | logger.ExitFn = func(c int) { 752 | panic("os.Exit called") 753 | } 754 | defer func() { 755 | json := `{"level":"fatal","message":"message","thunder_frequency":1000,"params":{"userID":"123456","action":"login","result":"success","int64":120}}` + "\n" 756 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 757 | r := recover() 758 | if r == nil { 759 | t.Errorf("logger.Fatal() recover = %v", r) 760 | } 761 | }() 762 | logger.FatalWithFields("message", func(e Entry) { 763 | e.String("userID", "123456") 764 | e.String("action", "login") 765 | e.String("result", "success") 766 | e.Int64("int64", 120) 767 | }) 768 | }) 769 | } 770 | 771 | func TestOnelogWithAndContext(t *testing.T) { 772 | t.Run("fields-info", func(t *testing.T) { 773 | testObj := &TestObj{foo: "bar"} 774 | testArr := TestObjArr{testObj} 775 | w := newWriter() 776 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 777 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 778 | logger.InfoWithFields("message", func(e Entry) { 779 | e.String("userID", "123456") 780 | e.String("action", "login") 781 | e.String("result", "success") 782 | e.Int("count", 100) 783 | e.Int64("int64", 100) 784 | e.Float("float64", 0.15) 785 | e.Bool("done", true) 786 | e.Err("error", errors.New("some error")) 787 | e.ObjectFunc("user", func(e Entry) { 788 | e.String("name", "somename") 789 | }) 790 | e.Object("testObj", testObj) 791 | e.Array("testArr", testArr) 792 | }) 793 | json := `{"level":"info","message":"message","params":{"userID":"123456",` + 794 | `"action":"login","result":"success","count":100,"int64":100,"float64":0.15,"done":true,` + 795 | `"error":"some error","user":{"name":"somename"},"testObj":{"foo":"bar"},` + 796 | `"testArr":[{"foo":"bar"}],"thunder_frequency":1000}}` + "\n" 797 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 798 | }) 799 | t.Run("fields-debug", func(t *testing.T) { 800 | w := newWriter() 801 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 802 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 803 | logger.DebugWithFields("message", func(e Entry) { 804 | e.String("userID", "123456") 805 | e.String("action", "login") 806 | e.String("result", "success") 807 | }) 808 | json := `{"level":"debug","message":"message","params":{"userID":"123456","action":"login","result":"success","thunder_frequency":1000}}` + "\n" 809 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 810 | }) 811 | t.Run("fields-warn", func(t *testing.T) { 812 | w := newWriter() 813 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 814 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 815 | logger.WarnWithFields("message", func(e Entry) { 816 | e.String("userID", "123456") 817 | e.String("action", "login") 818 | e.String("result", "success") 819 | }) 820 | json := `{"level":"warn","message":"message","params":{"userID":"123456","action":"login","result":"success","thunder_frequency":1000}}` + "\n" 821 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 822 | }) 823 | t.Run("fields-error", func(t *testing.T) { 824 | w := newWriter() 825 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 826 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 827 | logger.ErrorWithFields("message", func(e Entry) { 828 | e.String("userID", "123456") 829 | e.String("action", "login") 830 | e.String("result", "success") 831 | }) 832 | json := `{"level":"error","message":"message","params":{"userID":"123456","action":"login","result":"success","thunder_frequency":1000}}` + "\n" 833 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 834 | }) 835 | t.Run("fields-fatal", func(t *testing.T) { 836 | w := newWriter() 837 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 838 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 839 | logger.ExitFn = func(c int) { 840 | panic("os.Exit called") 841 | } 842 | defer func() { 843 | json := `{"level":"fatal","message":"message","params":{"userID":"123456","action":"login","result":"success","int64":120,"thunder_frequency":1000}}` + "\n" 844 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 845 | r := recover() 846 | if r == nil { 847 | t.Errorf("logger.Fatal() recover = %v", r) 848 | } 849 | }() 850 | logger.FatalWithFields("message", func(e Entry) { 851 | e.String("userID", "123456") 852 | e.String("action", "login") 853 | e.String("result", "success") 854 | e.Int64("int64", 120) 855 | }) 856 | }) 857 | } 858 | 859 | func TestOnelogWithFieldsAndContext(t *testing.T) { 860 | t.Run("fields-info", func(t *testing.T) { 861 | testObj := &TestObj{foo: "bar"} 862 | testArr := TestObjArr{testObj} 863 | w := newWriter() 864 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 865 | logger.InfoWithFields("message", func(e Entry) { 866 | e.String("userID", "123456") 867 | e.String("action", "login") 868 | e.String("result", "success") 869 | e.Int("count", 100) 870 | e.Int64("int64", 100) 871 | e.Float("float64", 0.15) 872 | e.Bool("done", true) 873 | e.Err("error", errors.New("some error")) 874 | e.ObjectFunc("user", func(e Entry) { 875 | e.String("name", "somename") 876 | }) 877 | e.Object("testObj", testObj) 878 | e.Array("testArr", testArr) 879 | }) 880 | json := `{"level":"info","message":"message","params":{"userID":"123456",` + 881 | `"action":"login","result":"success","count":100,"int64":100,"float64":0.15,"done":true,` + 882 | `"error":"some error","user":{"name":"somename"},"testObj":{"foo":"bar"},` + 883 | `"testArr":[{"foo":"bar"}]}}` + "\n" 884 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 885 | }) 886 | t.Run("fields-debug", func(t *testing.T) { 887 | w := newWriter() 888 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 889 | logger.DebugWithFields("message", func(e Entry) { 890 | e.String("userID", "123456") 891 | e.String("action", "login") 892 | e.String("result", "success") 893 | }) 894 | json := `{"level":"debug","message":"message","params":{"userID":"123456","action":"login","result":"success"}}` + "\n" 895 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 896 | }) 897 | t.Run("fields-warn", func(t *testing.T) { 898 | w := newWriter() 899 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 900 | logger.WarnWithFields("message", func(e Entry) { 901 | e.String("userID", "123456") 902 | e.String("action", "login") 903 | e.String("result", "success") 904 | }) 905 | json := `{"level":"warn","message":"message","params":{"userID":"123456","action":"login","result":"success"}}` + "\n" 906 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 907 | }) 908 | t.Run("fields-error", func(t *testing.T) { 909 | w := newWriter() 910 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 911 | logger.ErrorWithFields("message", func(e Entry) { 912 | e.String("userID", "123456") 913 | e.String("action", "login") 914 | e.String("result", "success") 915 | }) 916 | json := `{"level":"error","message":"message","params":{"userID":"123456","action":"login","result":"success"}}` + "\n" 917 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 918 | }) 919 | t.Run("fields-fatal", func(t *testing.T) { 920 | w := newWriter() 921 | logger := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 922 | logger.ExitFn = func(c int) { 923 | panic("os.Exit called") 924 | } 925 | defer func() { 926 | json := `{"level":"fatal","message":"message","params":{"userID":"123456","action":"login","result":"success","int64":120}}` + "\n" 927 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 928 | r := recover() 929 | if r == nil { 930 | t.Errorf("logger.Fatal() recover = %v", r) 931 | } 932 | }() 933 | logger.FatalWithFields("message", func(e Entry) { 934 | e.String("userID", "123456") 935 | e.String("action", "login") 936 | e.String("result", "success") 937 | e.Int64("int64", 120) 938 | }) 939 | }) 940 | t.Run("fields-disabled-level-info", func(t *testing.T) { 941 | w := newWriter() 942 | logger := New(w, DEBUG|WARN|ERROR|FATAL) 943 | logger.InfoWithFields("message", func(e Entry) { 944 | e.String("userID", "123456") 945 | e.String("action", "login") 946 | e.String("result", "success") 947 | }) 948 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 949 | assert.False(t, w.called, "writer should not be called") 950 | }) 951 | t.Run("basic-message-disabled-level-debug", func(t *testing.T) { 952 | w := newWriter() 953 | logger := New(w, INFO|WARN|ERROR|FATAL) 954 | logger.DebugWithFields("message", func(e Entry) { 955 | e.String("userID", "123456") 956 | e.String("action", "login") 957 | e.String("result", "success") 958 | }) 959 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 960 | assert.False(t, w.called, "writer should not be called") 961 | }) 962 | t.Run("basic-message-disabled-level-warn", func(t *testing.T) { 963 | w := newWriter() 964 | logger := New(w, INFO|DEBUG|ERROR|FATAL) 965 | logger.WarnWithFields("message", func(e Entry) { 966 | e.String("userID", "123456") 967 | e.String("action", "login") 968 | e.String("result", "success") 969 | }) 970 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 971 | assert.False(t, w.called, "writer should not be called") 972 | }) 973 | t.Run("basic-message-disabled-level-error", func(t *testing.T) { 974 | w := newWriter() 975 | logger := New(w, INFO|WARN|DEBUG|FATAL) 976 | logger.ErrorWithFields("message", func(e Entry) { 977 | e.String("userID", "123456") 978 | e.String("action", "login") 979 | e.String("result", "success") 980 | }) 981 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 982 | assert.False(t, w.called, "writer should not be called") 983 | }) 984 | t.Run("basic-message-disabled-level-fatal", func(t *testing.T) { 985 | w := newWriter() 986 | logger := New(w, INFO|WARN|ERROR|DEBUG) 987 | logger.ExitFn = func(c int) { 988 | panic("os.Exit called") 989 | } 990 | defer func() { 991 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 992 | assert.False(t, w.called, "writer should not be called") 993 | r := recover() 994 | if r != nil { 995 | t.Errorf("logger.Fatal() recover = %v", r) 996 | } 997 | }() 998 | logger.FatalWithFields("message", func(e Entry) { 999 | e.String("userID", "123456") 1000 | e.String("action", "login") 1001 | e.String("result", "success") 1002 | }) 1003 | }) 1004 | } 1005 | 1006 | func TestOnelogNoContextFromContextLogger(t *testing.T) { 1007 | t.Run("fields-info", func(t *testing.T) { 1008 | testObj := &TestObj{foo: "bar"} 1009 | testArr := TestObjArr{testObj} 1010 | w := newWriter() 1011 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1012 | logger := parent.WithContext("") 1013 | logger.InfoWithFields("message", func(e Entry) { 1014 | e.String("userID", "123456") 1015 | e.String("action", "login") 1016 | e.String("result", "success") 1017 | e.Int("count", 100) 1018 | e.Int64("int64", 100) 1019 | e.Float("float64", 0.15) 1020 | e.Bool("done", true) 1021 | e.Err("error", errors.New("some error")) 1022 | e.ObjectFunc("user", func(e Entry) { 1023 | e.String("name", "somename") 1024 | }) 1025 | e.Object("testObj", testObj) 1026 | e.Array("testArr", testArr) 1027 | }) 1028 | json := `{"level":"info","message":"message","userID":"123456",` + 1029 | `"action":"login","result":"success","count":100,"int64":100,"float64":0.15,"done":true,` + 1030 | `"error":"some error","user":{"name":"somename"},"testObj":{"foo":"bar"},` + 1031 | `"testArr":[{"foo":"bar"}]}` + "\n" 1032 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1033 | }) 1034 | t.Run("fields-debug", func(t *testing.T) { 1035 | w := newWriter() 1036 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1037 | logger := parent.WithContext("") 1038 | logger.DebugWithFields("message", func(e Entry) { 1039 | e.String("userID", "123456") 1040 | e.String("action", "login") 1041 | e.String("result", "success") 1042 | }) 1043 | json := `{"level":"debug","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 1044 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1045 | }) 1046 | t.Run("fields-warn", func(t *testing.T) { 1047 | w := newWriter() 1048 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1049 | logger := parent.WithContext("") 1050 | logger.WarnWithFields("message", func(e Entry) { 1051 | e.String("userID", "123456") 1052 | e.String("action", "login") 1053 | e.String("result", "success") 1054 | }) 1055 | json := `{"level":"warn","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 1056 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1057 | }) 1058 | t.Run("fields-error", func(t *testing.T) { 1059 | w := newWriter() 1060 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1061 | logger := parent.WithContext("") 1062 | logger.ErrorWithFields("message", func(e Entry) { 1063 | e.String("userID", "123456") 1064 | e.String("action", "login") 1065 | e.String("result", "success") 1066 | }) 1067 | json := `{"level":"error","message":"message","userID":"123456","action":"login","result":"success"}` + "\n" 1068 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1069 | }) 1070 | t.Run("fields-fatal", func(t *testing.T) { 1071 | w := newWriter() 1072 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1073 | logger := parent.WithContext("") 1074 | logger.ExitFn = func(c int) { 1075 | panic("os.Exit called") 1076 | } 1077 | defer func() { 1078 | json := `{"level":"fatal","message":"message","userID":"123456","action":"login","result":"success","int64":120}` + "\n" 1079 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1080 | r := recover() 1081 | if r == nil { 1082 | t.Errorf("logger.Fatal() recover = %v", r) 1083 | } 1084 | }() 1085 | logger.FatalWithFields("message", func(e Entry) { 1086 | e.String("userID", "123456") 1087 | e.String("action", "login") 1088 | e.String("result", "success") 1089 | e.Int64("int64", 120) 1090 | }) 1091 | }) 1092 | t.Run("fields-disabled-level-info", func(t *testing.T) { 1093 | w := newWriter() 1094 | logger := New(w, DEBUG|WARN|ERROR|FATAL) 1095 | logger.InfoWithFields("message", func(e Entry) { 1096 | e.String("userID", "123456") 1097 | e.String("action", "login") 1098 | e.String("result", "success") 1099 | }) 1100 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 1101 | assert.False(t, w.called, "writer should not be called") 1102 | }) 1103 | t.Run("basic-message-disabled-level-debug", func(t *testing.T) { 1104 | w := newWriter() 1105 | logger := New(w, INFO|WARN|ERROR|FATAL) 1106 | logger.DebugWithFields("message", func(e Entry) { 1107 | e.String("userID", "123456") 1108 | e.String("action", "login") 1109 | e.String("result", "success") 1110 | }) 1111 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 1112 | assert.False(t, w.called, "writer should not be called") 1113 | }) 1114 | t.Run("basic-message-disabled-level-warn", func(t *testing.T) { 1115 | w := newWriter() 1116 | logger := New(w, INFO|DEBUG|ERROR|FATAL) 1117 | logger.WarnWithFields("message", func(e Entry) { 1118 | e.String("userID", "123456") 1119 | e.String("action", "login") 1120 | e.String("result", "success") 1121 | }) 1122 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 1123 | assert.False(t, w.called, "writer should not be called") 1124 | }) 1125 | t.Run("basic-message-disabled-level-error", func(t *testing.T) { 1126 | w := newWriter() 1127 | logger := New(w, INFO|WARN|DEBUG|FATAL) 1128 | logger.ErrorWithFields("message", func(e Entry) { 1129 | e.String("userID", "123456") 1130 | e.String("action", "login") 1131 | e.String("result", "success") 1132 | }) 1133 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 1134 | assert.False(t, w.called, "writer should not be called") 1135 | }) 1136 | t.Run("basic-message-disabled-level-fatal", func(t *testing.T) { 1137 | w := newWriter() 1138 | logger := New(w, INFO|WARN|ERROR|DEBUG) 1139 | logger.ExitFn = func(c int) { 1140 | panic("os.Exit called") 1141 | } 1142 | defer func() { 1143 | assert.Equal(t, string(w.b), ``, "bytes written to the writer dont equal expected result") 1144 | assert.False(t, w.called, "writer should not be called") 1145 | r := recover() 1146 | if r != nil { 1147 | t.Errorf("logger.Fatal() recover = %v", r) 1148 | } 1149 | }() 1150 | logger.FatalWithFields("message", func(e Entry) { 1151 | e.String("userID", "123456") 1152 | e.String("action", "login") 1153 | e.String("result", "success") 1154 | }) 1155 | }) 1156 | } 1157 | 1158 | func TestOnelogFieldsChainAndContext(t *testing.T) { 1159 | t.Run("fields-info", func(t *testing.T) { 1160 | testObj := &TestObj{foo: "bar"} 1161 | testArr := TestObjArr{testObj} 1162 | w := newWriter() 1163 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1164 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 1165 | logger.InfoWith("message"). 1166 | String("userID", "123456"). 1167 | String("action", "login"). 1168 | String("result", "success"). 1169 | Int("count", 100). 1170 | Int64("int64", 100). 1171 | Float("float64", 0.15). 1172 | Bool("done", true). 1173 | Err("error", errors.New("some error")). 1174 | ObjectFunc("user", func(e Entry) { 1175 | e.String("name", "somename") 1176 | }). 1177 | Object("testObj", testObj). 1178 | Array("testArr", testArr).Write() 1179 | 1180 | json := `{"level":"info","message":"message","params":{"userID":"123456",` + 1181 | `"action":"login","result":"success","count":100,"int64":100,"float64":0.15,"done":true,` + 1182 | `"error":"some error","user":{"name":"somename"},"testObj":{"foo":"bar"},` + 1183 | `"testArr":[{"foo":"bar"}],"thunder_frequency":1000}}` + "\n" 1184 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1185 | }) 1186 | t.Run("fields-debug", func(t *testing.T) { 1187 | w := newWriter() 1188 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1189 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 1190 | logger.DebugWith("message"). 1191 | String("userID", "123456"). 1192 | String("action", "login"). 1193 | String("result", "success").Write() 1194 | json := `{"level":"debug","message":"message","params":{"userID":"123456","action":"login","result":"success","thunder_frequency":1000}}` + "\n" 1195 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1196 | }) 1197 | t.Run("fields-warn", func(t *testing.T) { 1198 | w := newWriter() 1199 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1200 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 1201 | logger.WarnWith("message"). 1202 | String("userID", "123456"). 1203 | String("action", "login"). 1204 | String("result", "success").Write() 1205 | json := `{"level":"warn","message":"message","params":{"userID":"123456","action":"login","result":"success","thunder_frequency":1000}}` + "\n" 1206 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1207 | }) 1208 | t.Run("fields-error", func(t *testing.T) { 1209 | w := newWriter() 1210 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1211 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 1212 | logger.ErrorWith("message"). 1213 | String("userID", "123456"). 1214 | String("action", "login"). 1215 | String("result", "success").Write() 1216 | json := `{"level":"error","message":"message","params":{"userID":"123456","action":"login","result":"success","thunder_frequency":1000}}` + "\n" 1217 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1218 | }) 1219 | t.Run("fields-fatal", func(t *testing.T) { 1220 | w := newWriter() 1221 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1222 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 1223 | logger.ExitFn = func(c int) { 1224 | panic("os.Exit called") 1225 | } 1226 | defer func() { 1227 | json := `{"level":"fatal","message":"message","params":{"userID":"123456","action":"login","result":"success","int64":120,"thunder_frequency":1000}}` + "\n" 1228 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1229 | r := recover() 1230 | if r == nil { 1231 | t.Errorf("logger.Fatal() recover = %v", r) 1232 | } 1233 | }() 1234 | logger.FatalWith("message"). 1235 | String("userID", "123456"). 1236 | String("action", "login"). 1237 | String("result", "success"). 1238 | Int64("int64", 120).Write() 1239 | 1240 | }) 1241 | 1242 | t.Run("multi-augmented-logger", func(t *testing.T) { 1243 | w := newWriter() 1244 | parent := NewContext(w, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1245 | logger := parent.With(func(e Entry) { e.Int("thunder_frequency", 1000) }) 1246 | logger = logger.With(func(e Entry) { e.String("foo", "bar") }) 1247 | logger.ExitFn = func(c int) { 1248 | panic("os.Exit called") 1249 | } 1250 | defer func() { 1251 | json := `{"level":"fatal","message":"message","params":{"thunder_frequency":1000,"foo":"bar"}}` + "\n" 1252 | assert.Equal(t, json, string(w.b), "bytes written to the writer dont equal expected result") 1253 | r := recover() 1254 | if r == nil { 1255 | t.Errorf("logger.Fatal() recover = %v", r) 1256 | } 1257 | }() 1258 | logger.Fatal("message") 1259 | }) 1260 | } 1261 | 1262 | func TestFatalActualOsExit(t *testing.T) { 1263 | if os.Getenv("FatalActualOsExit") == "1" { 1264 | parent := NewContext(os.Stdout, DEBUG|INFO|WARN|ERROR|FATAL, "params") 1265 | logger := parent.WithContext("") 1266 | logger.ExitFn = nil 1267 | logger.FatalWithFields("message", func(e Entry) { 1268 | e.String("userID", "123456") 1269 | e.String("action", "login") 1270 | e.String("result", "success") 1271 | e.Int64("int64", 120) 1272 | }) 1273 | return 1274 | } 1275 | cmd := exec.Command(os.Args[0], "-test.run=TestFatalActualOsExit") 1276 | cmd.Env = append(os.Environ(), "FatalActualOsExit=1") 1277 | err := cmd.Run() 1278 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 1279 | return 1280 | } 1281 | t.Fatalf("process ran with err %v, want exit status 1", err) 1282 | } 1283 | -------------------------------------------------------------------------------- /onelog.go: -------------------------------------------------------------------------------- 1 | // Package onelog is a fast, low allocation and modular JSON logger. 2 | // 3 | // It uses github.com/francoispqt/gojay as JSON encoder. 4 | // 5 | // Basic usage: 6 | // import "github.com/francoispqt/onelog/log" 7 | // 8 | // log.Info("hello world !") // {"level":"info","message":"hello world !", "time":1494567715} 9 | // 10 | // You can create your own logger: 11 | // import "github.com/francoispqt/onelog 12 | // 13 | // var logger = onelog.New(os.Stdout, onelog.ALL) 14 | // 15 | // func main() { 16 | // logger.Info("hello world !") // {"level":"info","message":"hello world !"} 17 | // } 18 | package onelog 19 | --------------------------------------------------------------------------------