├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc └── structure.jpg ├── example ├── custom_example.go └── default_example.go ├── go.mod ├── slf_api.go ├── slf_core.go ├── slf_core_test.go ├── slf_driver_async.go ├── slf_driver_sync.go ├── slf_driver_test.go ├── slf_hook.go ├── slf_hook_test.go ├── slf_level.go ├── slf_level_test.go ├── slf_logger.go ├── slf_logger_test.go ├── slf_model.go ├── slf_model_test.go ├── slf_stack.go └── slf_stack_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | go.sum 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.13.x" 4 | - "1.14.x" 5 | - "1.15.x" 6 | - "1.16.x" 7 | 8 | script: 9 | - export GOMAXPROCS=4 10 | - export GORACE=halt_on_error=1 11 | - go test -race -v ./... 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slf4go [![Build Status](https://travis-ci.org/go-eden/slf4go.svg?branch=master)](https://travis-ci.org/go-eden/slf4go) 2 | 3 | Simple logger facade for Golang, inspired by `slf4j`, which focused on performance and scalability. 4 | 5 | # Introduction 6 | 7 | Before introducing this library, let's walk through the composition of logging library. 8 | 9 | 1. **Provide API**: like `Trace` `Debug` `Info` `Warn` `Error` etc. 10 | 2. **Collect Info**: like timestamp, stacktrace, and other context fields etc. 11 | 3. **Format & Store**: print log into `stdout` or store it directly etc. 12 | 13 | For most logging library, `1` and `2` is quite similar, 14 | but different libraries may use different logging libraries, 15 | if your project dependents multi libraries, the final log could be very messy. 16 | 17 | In the `java` language, most libraries use `slf4j` as its logging facade, 18 | you can decide to use `logback` or `log4j` etc as real logging implementation, and switch it easily. 19 | 20 | I believe there should have similar "facade" in golang, and i hope this library could be golang's `slf4j`. 21 | 22 | `slf4go` focus on `1` and `2`, and will collect all information to build an integrated `Log` instance, 23 | it expect other library provide `3` implementation, for more details, check `Driver` section. 24 | 25 | The structure of standard `Log` is: 26 | 27 | ```go 28 | type Log struct { 29 | Time int64 `json:"date"` // log's time(us) 30 | Logger string `json:"logger"` // log's name, default is package 31 | 32 | Pid int `json:"pid"` // the process id which generated this log 33 | Gid int `json:"gid"` // the goroutine id which generated this log 34 | DebugStack *string `json:"debug_stack"` // the debug stack of this log. Only for Panic and Fatal 35 | Stack *Stack `json:"stack"` // the stack info of this log. Contains {Package, Filename, Function, Line} 36 | 37 | Level Level `json:"level"` // log's level 38 | Format *string `json:"format"` // log's format 39 | Args []interface{} `json:"args"` // log's format args 40 | Fields Fields `json:"fields"` // additional custom fields 41 | } 42 | ``` 43 | 44 | What need special explanation is, `slf4go` has very high performance, for more details, check `Performance` section. 45 | 46 | # Components 47 | 48 | `slf4go` have several components: 49 | 50 | + `Log`: Log record's structure, contains `Time`, `Logger`, `Pid`, `Gid`, `Stack`, `Fields`, etc. 51 | + `Logger`: Provide api for `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Panic`, `Fatal` etc. 52 | + `Driver`: It's an interface, used for decoupling `Api` and `Implementation`. 53 | + `Hook`: Provide a hook feature, can be used for log's synchronous callback. 54 | 55 | For better understanding, check this chart: 56 | 57 | 58 | 59 | # Usage 60 | 61 | This section provides complete instructions on how to install and use `slf4go`. 62 | 63 | ## Install 64 | 65 | Could use this command to install `slf4go`: 66 | 67 | ```bash 68 | go get github.com/go-eden/slf4go 69 | ``` 70 | 71 | Could import `slf4go` like this: 72 | 73 | ```go 74 | import ( 75 | slog "github.com/go-eden/slf4go" 76 | ) 77 | ``` 78 | 79 | ## Use Global Logger 80 | 81 | By default, `Slf4go` provided a global `Logger`, in most case, you can use it directly by static function, don't need any other operation. 82 | 83 | ```go 84 | slog.Debugf("debug time: %v", time.Now()) 85 | slog.Warn("warn log") 86 | slog.Error("error log") 87 | slog.Panicf("panic time: %v", time.Now()) 88 | ``` 89 | 90 | The final log is like this: 91 | 92 | ``` 93 | 2019-06-16 19:35:05.167 [0] [TRACE] [main] default_example.go:12 debug time: 2019-06-16 19:35:05.167783 +0800 CST m=+0.000355435 94 | 2019-06-16 19:35:05.168 [0] [ WARN] [main] default_example.go:15 warn log 95 | 2019-06-16 19:35:05.168 [0] [ERROR] [main] default_example.go:17 error log 96 | 2019-06-16 19:35:05.168 [0] [PANIC] [main] default_example.go:20 panic time: 2019-06-16 19:35:05.168064 +0800 CST m=+0.000636402 97 | goroutine 1 [running]: 98 | runtime/debug.Stack(0x10aab40, 0xc0000b4100, 0x1) 99 | /usr/local/Cellar/go/1.12.6/libexec/src/runtime/debug/stack.go:24 +0x9d 100 | github.com/go-eden/slf4go.Panicf(0x10cfd89, 0xe, 0xc0000b40f0, 0x1, 0x1) 101 | /Users/sulin/workspace/go-eden/slf4go/slf_core.go:191 +0x80 102 | main.main() 103 | /Users/sulin/workspace/go-eden/slf4go/example/default_example.go:20 +0x213 104 | ``` 105 | 106 | What needs additional explanation is that `panic` and `fatal` will print `goroutine` stack automatically. 107 | 108 | ## Use Your Own Logger 109 | 110 | You can create your own `Logger` for other purposes: 111 | 112 | ```go 113 | log1 := slog.GetLogger() // Logger's name will be package name, like "main" or "github.com/go-eden/slf4go" etc 114 | log1.Info("hello") 115 | log2 := slog.NewLogger("test") // Logger's name will be the specified "test" 116 | log2.Info("world") 117 | ``` 118 | 119 | The `name` of `log1` will be caller's package name, like `main` or `github.com/go-eden/slf4go` etc, it depends on where you call it. 120 | The `name` of `log2` will be the specified `test`. 121 | 122 | Those `name` are important: 123 | 124 | + It would be fill into the final log directly. 125 | + It would be used to check if logging is enabled. 126 | + It would be used to decide whether and where to record the log. 127 | 128 | ## Use Fields 129 | 130 | You could use `BindFields` to add fields into the specified `Logger`, and use `WithFields` to create new `Logger` with the specified fields. 131 | 132 | ```go 133 | log1 := slog.GetLogger() 134 | log1.BindFields(slog.Fields{"age": 18}) 135 | log1.Debug("hell1") 136 | 137 | log1.WithFields(slog.Fields{"fav": "basketball"}).Warn("hello2") 138 | 139 | log2 := log1.WithFields(slog.Fields{"fav": "basketball"}) 140 | log2.Info("hello2") 141 | ``` 142 | 143 | The `fields` will be attached to `Log`, and finally passed to `Driver`, 144 | the `Driver` decided how to print or where to store them. 145 | 146 | ## Use Level 147 | 148 | You can setup global level by `SetLevel`, which means the lower level log will be ignored. 149 | 150 | ```go 151 | slog.SetLevel(slog.WarnLevel) 152 | slog.Info("no log") // will be ignored 153 | slog.Error("error") 154 | ``` 155 | 156 | Above code setup global level to be `WARN`, so `INFO` log will be ignored, 157 | there should have other way to config different loggers' level, 158 | it based on which `Driver` you use. 159 | 160 | You can check the specified level was enabled or not like this: 161 | 162 | ```go 163 | l := slog.GetLogger() 164 | if l.IsDebugEnabled() { 165 | l.Debug("debug....") 166 | } 167 | ``` 168 | 169 | In this example, `slf4go` will ask `Driver` whether accept `DEBUG` log of current logger, this process should cost few nanoseconds. 170 | 171 | In fact, `Logger` will call `IsDebugEnabled()` in the `Debug()` function to filter unnecessary log, 172 | but this can't avoid the performance loss of preparing `Debug()`'s arguments, like string's `concat`. 173 | 174 | As a comparison, preparing data and building `Log` would cost hundreds nanoseconds. 175 | 176 | ## Use Hook 177 | 178 | In `slf4go`, it's very easy to register log hook: 179 | 180 | ```go 181 | slog.RegisterHook(func(l *Log) { 182 | println(l) // you better not log again, it could be infinite loop 183 | }) 184 | slog.Debugf("are you prety? %t", true) 185 | ``` 186 | 187 | The `RegisterHook` accept `func(*Log)` argument, and `slf4go` will broadcast all `Log` to it asynchronously. 188 | 189 | # Driver 190 | 191 | `Driver` is the bridge between the upper `slf4go` and the lower logging implementation. 192 | 193 | ```go 194 | // Driver define the standard log print specification 195 | type Driver interface { 196 | // Retrieve the name of current driver, like 'default', 'logrus'... 197 | Name() string 198 | 199 | // Print responsible of printing the standard Log 200 | Print(l *Log) 201 | 202 | // Retrieve log level of the specified logger, 203 | // it should return the lowest Level that could be print, 204 | // which can help invoker to decide whether prepare print or not. 205 | GetLevel(logger string) Level 206 | } 207 | ``` 208 | 209 | You can provider your own implementation, and replace the default driver. 210 | 211 | ## Default StdDriver 212 | 213 | By default, `slf4go` provides a `StdDriver` as fallback, it will format `Log` and print it into `stdout` directly, 214 | if you don't need other features, could use it directly. 215 | 216 | This `Driver` will print log like this: 217 | 218 | ``` 219 | 2019-06-16 19:35:05.168 [0] [ERROR] [github.com/go-eden/slf4go] default_example.go:17 error log 220 | ``` 221 | 222 | It contains these information: 223 | 224 | + `2019-06-16 19:35:05.168`: Log's datetime. 225 | + `[0]`: Log's gid, the id of `goroutine`, like `thread-id`, it could be used for tracing. 226 | + `[ERROR]`: Log's level. 227 | + `[github.com/go-eden/slf4go]`: the caller's `logger`, it's package name by default. 228 | + `default_example.go:17`: the caller's `filename` and `line`. 229 | + `error log`: message, if level is `PANIC` or `FATAL`, `DebugStack` will be print too. 230 | 231 | Other information was ignored, like `fields` `pid` etc. 232 | 233 | ## Provide your own driver 234 | 235 | You could implement a `Driver` easily, this is an example: 236 | 237 | ```go 238 | type MyDriver struct {} 239 | 240 | func (*MyDriver) Name() string { 241 | return "my_driver" 242 | } 243 | 244 | func (*MyDriver) Print(l *slog.Log) { 245 | // print log, save db, send remote, etc. 246 | } 247 | 248 | func (*MyDriver) GetLevel(logger string) Level { 249 | return InfoLevel 250 | } 251 | 252 | func func init() { 253 | slog.SetDriver(new(MyDriver)) 254 | } 255 | ``` 256 | 257 | Above code provided an empty `Driver`, which means all log will be ignored totally. 258 | 259 | ## Use `slf4go-classic` 260 | 261 | TODO 262 | 263 | ## User `slf4go-zap` 264 | 265 | Thanks @phenix3443. 266 | 267 | [https://github.com/go-eden/slf4go-zap](https://github.com/go-eden/slf4go-zap) 268 | 269 | ## Use `slf4go-logrus` 270 | 271 | This is a simple `Driver` implementation for bridging `slf4go` and `logrus`. 272 | 273 | I haven't used logrus too much, so this library don't have lots of features currently, 274 | hope you can help me improve this library, any Pull Request will be very welcomed. 275 | 276 | [https://github.com/go-eden/slf4go-logrus](https://github.com/go-eden/slf4go-logrus) 277 | 278 | # Performance 279 | 280 | This section's benchmark was based on `MacBook Pro (15-inch, 2018)`. 281 | 282 | When using empty `Driver`, the performace of `slf4go` is like this: 283 | 284 | ``` 285 | BenchmarkLogger-12 3000000 420 ns/op 112 B/op 2 allocs/op 286 | ``` 287 | 288 | Which means if your `Driver` handles `Log` asychronously, log function like `Debug` `Info` `Warn` `Error` will complete in `500ns`. 289 | 290 | Here are some optimization points, hope it can help someone. 291 | 292 | ## stack 293 | 294 | Normally, you can obtain caller's trace `Frame` by `runtime.Caller` and `runtime.FuncForPC`, 295 | `slf4go` caches `Frame` for `pc`, which is a big performance boost. 296 | 297 | In my benchmark, it improved performace from `430ns` to `180ns`, 298 | For more details, you can check [source code](./slf_stack.go). 299 | 300 | ## etime 301 | 302 | `slf4go` use [`etime`](https://github.com/go-eden/etime) to get system time, 303 | compared to `time.Now()`, `etime.CurrentMicrosecond()` has better performance. 304 | 305 | In my benchmark, it improved performance from `68ns` to `40ns`. 306 | 307 | # License 308 | 309 | MIT 310 | -------------------------------------------------------------------------------- /doc/structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-eden/slf4go/ce902d06f81de6d3f4f6212c4d35291a0d9c662d/doc/structure.jpg -------------------------------------------------------------------------------- /example/custom_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | slog "github.com/go-eden/slf4go" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | testLog := slog.NewLogger("test") 10 | testLog.Info("hello world") 11 | 12 | time.Sleep(time.Millisecond) 13 | } 14 | -------------------------------------------------------------------------------- /example/default_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | slog "github.com/go-eden/slf4go" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | log := slog.NewLogger("example") 11 | log.Trace("trace log") 12 | log.Tracef("trace time: %v", time.Now()) 13 | log.Debug("debug log") 14 | 15 | slog.Debugf("debug time: %v", time.Now()) 16 | 17 | log.Info("info log") 18 | log.Infof("info log: %v", time.Now()) 19 | 20 | slog.Warn("warn log") 21 | 22 | log.Warnf("warn log: %v", time.Now()) 23 | 24 | slog.Error("error log") 25 | 26 | log.Errorf("error time: %v", time.Now()) 27 | log.Panic("panic log") 28 | 29 | slog.Panicf("panic time: %v", time.Now()) 30 | 31 | slog.SetContextField("RequestID", rand.Uint64()) 32 | slog.Info("<- RequestID is here") 33 | 34 | log.Warn("<- RequestID is here") 35 | 36 | time.Sleep(time.Millisecond) 37 | } 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-eden/slf4go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/go-eden/common v0.1.14 7 | github.com/go-eden/routine v0.0.1 // indirect 8 | github.com/stretchr/testify v1.7.0 9 | ) 10 | -------------------------------------------------------------------------------- /slf_api.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | const ( 4 | rootLoggerName = "root" 5 | stdBufSize = 1 << 10 6 | ) 7 | 8 | // Driver define the standard log print specification 9 | type Driver interface { 10 | 11 | // Name return the name of current driver, like 'default', 'logrus'... 12 | Name() string 13 | 14 | // Print responsible of printing the standard Log 15 | Print(l *Log) 16 | 17 | // GetLevel return log level of the specified logger, 18 | // it should return the lowest Level that could be print, 19 | // which can help invoker to decide whether prepare print or not. 20 | GetLevel(logger string) Level 21 | } 22 | 23 | type Logger interface { 24 | 25 | // Name obtain logger's name 26 | Name() string 27 | 28 | // Level obtain logger's level, lower will not be print 29 | Level() Level 30 | 31 | // BindFields add the specified fields into the current Logger. 32 | BindFields(fields Fields) 33 | 34 | // WithFields derive an new Logger by the specified fields from the current Logger. 35 | WithFields(fields Fields) Logger 36 | 37 | // IsTraceEnabled Whether trace of current logger enabled or not 38 | IsTraceEnabled() bool 39 | 40 | // IsDebugEnabled Whether debug of current logger enabled or not 41 | IsDebugEnabled() bool 42 | 43 | // IsInfoEnabled Whether info of current logger enabled or not 44 | IsInfoEnabled() bool 45 | 46 | // IsWarnEnabled Whether warn of current logger enabled or not 47 | IsWarnEnabled() bool 48 | 49 | // IsErrorEnabled Whether error of current logger enabled or not 50 | IsErrorEnabled() bool 51 | 52 | // IsPanicEnabled Whether panic of current logger enabled or not 53 | IsPanicEnabled() bool 54 | 55 | // IsFatalEnabled Whether fatal of current logger enabled or not 56 | IsFatalEnabled() bool 57 | 58 | // Trace record trace level's log 59 | Trace(v ...interface{}) 60 | 61 | // Tracef record trace level's log with custom format. 62 | Tracef(format string, v ...interface{}) 63 | 64 | // Debug record debug level's log 65 | Debug(v ...interface{}) 66 | 67 | // Debugf record debug level's log with custom format. 68 | Debugf(format string, v ...interface{}) 69 | 70 | // Info record info level's log 71 | Info(v ...interface{}) 72 | 73 | // Infof record info level's log with custom format. 74 | Infof(format string, v ...interface{}) 75 | 76 | // Warn record warn level's log 77 | Warn(v ...interface{}) 78 | 79 | // Warnf record warn level's log with custom format. 80 | Warnf(format string, v ...interface{}) 81 | 82 | // Error record error level's log 83 | Error(v ...interface{}) 84 | 85 | // Errorf record error level's log with custom format. 86 | Errorf(format string, v ...interface{}) 87 | 88 | // Panic record panic level's log 89 | Panic(v ...interface{}) 90 | 91 | // Panicf record panic level's log with custom format 92 | Panicf(format string, v ...interface{}) 93 | 94 | // Fatal record fatal level's log 95 | Fatal(v ...interface{}) 96 | 97 | // Fatalf record fatal level's log with custom format. 98 | Fatalf(format string, v ...interface{}) 99 | } 100 | -------------------------------------------------------------------------------- /slf_core.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "github.com/go-eden/common/etime" 5 | "github.com/go-eden/routine" 6 | "os" 7 | "runtime" 8 | "runtime/debug" 9 | "strings" 10 | "sync/atomic" 11 | ) 12 | 13 | var ( 14 | context string // the process name, useless 15 | pid = os.Getpid() // the cached id of current process 16 | startTime = etime.NowMicrosecond() - 1 // the start time of current process 17 | 18 | globalHook = newHooks() 19 | globalLogger *logger // global default logger 20 | globalDriver atomic.Value // global driver 21 | globalLevelSetting LevelSetting // global setting 22 | globalCxtFields routine.LocalStorage // Fields in goroutine's local storage. 23 | ) 24 | 25 | func init() { 26 | exec := os.Args[0] 27 | sp := uint8(os.PathSeparator) 28 | if off := strings.LastIndexByte(exec, sp); off > 0 { 29 | exec = exec[off+1:] 30 | } 31 | // setup default context 32 | SetContext(exec) 33 | // setup default driver 34 | SetDriver(new(StdDriver)) 35 | // init global localstorage 36 | globalCxtFields = routine.NewLocalStorage() 37 | // setup default logger 38 | globalLogger = newLogger(rootLoggerName).(*logger) 39 | // setup default level 40 | globalLevelSetting.setRootLevel(TraceLevel) 41 | } 42 | 43 | // SetContext update the global context name 44 | func SetContext(name string) { 45 | context = name 46 | } 47 | 48 | // GetContext obtain the global context name 49 | func GetContext() string { 50 | return context 51 | } 52 | 53 | // SetDriver update the global log driver 54 | func SetDriver(d Driver) { 55 | replacedDriver := globalDriver.Load() 56 | globalDriver.Store(&d) 57 | 58 | // close old driver 59 | if replacedDriver != nil { 60 | tmp := *(replacedDriver.(*Driver)) 61 | if tmp, ok := tmp.(*AsyncDriver); ok { 62 | tmp.close() 63 | } 64 | } 65 | } 66 | 67 | func getDriver() Driver { 68 | return *globalDriver.Load().(*Driver) 69 | } 70 | 71 | // EnableAsyncDriver enable the AsyncDriver, it has better performance 72 | func EnableAsyncDriver() { 73 | SetDriver(newAsyncDriver(stdBufSize)) 74 | } 75 | 76 | // SetLevel update the global level, all lower level will not be send to driver to print 77 | func SetLevel(lv Level) { 78 | globalLevelSetting.setRootLevel(lv) 79 | } 80 | 81 | // SetLoggerLevel setup log-level of the specified logger by name 82 | func SetLoggerLevel(loggerName string, level Level) { 83 | globalLevelSetting.setLoggerLevel(map[string]Level{loggerName: level}) 84 | } 85 | 86 | // SetLoggerLevelMap batch set logger's level 87 | func SetLoggerLevelMap(levelMap map[string]Level) { 88 | globalLevelSetting.setLoggerLevel(levelMap) 89 | } 90 | 91 | // RegisterHook register a hook, all log will inform it 92 | func RegisterHook(f func(*Log)) { 93 | globalHook.addHook(f) 94 | } 95 | 96 | // GetLogger create new Logger by caller's package name 97 | func GetLogger() (l Logger) { 98 | var pc [1]uintptr 99 | _ = runtime.Callers(2, pc[:]) 100 | s := ParseStack(pc[0]) 101 | if name := s.Package; len(name) > 0 { 102 | return newLogger(name) 103 | } 104 | Warnf("cannot parse package, use global logger") 105 | return globalLogger 106 | } 107 | func SetContextField(key string, value interface{}) { 108 | SetContextFields(Fields{key: value}) 109 | } 110 | 111 | func DelContextField(key string) { 112 | var oldCxtFields Fields 113 | if v := globalCxtFields.Get(); v != nil { 114 | oldCxtFields = v.(Fields) 115 | } 116 | if oldCxtFields == nil || oldCxtFields[key] == nil { 117 | return 118 | } 119 | cxtFields := make(Fields, len(oldCxtFields)-1) 120 | for k, v := range oldCxtFields { 121 | if k != key { 122 | cxtFields[k] = v 123 | } 124 | } 125 | globalCxtFields.Set(cxtFields) 126 | } 127 | 128 | func GetContextField(key string) (value interface{}) { 129 | var oldCxtFields Fields 130 | if v := globalCxtFields.Get(); v != nil { 131 | oldCxtFields = v.(Fields) 132 | } 133 | if oldCxtFields != nil { 134 | value = oldCxtFields[key] 135 | } 136 | return 137 | } 138 | 139 | func SetContextFields(fields Fields) { 140 | var oldCxtFields Fields 141 | if v := globalCxtFields.Get(); v != nil { 142 | oldCxtFields = v.(Fields) 143 | } 144 | globalCxtFields.Set(NewFields(oldCxtFields, fields)) 145 | } 146 | 147 | // NewLogger create new Logger by the specified name 148 | func NewLogger(name string) Logger { 149 | return newLogger(name) 150 | } 151 | 152 | // Trace reference Logger.Trace 153 | func Trace(v ...interface{}) { 154 | if !globalLogger.IsTraceEnabled() { 155 | return 156 | } 157 | var pc [1]uintptr 158 | _ = runtime.Callers(2, pc[:]) 159 | globalLogger.print(TraceLevel, pc[0], nil, v...) 160 | } 161 | 162 | // Tracef reference Logger.Tracef 163 | func Tracef(format string, v ...interface{}) { 164 | if !globalLogger.IsTraceEnabled() { 165 | return 166 | } 167 | var pc [1]uintptr 168 | _ = runtime.Callers(2, pc[:]) 169 | globalLogger.printf(TraceLevel, pc[0], nil, format, v...) 170 | } 171 | 172 | // Debug reference Logger.Debug 173 | func Debug(v ...interface{}) { 174 | if !globalLogger.IsDebugEnabled() { 175 | return 176 | } 177 | var pc [1]uintptr 178 | _ = runtime.Callers(2, pc[:]) 179 | globalLogger.print(DebugLevel, pc[0], nil, v...) 180 | } 181 | 182 | // Debugf reference Logger.Debugf 183 | func Debugf(format string, v ...interface{}) { 184 | if !globalLogger.IsDebugEnabled() { 185 | return 186 | } 187 | var pc [1]uintptr 188 | _ = runtime.Callers(2, pc[:]) 189 | globalLogger.printf(DebugLevel, pc[0], nil, format, v...) 190 | } 191 | 192 | // Info reference Logger.Info 193 | func Info(v ...interface{}) { 194 | if !globalLogger.IsInfoEnabled() { 195 | return 196 | } 197 | var pc [1]uintptr 198 | _ = runtime.Callers(2, pc[:]) 199 | globalLogger.print(InfoLevel, pc[0], nil, v...) 200 | } 201 | 202 | // Infof reference Logger.Infof 203 | func Infof(format string, v ...interface{}) { 204 | if !globalLogger.IsInfoEnabled() { 205 | return 206 | } 207 | var pc [1]uintptr 208 | _ = runtime.Callers(2, pc[:]) 209 | globalLogger.printf(InfoLevel, pc[0], nil, format, v...) 210 | } 211 | 212 | // Warn reference Logger.Warn 213 | func Warn(v ...interface{}) { 214 | if !globalLogger.IsWarnEnabled() { 215 | return 216 | } 217 | var pc [1]uintptr 218 | _ = runtime.Callers(2, pc[:]) 219 | globalLogger.print(WarnLevel, pc[0], nil, v...) 220 | } 221 | 222 | // Warnf reference Logger.Warnf 223 | func Warnf(format string, v ...interface{}) { 224 | if !globalLogger.IsWarnEnabled() { 225 | return 226 | } 227 | var pc [1]uintptr 228 | _ = runtime.Callers(2, pc[:]) 229 | globalLogger.printf(WarnLevel, pc[0], nil, format, v...) 230 | } 231 | 232 | // Error reference Logger.Error 233 | func Error(v ...interface{}) { 234 | if !globalLogger.IsErrorEnabled() { 235 | return 236 | } 237 | var pc [1]uintptr 238 | _ = runtime.Callers(2, pc[:]) 239 | globalLogger.print(ErrorLevel, pc[0], nil, v...) 240 | } 241 | 242 | // Errorf reference Logger.Errorf 243 | func Errorf(format string, v ...interface{}) { 244 | if !globalLogger.IsErrorEnabled() { 245 | return 246 | } 247 | var pc [1]uintptr 248 | _ = runtime.Callers(2, pc[:]) 249 | globalLogger.printf(ErrorLevel, pc[0], nil, format, v...) 250 | } 251 | 252 | // Panic reference Logger.Panic 253 | func Panic(v ...interface{}) { 254 | if !globalLogger.IsPanicEnabled() { 255 | return 256 | } 257 | var pc [1]uintptr 258 | _ = runtime.Callers(2, pc[:]) 259 | stack := string(debug.Stack()) 260 | globalLogger.print(PanicLevel, pc[0], &stack, v...) 261 | } 262 | 263 | // Panicf reference Logger.Panicf 264 | func Panicf(format string, v ...interface{}) { 265 | if !globalLogger.IsPanicEnabled() { 266 | return 267 | } 268 | var pc [1]uintptr 269 | _ = runtime.Callers(2, pc[:]) 270 | stack := string(debug.Stack()) 271 | globalLogger.printf(PanicLevel, pc[0], &stack, format, v...) 272 | } 273 | 274 | // Fatal reference Logger.Fatal 275 | func Fatal(v ...interface{}) { 276 | if !globalLogger.IsFatalEnabled() { 277 | return 278 | } 279 | var pc [1]uintptr 280 | _ = runtime.Callers(2, pc[:]) 281 | stack := string(debug.Stack()) 282 | globalLogger.print(FatalLevel, pc[0], &stack, v...) 283 | } 284 | 285 | // Fatalf reference Logger.Fatalf 286 | func Fatalf(format string, v ...interface{}) { 287 | if !globalLogger.IsFatalEnabled() { 288 | return 289 | } 290 | var pc [1]uintptr 291 | _ = runtime.Callers(2, pc[:]) 292 | stack := string(debug.Stack()) 293 | globalLogger.printf(FatalLevel, pc[0], &stack, format, v...) 294 | } 295 | -------------------------------------------------------------------------------- /slf_core_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "math/rand" 6 | "sync" 7 | "sync/atomic" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestLogger(t *testing.T) { 13 | log := GetLogger() 14 | log.Trace("are you prety?", true) 15 | log.Debugf("are you prety? %t", true) 16 | log.Info("how old are you? ", nil) 17 | log.Infof("i'm %010d", 18) 18 | log.Warn("you aren't honest! ") 19 | log.Warnf("haha%02d %v", 1000, nil) 20 | log.Trace("set level to warn!!!!!") 21 | log.Trace("what?") 22 | log.Info("what?") 23 | log.Error("what?") 24 | log.Errorf("what?..$%s$", "XD") 25 | log.Fatalf("import cycle not allowed! %s", "shit...") 26 | log.Fatal("never reach here") 27 | log.Panic("panic...") 28 | 29 | SetContextField("uid", rand.Uint64()) 30 | SetContextFields(Fields{"admin": true, "username": "jackson"}) 31 | log.Infof("with uid, admin, username context variable") 32 | 33 | log.BindFields(Fields{"tid": "bytedance"}) 34 | log.Infof("with tid logger binded fields") 35 | 36 | DelContextField("uid") 37 | log.Infof("no uid context variable") 38 | } 39 | 40 | func TestDefaultLogger(t *testing.T) { 41 | SetLevel(TraceLevel) 42 | Trace("are you prety?", true) 43 | Debugf("are you prety? %t", true) 44 | Debug("okkkkkk") 45 | Info("how old are you? ", nil) 46 | Infof("i'm %010d", 18) 47 | Warn("you aren't honest! ") 48 | Warnf("haha%02d %v", 1000, nil) 49 | Trace("set level to warn!!!!!") 50 | Tracef("what: %d", 1230) 51 | Info("what?") 52 | Error("what?") 53 | Errorf("what?..$%s$", "XD") 54 | Panic("panic") 55 | Fatalf("import cycle not allowed! %s", "shit...") 56 | Fatal("never reach here") 57 | time.Sleep(time.Millisecond) 58 | } 59 | 60 | func TestAsyncLogger(t *testing.T) { 61 | EnableAsyncDriver() 62 | 63 | log := GetLogger() 64 | log.Infof("no fields") 65 | 66 | SetContextField("uid", rand.Uint64()) 67 | SetContextFields(Fields{"admin": true, "username": "jackson"}) 68 | log.Infof("with uid, admin, username context variable") 69 | 70 | log.BindFields(Fields{"tid": "bytedance"}) 71 | log.Infof("with tid logger binded fields") 72 | 73 | DelContextField("uid") 74 | log.Infof("no uid context variable") 75 | 76 | time.Sleep(time.Millisecond) 77 | SetDriver(new(StdDriver)) 78 | } 79 | 80 | func TestLoggerLevelFilter(t *testing.T) { 81 | SetLevel(WarnLevel) 82 | SetLoggerLevel("debug", DebugLevel) 83 | SetLoggerLevelMap(map[string]Level{ 84 | "info": InfoLevel, 85 | "error": ErrorLevel, 86 | }) 87 | 88 | debugLog := NewLogger("debug") 89 | infoLog := NewLogger("info") 90 | errorLog := NewLogger("error") 91 | tmpLog := NewLogger("xxxxx") 92 | 93 | var debugCount, infoCount, errorCount int32 94 | RegisterHook(func(log *Log) { 95 | switch log.Level { 96 | case DebugLevel: 97 | atomic.AddInt32(&debugCount, 1) 98 | case InfoLevel: 99 | atomic.AddInt32(&infoCount, 1) 100 | case ErrorLevel: 101 | atomic.AddInt32(&errorCount, 1) 102 | } 103 | }) 104 | 105 | debugLog.Trace("debug.trace, invisible") 106 | debugLog.Debug("debug.debug, visible") 107 | 108 | infoLog.Debug("info.debug, invisible") 109 | infoLog.Info("info.info, visible") 110 | infoLog.Error("info.error, visible") 111 | 112 | errorLog.Info("error.info, invisible") 113 | errorLog.Warn("error.warn, invisble") 114 | errorLog.Error("error.error, visible") 115 | 116 | tmpLog.Info("tmp.info, invisible") 117 | tmpLog.Warn("tmp.warn, visible") 118 | tmpLog.Error("tmp.error, visible") 119 | 120 | time.Sleep(time.Millisecond) 121 | assert.True(t, atomic.LoadInt32(&debugCount) == 1, atomic.LoadInt32(&debugCount)) 122 | assert.True(t, atomic.LoadInt32(&infoCount) == 1) 123 | assert.True(t, atomic.LoadInt32(&errorCount) == 3) 124 | } 125 | 126 | func TestConcurrency(t *testing.T) { 127 | log := NewLogger("concurrency") 128 | d := newAsyncDriver(1 << 12) 129 | d.stdout = nil 130 | SetDriver(d) 131 | 132 | const threadNum = 64 133 | var wait sync.WaitGroup 134 | wait.Add(threadNum) 135 | for i := 0; i < threadNum; i++ { 136 | threadId := i 137 | go func() { 138 | for x := 0; x < 1000; x++ { 139 | log.Infof("threadId=%v, seq=%d", threadId, x) 140 | log.Error(threadId, x, " xxxxxxxxxxxxxxxx") 141 | time.Sleep(time.Microsecond * 100) 142 | } 143 | wait.Done() 144 | }() 145 | } 146 | wait.Wait() 147 | time.Sleep(time.Second) 148 | 149 | // use default StdDriver for avoiding break other tests 150 | SetDriver(new(StdDriver)) 151 | } 152 | -------------------------------------------------------------------------------- /slf_driver_async.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-eden/common/efmt" 6 | "io" 7 | "os" 8 | "runtime/debug" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type immutableLog struct { 14 | Time int64 // -1 means EOF 15 | Logger string 16 | Pid int 17 | Gid int 18 | Level Level 19 | Stack *Stack 20 | Fields Fields 21 | CxtFields Fields 22 | Msg string 23 | DebugStack *string 24 | } 25 | 26 | // AsyncDriver The async driver, it should have better performance 27 | type AsyncDriver struct { 28 | stdout io.Writer 29 | errout io.Writer 30 | channel chan immutableLog 31 | } 32 | 33 | func newAsyncDriver(bufsize int32) *AsyncDriver { 34 | t := &AsyncDriver{ 35 | stdout: os.Stdout, 36 | errout: os.Stderr, 37 | channel: make(chan immutableLog, bufsize), 38 | } 39 | 40 | go t.asyncPrint() 41 | 42 | return t 43 | } 44 | 45 | func (t *AsyncDriver) Name() string { 46 | return "default" 47 | } 48 | 49 | func (t *AsyncDriver) Print(l *Log) { 50 | iLog := immutableLog{ 51 | Time: l.Time, 52 | Logger: l.Logger, 53 | Pid: l.Pid, 54 | Gid: l.Gid, 55 | Stack: l.Stack, 56 | Fields: l.Fields, 57 | CxtFields: l.CxtFields, 58 | Level: l.Level, 59 | DebugStack: l.DebugStack, 60 | } 61 | 62 | // prepare message 63 | if l.Format != nil { 64 | iLog.Msg = fmt.Sprintf(*l.Format, l.Args...) 65 | } else { 66 | iLog.Msg = fmt.Sprint(l.Args...) 67 | } 68 | 69 | // send signal when buffer is empty 70 | select { 71 | case t.channel <- iLog: 72 | default: 73 | _, _ = t.errout.Write([]byte("WARNING: log lost, channel is full...\n")) 74 | } 75 | } 76 | 77 | func (t *AsyncDriver) GetLevel(_ string) Level { 78 | return TraceLevel 79 | } 80 | 81 | // print log asynchronously 82 | func (t *AsyncDriver) asyncPrint() { 83 | defer func() { 84 | if err := recover(); err != nil { 85 | _, _ = t.errout.Write([]byte(fmt.Sprintf("AsyncDriver panic: %v\n%s", err, string(debug.Stack())))) 86 | } 87 | }() 88 | var p efmt.Printer 89 | for { 90 | l, ok := <-t.channel 91 | if !ok || l.Time == -1 { 92 | return 93 | } 94 | var fields []string 95 | if len(l.CxtFields) > 0 { 96 | for k, v := range l.CxtFields { 97 | fields = append(fields, fmt.Sprintf("%s=%v", k, v)) 98 | } 99 | } 100 | if len(l.Fields) > 0 { 101 | for k, v := range l.Fields { 102 | fields = append(fields, fmt.Sprintf("%s=%v", k, v)) 103 | } 104 | } 105 | var msg = l.Msg 106 | if len(fields) > 0 { 107 | msg = "[" + strings.Join(fields, ", ") + "] " + msg 108 | } 109 | // format and print log to stdout 110 | if w := t.stdout; w != nil { 111 | var body []byte 112 | ts := time.Unix(0, l.Time*1000).Format("2006-01-02 15:04:05.999999") 113 | if l.DebugStack != nil { 114 | body = p.Sprintf("%-26s [%d] [%-5s] [%s] %s:%d %s\n%s\n", ts, l.Gid, l.Level.String(), l.Logger, l.Stack.Filename, l.Stack.Line, msg, *l.DebugStack) 115 | } else { 116 | body = p.Sprintf("%-26s [%d] [%-5s] [%s] %s:%d %s\n", ts, l.Gid, l.Level.String(), l.Logger, l.Stack.Filename, l.Stack.Line, msg) 117 | } 118 | _, _ = w.Write(body) 119 | } 120 | } 121 | } 122 | 123 | // close this driver 124 | func (t *AsyncDriver) close() { 125 | t.channel <- immutableLog{Time: -1} 126 | } 127 | -------------------------------------------------------------------------------- /slf_driver_sync.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // StdDriver The default driver, just print stdout directly 12 | type StdDriver struct { 13 | sync.Mutex 14 | } 15 | 16 | func (p *StdDriver) Name() string { 17 | return "default" 18 | } 19 | 20 | func (p *StdDriver) Print(l *Log) { 21 | p.Lock() 22 | defer p.Unlock() 23 | var ts = time.Unix(0, l.Time*1000).Format("2006-01-02 15:04:05.999999") 24 | var fields []string 25 | if len(l.CxtFields) > 0 { 26 | for k, v := range l.CxtFields { 27 | fields = append(fields, fmt.Sprintf("%s=%v", k, v)) 28 | } 29 | } 30 | if len(l.Fields) > 0 { 31 | for k, v := range l.Fields { 32 | fields = append(fields, fmt.Sprintf("%s=%v", k, v)) 33 | } 34 | } 35 | var msg string 36 | if l.Format != nil { 37 | msg = fmt.Sprintf(*l.Format, l.Args...) 38 | } else { 39 | msg = fmt.Sprint(l.Args...) 40 | } 41 | if len(fields) > 0 { 42 | msg = "[" + strings.Join(fields, ", ") + "] " + msg 43 | } 44 | var result string 45 | if l.DebugStack != nil { 46 | result = fmt.Sprintf("%-26s [%d] [%-5s] [%s] %s:%d %s\n%s\n", ts, l.Gid, l.Level.String(), l.Logger, l.Stack.Filename, l.Stack.Line, msg, *l.DebugStack) 47 | } else { 48 | result = fmt.Sprintf("%-26s [%d] [%-5s] [%s] %s:%d %s\n", ts, l.Gid, l.Level.String(), l.Logger, l.Stack.Filename, l.Stack.Line, msg) 49 | } 50 | _, _ = os.Stdout.Write([]byte(result)) 51 | } 52 | 53 | func (p *StdDriver) GetLevel(_ string) Level { 54 | return TraceLevel 55 | } 56 | -------------------------------------------------------------------------------- /slf_driver_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNilDriver(t *testing.T) { 8 | d := &NilDriver{} 9 | SetDriver(d) 10 | 11 | log := GetLogger() 12 | log.Info("what???") 13 | 14 | // use default StdDriver for avoiding break other tests 15 | SetDriver(new(StdDriver)) 16 | } 17 | 18 | // BenchmarkLogger-12 3000000 420 ns/op 112 B/op 2 allocs/op 19 | func BenchmarkLogger(b *testing.B) { 20 | SetDriver(new(NilDriver)) 21 | 22 | log := GetLogger() 23 | b.ResetTimer() 24 | b.ReportAllocs() 25 | for i := 0; i < b.N; i++ { 26 | log.Infof("hello world") 27 | } 28 | } 29 | 30 | // BenchmarkDefaultLogger-12 2475452 419.4 ns/op 128 B/op 2 allocs/op 31 | func BenchmarkDefaultLogger(b *testing.B) { 32 | SetDriver(new(NilDriver)) 33 | 34 | b.ResetTimer() 35 | b.ReportAllocs() 36 | for i := 0; i < b.N; i++ { 37 | Infof("hello world") 38 | } 39 | } 40 | 41 | // use ring slice 42 | // BenchmarkAsyncDriverChannel-12 1638012 728.1 ns/op 144 B/op 3 allocs/op 43 | // use channel again 44 | // BenchmarkAsyncDriverChannel-12 1433360 822.1 ns/op 168 B/op 4 allocs/op 45 | func BenchmarkAsyncDriverChannel(b *testing.B) { 46 | d := newAsyncDriver(1 << 12) 47 | d.stdout = nil 48 | SetDriver(d) 49 | log := GetLogger() 50 | 51 | b.ReportAllocs() 52 | b.ResetTimer() 53 | for i := 0; i < b.N; i++ { 54 | log.Infof("hello world....%v", 1234.1) 55 | } 56 | } 57 | 58 | // -------------------------------- 59 | 60 | type NilDriver struct { 61 | } 62 | 63 | func (*NilDriver) Name() string { 64 | return "" 65 | } 66 | 67 | func (*NilDriver) Print(_ *Log) { 68 | } 69 | 70 | func (*NilDriver) GetLevel(_ string) Level { 71 | return TraceLevel 72 | } 73 | -------------------------------------------------------------------------------- /slf_hook.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "runtime/debug" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | hookCacheSize = 1 << 10 11 | hookInitStatus = -1 12 | ) 13 | 14 | // hook represent a registered hook func 15 | type hook struct { 16 | hookFun func(*Log) 17 | logChan chan *Log 18 | } 19 | 20 | func newHook(f func(*Log)) *hook { 21 | h := &hook{ 22 | hookFun: f, 23 | logChan: make(chan *Log, hookCacheSize), 24 | } 25 | go func() { 26 | for l := range h.logChan { 27 | h.trigger(l) 28 | } 29 | }() 30 | return h 31 | } 32 | 33 | func (t *hook) trigger(l *Log) { 34 | defer func() { 35 | if recover() != nil { 36 | Errorf("hook trigger error:\n", string(debug.Stack())) 37 | } 38 | }() 39 | t.hookFun(l) 40 | } 41 | 42 | // hooks 43 | type hooks struct { 44 | sync.Mutex 45 | logChan chan *Log 46 | hookStatus int32 47 | hookList atomic.Value // []*hook 48 | } 49 | 50 | func newHooks() *hooks { 51 | t := &hooks{ 52 | hookStatus: hookInitStatus, 53 | logChan: make(chan *Log, hookCacheSize), 54 | } 55 | t.hookList.Store([]*hook{}) 56 | return t 57 | } 58 | 59 | func (t *hooks) addHook(f func(*Log)) { 60 | if atomic.AddInt32(&t.hookStatus, 1) == 0 { 61 | go func() { 62 | defer func() { 63 | if recover() != nil { 64 | Errorf("BUG: async-broadcast error:\n", string(debug.Stack())) 65 | } 66 | }() 67 | for logInst := range t.logChan { 68 | for _, h := range t.hookList.Load().([]*hook) { 69 | h.logChan <- logInst 70 | } 71 | } 72 | }() 73 | } 74 | t.Lock() 75 | hs := t.hookList.Load().([]*hook) 76 | hs = append(hs, newHook(f)) 77 | t.hookList.Store(hs) 78 | atomic.StoreInt32(&t.hookStatus, int32(len(hs))) 79 | t.Unlock() 80 | } 81 | 82 | func (t *hooks) broadcast(l *Log) { 83 | if atomic.LoadInt32(&t.hookStatus) <= 0 { 84 | return 85 | } 86 | select { 87 | case t.logChan <- l: 88 | default: 89 | Warnf("broadcast failed, log channel is full") 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /slf_hook_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestRegisterHook(t *testing.T) { 11 | var count uint32 12 | RegisterHook(func(log *Log) { 13 | t.Logf("hook: %v", log) 14 | atomic.AddUint32(&count, 1) 15 | }) 16 | 17 | SetLevel(TraceLevel) 18 | 19 | log := GetLogger() 20 | log.Trace("are you prety?", true) 21 | log.Debugf("are you prety? %t", true) 22 | log.Info("how old are you? ", nil) 23 | log.Infof("i'm %010d", 18) 24 | log.Warn("you aren't honest! ") 25 | log.Warnf("haha%02d %v", 1000, nil) 26 | log.Trace("set level to warn!!!!!") 27 | Trace("what?") 28 | log.Info("what?") 29 | log.Error("what?") 30 | log.Errorf("what?..$%s$", "XD") 31 | log.Fatalf("import cycle not allowed! %s", "shit...") 32 | log.Fatal("never reach here?") 33 | time.Sleep(time.Millisecond * 50) 34 | 35 | assert.True(t, atomic.LoadUint32(&count) == 13, atomic.LoadUint32(&count)) 36 | } 37 | -------------------------------------------------------------------------------- /slf_level.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "strconv" 5 | "sync/atomic" 6 | ) 7 | 8 | type Level int32 9 | 10 | const ( 11 | TraceLevel Level = iota 12 | DebugLevel 13 | InfoLevel 14 | WarnLevel 15 | ErrorLevel 16 | PanicLevel 17 | FatalLevel 18 | ) 19 | 20 | // String Retrieve Level's name 21 | func (l Level) String() string { 22 | switch l { 23 | case TraceLevel: 24 | return "TRACE" 25 | case DebugLevel: 26 | return "DEBUG" 27 | case InfoLevel: 28 | return "INFO" 29 | case WarnLevel: 30 | return "WARN" 31 | case ErrorLevel: 32 | return "ERROR" 33 | case PanicLevel: 34 | return "PANIC" 35 | case FatalLevel: 36 | return "FATAL" 37 | default: 38 | return strconv.Itoa(int(l)) 39 | } 40 | } 41 | 42 | type LevelSetting struct { 43 | rootLevel int32 44 | loggerMap atomic.Value // map[string]Level 45 | } 46 | 47 | func (t *LevelSetting) setRootLevel(l Level) { 48 | atomic.StoreInt32(&t.rootLevel, int32(l)) 49 | } 50 | 51 | func (t *LevelSetting) setLoggerLevel(levelMap map[string]Level) { 52 | if rv, ok := levelMap[rootLoggerName]; ok { 53 | t.setRootLevel(rv) 54 | } 55 | newSettings := map[string]Level{} 56 | if tmp := t.loggerMap.Load(); tmp != nil { 57 | for k, v := range tmp.(map[string]Level) { 58 | newSettings[k] = v 59 | } 60 | } 61 | for k, v := range levelMap { 62 | newSettings[k] = v 63 | } 64 | t.loggerMap.Store(newSettings) 65 | } 66 | 67 | func (t *LevelSetting) getLoggerLevel(loggerName string) Level { 68 | rl := Level(atomic.LoadInt32(&t.rootLevel)) 69 | if loggerName == rootLoggerName { 70 | return rl 71 | } 72 | if m := t.loggerMap.Load(); m != nil { 73 | if v, ok := m.(map[string]Level)[loggerName]; ok { 74 | return v 75 | } 76 | } 77 | return rl 78 | } 79 | -------------------------------------------------------------------------------- /slf_level_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestLevel(t *testing.T) { 10 | t.Log(FatalLevel.String()) 11 | t.Log(TraceLevel) 12 | } 13 | 14 | func TestLevelSetting(t *testing.T) { 15 | var setting LevelSetting 16 | 17 | setting.setRootLevel(WarnLevel) 18 | setting.setLoggerLevel(map[string]Level{ 19 | TraceLevel.String(): TraceLevel, 20 | DebugLevel.String(): DebugLevel, 21 | InfoLevel.String(): InfoLevel, 22 | }) 23 | setting.setLoggerLevel(map[string]Level{ 24 | WarnLevel.String(): WarnLevel, 25 | ErrorLevel.String(): ErrorLevel, 26 | PanicLevel.String(): PanicLevel, 27 | FatalLevel.String(): FatalLevel, 28 | }) 29 | 30 | assert.True(t, setting.getLoggerLevel(rootLoggerName) == WarnLevel) 31 | assert.True(t, setting.getLoggerLevel(WarnLevel.String()) == WarnLevel) 32 | assert.True(t, setting.getLoggerLevel(ErrorLevel.String()) == ErrorLevel) 33 | assert.True(t, setting.getLoggerLevel("xxxx") == WarnLevel) 34 | } 35 | 36 | // BenchmarkLevelSetting-12 59575768 17.50 ns/op 0 B/op 0 allocs/op 37 | func BenchmarkLevelSetting(b *testing.B) { 38 | var setting LevelSetting 39 | for i := 0; i < 100; i++ { 40 | setting.setLoggerLevel(map[string]Level{ 41 | fmt.Sprintf("logger_%v", i): WarnLevel, 42 | }) 43 | } 44 | b.ResetTimer() 45 | b.ReportAllocs() 46 | for i := 0; i < b.N; i++ { 47 | setting.getLoggerLevel("logger_0") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /slf_logger.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "runtime" 5 | "runtime/debug" 6 | "sync/atomic" 7 | ) 8 | 9 | type logger struct { 10 | name string 11 | fields atomic.Value // Fields in logger, could be shared in all goroutines. 12 | } 13 | 14 | func newLogger(s string) Logger { 15 | l := &logger{name: s} 16 | l.fields.Store(Fields{}) 17 | return l 18 | } 19 | 20 | // Name obtain logger's name 21 | func (l *logger) Name() string { 22 | return l.name 23 | } 24 | 25 | func (l *logger) Level() Level { 26 | result := getDriver().GetLevel(l.name) 27 | lv := globalLevelSetting.getLoggerLevel(l.name) 28 | if result < lv { 29 | result = lv 30 | } 31 | return result 32 | } 33 | 34 | func (l *logger) BindFields(fields Fields) { 35 | oldFields := l.fields.Load().(Fields) 36 | l.fields.Store(NewFields(oldFields, fields)) 37 | } 38 | 39 | func (l *logger) WithFields(fields Fields) Logger { 40 | oldFields := l.fields.Load().(Fields) 41 | result := newLogger(l.name) 42 | result.BindFields(NewFields(oldFields, fields)) 43 | return result 44 | } 45 | 46 | func (l *logger) IsTraceEnabled() bool { 47 | return l.Level() <= TraceLevel 48 | } 49 | 50 | func (l *logger) IsDebugEnabled() bool { 51 | return l.Level() <= DebugLevel 52 | } 53 | 54 | func (l *logger) IsInfoEnabled() bool { 55 | return l.Level() <= InfoLevel 56 | } 57 | 58 | func (l *logger) IsWarnEnabled() bool { 59 | return l.Level() <= WarnLevel 60 | } 61 | 62 | func (l *logger) IsErrorEnabled() bool { 63 | return l.Level() <= ErrorLevel 64 | } 65 | 66 | func (l *logger) IsPanicEnabled() bool { 67 | return l.Level() <= PanicLevel 68 | } 69 | 70 | func (l *logger) IsFatalEnabled() bool { 71 | return l.Level() <= FatalLevel 72 | } 73 | 74 | func (l *logger) Trace(v ...interface{}) { 75 | if !l.IsTraceEnabled() { 76 | return // don't need log 77 | } 78 | var pc [1]uintptr 79 | _ = runtime.Callers(2, pc[:]) 80 | l.print(TraceLevel, pc[0], nil, v...) 81 | } 82 | 83 | func (l *logger) Tracef(format string, v ...interface{}) { 84 | if !l.IsTraceEnabled() { 85 | return // don't need log 86 | } 87 | var pc [1]uintptr 88 | _ = runtime.Callers(2, pc[:]) 89 | l.printf(TraceLevel, pc[0], nil, format, v...) 90 | } 91 | 92 | func (l *logger) Debug(v ...interface{}) { 93 | if !l.IsDebugEnabled() { 94 | return // don't need log 95 | } 96 | var pc [1]uintptr 97 | _ = runtime.Callers(2, pc[:]) 98 | l.print(DebugLevel, pc[0], nil, v...) 99 | } 100 | 101 | func (l *logger) Debugf(format string, v ...interface{}) { 102 | if !l.IsDebugEnabled() { 103 | return // don't need log 104 | } 105 | var pc [1]uintptr 106 | _ = runtime.Callers(2, pc[:]) 107 | l.printf(DebugLevel, pc[0], nil, format, v...) 108 | } 109 | 110 | func (l *logger) Info(v ...interface{}) { 111 | if !l.IsInfoEnabled() { 112 | return // don't need log 113 | } 114 | var pc [1]uintptr 115 | _ = runtime.Callers(2, pc[:]) 116 | l.print(InfoLevel, pc[0], nil, v...) 117 | } 118 | 119 | func (l *logger) Infof(format string, v ...interface{}) { 120 | if !l.IsInfoEnabled() { 121 | return // don't need log 122 | } 123 | var pc [1]uintptr 124 | _ = runtime.Callers(2, pc[:]) 125 | l.printf(InfoLevel, pc[0], nil, format, v...) 126 | } 127 | 128 | func (l *logger) Warn(v ...interface{}) { 129 | if !l.IsWarnEnabled() { 130 | return // don't need log 131 | } 132 | var pc [1]uintptr 133 | _ = runtime.Callers(2, pc[:]) 134 | l.print(WarnLevel, pc[0], nil, v...) 135 | } 136 | 137 | func (l *logger) Warnf(format string, v ...interface{}) { 138 | if !l.IsWarnEnabled() { 139 | return // don't need log 140 | } 141 | var pc [1]uintptr 142 | _ = runtime.Callers(2, pc[:]) 143 | l.printf(WarnLevel, pc[0], nil, format, v...) 144 | } 145 | 146 | func (l *logger) Error(v ...interface{}) { 147 | if !l.IsErrorEnabled() { 148 | return // don't need log 149 | } 150 | var pc [1]uintptr 151 | _ = runtime.Callers(2, pc[:]) 152 | l.print(ErrorLevel, pc[0], nil, v...) 153 | } 154 | 155 | func (l *logger) Errorf(format string, v ...interface{}) { 156 | if !l.IsErrorEnabled() { 157 | return // don't need log 158 | } 159 | var pc [1]uintptr 160 | _ = runtime.Callers(2, pc[:]) 161 | l.printf(ErrorLevel, pc[0], nil, format, v...) 162 | } 163 | 164 | func (l *logger) Panic(v ...interface{}) { 165 | if !l.IsPanicEnabled() { 166 | return // don't need log 167 | } 168 | var pc [1]uintptr 169 | _ = runtime.Callers(2, pc[:]) 170 | stack := string(debug.Stack()) 171 | l.print(PanicLevel, pc[0], &stack, v...) 172 | } 173 | 174 | func (l *logger) Panicf(format string, v ...interface{}) { 175 | if !l.IsPanicEnabled() { 176 | return // don't need log 177 | } 178 | var pc [1]uintptr 179 | _ = runtime.Callers(2, pc[:]) 180 | stack := string(debug.Stack()) 181 | l.printf(PanicLevel, pc[0], &stack, format, v...) 182 | } 183 | 184 | func (l *logger) Fatal(v ...interface{}) { 185 | if !l.IsFatalEnabled() { 186 | return // don't need log 187 | } 188 | var pc [1]uintptr 189 | _ = runtime.Callers(2, pc[:]) 190 | stack := string(debug.Stack()) 191 | l.print(FatalLevel, pc[0], &stack, v...) 192 | } 193 | 194 | func (l *logger) Fatalf(format string, v ...interface{}) { 195 | if !l.IsFatalEnabled() { 196 | return // don't need log 197 | } 198 | var pc [1]uintptr 199 | _ = runtime.Callers(2, pc[:]) 200 | stack := string(debug.Stack()) 201 | l.printf(FatalLevel, pc[0], &stack, format, v...) 202 | } 203 | 204 | func (l *logger) print(level Level, pc uintptr, stack *string, v ...interface{}) { 205 | var cxtFields Fields 206 | if v := globalCxtFields.Get(); v != nil { 207 | cxtFields = v.(Fields) 208 | } 209 | log := NewLog(level, pc, stack, nil, v, l.fields.Load().(Fields), cxtFields) 210 | log.Logger = l.name 211 | globalHook.broadcast(log) 212 | getDriver().Print(log) 213 | } 214 | 215 | func (l *logger) printf(level Level, pc uintptr, stack *string, format string, v ...interface{}) { 216 | var cxtFields Fields 217 | if v := globalCxtFields.Get(); v != nil { 218 | cxtFields = v.(Fields) 219 | } 220 | log := NewLog(level, pc, stack, &format, v, l.fields.Load().(Fields), cxtFields) 221 | log.Logger = l.name 222 | globalHook.broadcast(log) 223 | getDriver().Print(log) 224 | } 225 | -------------------------------------------------------------------------------- /slf_logger_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // BenchmarkNoLog-12 50000000 31.8 ns/op 32 B/op 1 allocs/op 8 | func BenchmarkNoLog(b *testing.B) { 9 | SetLevel(WarnLevel) 10 | log := GetLogger() 11 | 12 | b.ResetTimer() 13 | b.ReportAllocs() 14 | for i := 0; i < b.N; i++ { 15 | log.Info("hahahhhhh %v", nil) 16 | } 17 | } 18 | 19 | // BenchmarkNoLog2-12 500000000 3.34 ns/op 0 B/op 0 allocs/op 20 | func BenchmarkNoLog2(b *testing.B) { 21 | SetLevel(WarnLevel) 22 | log := GetLogger() 23 | 24 | b.ResetTimer() 25 | b.ReportAllocs() 26 | for i := 0; i < b.N; i++ { 27 | if log.IsInfoEnabled() { 28 | log.Info("hahahhhhh %v", nil) 29 | } 30 | } 31 | } 32 | 33 | func TestLoggerFields(t *testing.T) { 34 | log1 := GetLogger() 35 | log1.BindFields(Fields{"age": 18}) 36 | log1.Debug("hell1") 37 | 38 | log2 := log1.WithFields(Fields{"score": 100.0}) 39 | log2.Info("hello2") 40 | 41 | log2.WithFields(Fields{"fav": "basketball"}).Warn("hello3") 42 | } 43 | 44 | func TestLoggerIsEnabled(t *testing.T) { 45 | SetLevel(WarnLevel) 46 | l := GetLogger() 47 | if l.IsDebugEnabled() { 48 | l.Debug("debug....") 49 | } 50 | if l.IsInfoEnabled() { 51 | l.Info("info....") 52 | } 53 | } 54 | 55 | // BenchmarkLoggerIsEnabled-12 85532004 14.14 ns/op 56 | func BenchmarkLoggerIsEnabled(b *testing.B) { 57 | SetLevel(WarnLevel) 58 | SetLoggerLevel("abc", InfoLevel) 59 | SetLoggerLevel("xyz", TraceLevel) 60 | 61 | l := NewLogger("abc") 62 | for i := 0; i < b.N; i++ { 63 | _ = l.IsInfoEnabled() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /slf_model.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "github.com/go-eden/common/etime" 5 | "github.com/go-eden/routine" 6 | ) 7 | 8 | // Fields represents attached fileds of log 9 | type Fields map[string]interface{} 10 | 11 | // NewFields merge multi fileds into new Fields instance 12 | func NewFields(fields ...Fields) Fields { 13 | var size int 14 | for _, item := range fields { 15 | size += len(item) 16 | } 17 | result := make(Fields, size) 18 | for _, item := range fields { 19 | if item != nil { 20 | for k, v := range item { 21 | result[k] = v 22 | } 23 | } 24 | } 25 | return result 26 | } 27 | 28 | // Log represent an log, contains all properties. 29 | type Log struct { 30 | Time int64 `json:"date"` // log's time(us) 31 | Logger string `json:"logger"` // log's name, default is package 32 | 33 | Pid int `json:"pid"` // the process id which generated this log 34 | Gid int `json:"gid"` // the goroutine id which generated this log 35 | Stack *Stack `json:"stack"` // the stack info of this log 36 | DebugStack *string `json:"debug_stack"` // the debug stack of this log 37 | 38 | Level Level `json:"level"` // log's level 39 | Format *string `json:"format"` // log's format 40 | Args []interface{} `json:"args"` // log's format args 41 | Fields Fields `json:"fields"` // additional custom fields 42 | CxtFields Fields `json:"cxt_fields"` // caller's goroutine context fields 43 | } 44 | 45 | // NewLog create an new Log instance 46 | // for better performance, caller should be provided by upper 47 | func NewLog(level Level, pc uintptr, debugStack *string, format *string, args []interface{}, fields, cxtFields Fields) *Log { 48 | var stack *Stack 49 | // support first args as custom stack 50 | if format == nil && len(args) > 1 { 51 | if s, ok := args[0].(*Stack); ok { 52 | stack = s 53 | args = args[1:] 54 | } 55 | } 56 | // default stack 57 | if stack == nil { 58 | stack = ParseStack(pc) 59 | } 60 | return &Log{ 61 | Time: etime.NowMicrosecond(), 62 | Logger: stack.Package, 63 | 64 | Pid: pid, 65 | Gid: int(routine.Goid()), 66 | Stack: stack, 67 | DebugStack: debugStack, 68 | 69 | Level: level, 70 | Format: format, 71 | Args: args, 72 | Fields: fields, 73 | CxtFields: cxtFields, 74 | } 75 | } 76 | 77 | // Uptime obtain log's createTime relative to application's startTime 78 | func (l *Log) Uptime() int64 { 79 | return l.Time - startTime 80 | } 81 | -------------------------------------------------------------------------------- /slf_model_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestPid(t *testing.T) { 11 | t.Log(os.Getpid()) 12 | time.Sleep(time.Second) 13 | } 14 | 15 | // BenchmarkPid-12 100000000 17.9 ns/op 0 B/op 0 allocs/op 16 | func BenchmarkPid(b *testing.B) { 17 | b.ReportAllocs() 18 | b.ResetTimer() 19 | for i := 0; i < b.N; i++ { 20 | os.Getpid() 21 | } 22 | } 23 | 24 | func TestNewLog(t *testing.T) { 25 | var pc [1]uintptr 26 | _ = runtime.Callers(2, pc[:]) 27 | l := NewLog(TraceLevel, pc[0], nil, nil, nil, nil, nil) 28 | t.Log(l) 29 | } 30 | 31 | // BenchmarkNewLog-12 10000000 169 ns/op 160 B/op 1 allocs/op 32 | // BenchmarkNewLog-12 2000000 769 ns/op 408 B/op 4 allocs/op 33 | // BenchmarkNewLog-12 2000000 720 ns/op 392 B/op 4 allocs/op 34 | // after optimization by ParseStack 35 | // BenchmarkNewLog-12 5000000 387 ns/op 176 B/op 1 allocs/op 36 | // BenchmarkNewLog-12 5000000 376 ns/op 160 B/op 1 allocs/op 37 | // BenchmarkNewLog-12 5000000 363 ns/op 96 B/op 1 allocs/op 38 | func BenchmarkNewLog(b *testing.B) { 39 | var pc [1]uintptr 40 | b.ReportAllocs() 41 | b.ResetTimer() 42 | for i := 0; i < b.N; i++ { 43 | _ = runtime.Callers(2, pc[:]) 44 | NewLog(TraceLevel, pc[0], nil, nil, nil, nil, nil) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /slf_stack.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "runtime" 5 | "strings" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | // for better preformance, use atomic-map 11 | var ( 12 | stackLock = new(sync.Mutex) 13 | stackCache = new(atomic.Value) 14 | 15 | stackMap = sync.Map{} // for test/compare 16 | ) 17 | 18 | // Stack represent pc's stack details. 19 | type Stack struct { 20 | pc uintptr 21 | Package string `json:"package"` 22 | Filename string `json:"filename"` 23 | Function string `json:"function"` 24 | Line int `json:"line"` 25 | } 26 | 27 | // cacheStack save the specified stackInfo into global atomic map. 28 | func cacheStack(s *Stack) { 29 | stackLock.Lock() 30 | defer stackLock.Unlock() 31 | var oldMap map[uintptr]*Stack 32 | if x := stackCache.Load(); x != nil { 33 | oldMap = x.(map[uintptr]*Stack) 34 | } 35 | // don't modify oldMap to avoid concurrency problem 36 | newMap := make(map[uintptr]*Stack) 37 | if oldMap != nil { 38 | for k, v := range oldMap { 39 | newMap[k] = v 40 | } 41 | } 42 | newMap[s.pc] = s 43 | stackCache.Store(newMap) 44 | } 45 | 46 | // loadStack retrieve cached Stack, could be nil 47 | func loadStack(pc uintptr) *Stack { 48 | x := stackCache.Load() 49 | if x == nil { 50 | return nil 51 | } 52 | infoMap := x.(map[uintptr]*Stack) 53 | 54 | return infoMap[pc] 55 | } 56 | 57 | // parseStack retrieve pc's stack info 58 | func parseStack(pc uintptr) (s *Stack) { 59 | var frame runtime.Frame 60 | if frames := runtime.CallersFrames([]uintptr{pc}); frames != nil { 61 | frame, _ = frames.Next() 62 | } 63 | s = &Stack{pc: pc, Line: frame.Line} 64 | // parse package and function 65 | var pkgName, funcName string 66 | if frame.Func != nil { 67 | var off int 68 | name := frame.Func.Name() 69 | for i := len(name) - 1; i >= 0; i-- { 70 | if name[i] == '/' { 71 | break 72 | } 73 | if name[i] == '.' { 74 | off = i 75 | } 76 | } 77 | if off > 0 { 78 | pkgName = name[:off] 79 | if off < len(name)-1 { 80 | funcName = name[off+1:] 81 | } 82 | } else { 83 | pkgName = name 84 | } 85 | } 86 | s.Package = pkgName 87 | s.Function = funcName 88 | // parse Filename 89 | var fileName = frame.File 90 | if off := strings.LastIndexByte(fileName, '/'); off > 0 && off < len(fileName)-1 { 91 | fileName = fileName[off+1:] 92 | } 93 | s.Filename = fileName 94 | return 95 | } 96 | 97 | // ParseStack retrieve pc's stack details, should cache result for performance optimization. 98 | func ParseStack(pc uintptr) (s *Stack) { 99 | if s = loadStack(pc); s != nil { 100 | return 101 | } 102 | s = parseStack(pc) 103 | cacheStack(s) 104 | return 105 | } 106 | 107 | // ParseStack2 this is slower, cannot use it. 108 | func ParseStack2(pc uintptr) (s *Stack) { 109 | if v, ok := stackMap.Load(pc); ok { 110 | s = v.(*Stack) 111 | } else { 112 | s = parseStack(pc) 113 | stackMap.Store(pc, s) 114 | } 115 | return 116 | } 117 | -------------------------------------------------------------------------------- /slf_stack_test.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | ) 7 | 8 | func TestPCStack(t *testing.T) { 9 | pc := make([]uintptr, 1, 1) 10 | _ = runtime.Callers(1, pc) 11 | 12 | t.Log(ParseStack(pc[0])) 13 | t.Log(ParseStack(pc[0])) 14 | t.Log(ParseStack(pc[0])) 15 | 16 | for i := 0; i < 3; i++ { 17 | t.Log(printPC()) 18 | } 19 | 20 | t.Log(printPC()) 21 | t.Log(printPC()) 22 | } 23 | 24 | func printPC() uintptr { 25 | pc := make([]uintptr, 1, 1) 26 | _ = runtime.Callers(1, pc) 27 | return pc[0] 28 | } 29 | 30 | // BenchmarkParseStack-12 300000000 5.83 ns/op 0 B/op 0 allocs/op 31 | func BenchmarkParseStack(b *testing.B) { 32 | pc := make([]uintptr, 1, 1) 33 | _ = runtime.Callers(1, pc) 34 | 35 | b.ResetTimer() 36 | b.ReportAllocs() 37 | for i := 0; i < b.N; i++ { 38 | ParseStack(pc[0]) 39 | } 40 | } 41 | 42 | // BenchmarkParseStack2-12 5000000 245 ns/op 248 B/op 3 allocs/op 43 | func BenchmarkParseStack2(b *testing.B) { 44 | pc := make([]uintptr, 1, 1) 45 | _ = runtime.Callers(1, pc) 46 | 47 | b.ResetTimer() 48 | b.ReportAllocs() 49 | for i := 0; i < b.N; i++ { 50 | ParseStack2(pc[0]) 51 | } 52 | } 53 | 54 | // skip=1 55 | // BenchmarkCaller-12 10000000 180 ns/op 0 B/op 0 allocs/op 56 | // skip=2 57 | // BenchmarkCaller-12 10000000 236 ns/op 0 B/op 0 allocs/op 58 | // skip=3 59 | // BenchmarkCaller-12 5000000 325 ns/op 0 B/op 0 allocs/op 60 | // For better performance, should invoke caller at upper stack 61 | func BenchmarkCaller(b *testing.B) { 62 | b.ResetTimer() 63 | b.ReportAllocs() 64 | for i := 0; i < b.N; i++ { 65 | var pc [1]uintptr 66 | runtime.Callers(3, pc[:]) 67 | } 68 | } 69 | 70 | func TestPackage(t *testing.T) { 71 | file, line := findCaller() 72 | t.Log(file, line) 73 | 74 | name, file, line := findCallerFunc() 75 | t.Log(name, file, line) 76 | } 77 | 78 | // BenchmarkFindCaller-12 3000000 498 ns/op 184 B/op 2 allocs/op 79 | func BenchmarkFindCaller(b *testing.B) { 80 | b.ResetTimer() 81 | b.ReportAllocs() 82 | for i := 0; i < b.N; i++ { 83 | findCaller() 84 | } 85 | } 86 | 87 | // BenchmarkFindCallerFunc-12 5000000 391 ns/op 184 B/op 2 allocs/op 88 | func BenchmarkFindCallerFunc(b *testing.B) { 89 | b.ResetTimer() 90 | b.ReportAllocs() 91 | for i := 0; i < b.N; i++ { 92 | findCallerFunc() 93 | } 94 | } 95 | 96 | func findCaller() (file string, line int) { 97 | _, file, line, _ = runtime.Caller(2) 98 | return 99 | } 100 | 101 | func findCallerFunc() (name string, file string, line int) { 102 | pc, file, line, _ := runtime.Caller(2) 103 | if f := runtime.FuncForPC(pc); f != nil { 104 | name = f.Name() 105 | } 106 | return 107 | } 108 | 109 | // BenchmarkCallers-12 10000000 177 ns/op 0 B/op 0 allocs/op 110 | // BenchmarkCallers-12 5000000 228 ns/op 0 B/op 0 allocs/op 111 | // BenchmarkCallers-12 3000000 435 ns/op 176 B/op 1 allocs/op 112 | func BenchmarkCallers(b *testing.B) { 113 | b.ResetTimer() 114 | b.ReportAllocs() 115 | rpc := make([]uintptr, 1, 1) 116 | for i := 0; i < b.N; i++ { 117 | runtime.Callers(2, rpc) 118 | _, _ = runtime.CallersFrames(rpc).Next() 119 | } 120 | } 121 | --------------------------------------------------------------------------------