├── License ├── Readme.md ├── example └── main.go └── logrus-stack-hook.go /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Gurpartap Singh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # logrus-stack 🎯 2 | [![GoDoc](https://godoc.org/github.com/Gurpartap/logrus-stack?status.svg)](https://godoc.org/github.com/Gurpartap/logrus-stack) 3 | 4 | logrus-stack provides [facebookgo/stack](https://github.com/facebookgo/stack) integration hook for [sirupsen/logrus](https://github.com/sirupsen/logrus). 5 | 6 | Instead of setting file, line, and func name values individually, this hook sets "caller" and/or "stack" objects containing file, line and func name. 7 | 8 | The values play well with `logrus.TextFormatter{}` as well as `logrus.JSONFormatter{}`. See example outputs below. 9 | 10 | ## Doc 11 | 12 | There's not much to it. See usage and [GoDoc](https://godoc.org/github.com/Gurpartap/logrus-stack). 13 | 14 | ## Usage 15 | 16 | ```bash 17 | $ go get github.com/Gurpartap/logrus-stack 18 | ``` 19 | 20 | ```go 21 | logrus.AddHook(logrus_stack.StandardHook()) 22 | ``` 23 | 24 | Same as: 25 | ```go 26 | callerLevels := logrus.AllLevels 27 | stackLevels := []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel} 28 | 29 | logrus.AddHook(logrus_stack.NewHook(callerLevels, stackLevels)) 30 | ``` 31 | 32 | ## Example 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "errors" 39 | "os" 40 | 41 | "github.com/Gurpartap/logrus-stack" 42 | "github.com/sirupsen/logrus" 43 | ) 44 | 45 | type Worker struct { 46 | JobID string 47 | } 48 | 49 | func (w Worker) Perform() { 50 | logrus.WithField("jod_id", w.JobID).Infoln("Now working") 51 | 52 | err := errors.New("I don't know what to do yet") 53 | if err != nil { 54 | logrus.Errorln(err) 55 | return 56 | } 57 | 58 | // ... 59 | } 60 | 61 | func main() { 62 | // Setup logrus. 63 | logrus.SetFormatter(&logrus.JSONFormatter{}) 64 | logrus.SetOutput(os.Stderr) 65 | 66 | // Add the stack hook. 67 | logrus.AddHook(logrus_stack.StandardHook()) 68 | 69 | // Let's try it. 70 | Worker{"123"}.Perform() 71 | } 72 | ``` 73 | 74 | ```bash 75 | $ go run example/main.go 76 | ``` 77 | 78 | ```go 79 | {"caller":{"File":"github.com/Gurpartap/logrus-stack/example/main.go","Line":16,"Name":"Worker.Perform"},"jod_id":"123","level":"info","msg":"Now working","time":"2016-10-10T01:17:40+05:30"} 80 | {"caller":{"File":"github.com/Gurpartap/logrus-stack/example/main.go","Line":20,"Name":"Worker.Perform"},"level":"error","msg":"I don't know what to do yet","stack":[{"File":"github.com/Gurpartap/logrus-stack/example/main.go","Line":20,"Name":"Worker.Perform"},{"File":"github.com/Gurpartap/logrus-stack/example/main.go","Line":36,"Name":"main"},{"File":"/usr/local/Cellar/go/1.7.1/libexec/src/runtime/proc.go","Line":183,"Name":"main"},{"File":"/usr/local/Cellar/go/1.7.1/libexec/src/runtime/asm_amd64.s","Line":2086,"Name":"goexit"}],"time":"2016-10-10T01:17:40+05:30"} 81 | ``` 82 | 83 | ###### Same as above but indented: 84 | 85 | ```json 86 | { 87 | "caller": { 88 | "File": "github.com/Gurpartap/logrus-stack/example/main.go", 89 | "Line": 16, 90 | "Name": "Worker.Perform" 91 | }, 92 | "jod_id": "123", 93 | "level": "info", 94 | "msg": "Now working", 95 | "time": "2016-10-10T09:41:00+05:30" 96 | } 97 | { 98 | "caller": { 99 | "File": "github.com/Gurpartap/logrus-stack/example/main.go", 100 | "Line": 20, 101 | "Name": "Worker.Perform" 102 | }, 103 | "level": "error", 104 | "msg": "I don't know what to do yet", 105 | "stack": [ 106 | { 107 | "File": "github.com/Gurpartap/logrus-stack/example/main.go", 108 | "Line": 20, 109 | "Name": "Worker.Perform" 110 | }, 111 | { 112 | "File": "github.com/Gurpartap/logrus-stack/example/main.go", 113 | "Line": 36, 114 | "Name": "main" 115 | }, 116 | { 117 | "File": "/usr/local/Cellar/go/1.7.1/libexec/src/runtime/proc.go", 118 | "Line": 183, 119 | "Name": "main" 120 | }, 121 | { 122 | "File": "/usr/local/Cellar/go/1.7.1/libexec/src/runtime/asm_amd64.s", 123 | "Line": 2086, 124 | "Name": "goexit" 125 | } 126 | ], 127 | "time": "2016-10-10T09:41:00+05:30" 128 | } 129 | 130 | ``` 131 | 132 | If the same example was used with `logrus.SetFormatter(&logrus.TextFormatter{})` instead, the output would be: 133 | 134 | ```bash 135 | INFO[0000] Now working caller=github.com/Gurpartap/logrus-stack/example/main.go:16 Worker.Perform jod_id=123 136 | ERRO[0000] I don't know what to do yet caller=github.com/Gurpartap/logrus-stack/example/main.go:20 Worker.Perform stack=github.com/Gurpartap/logrus-stack/example/main.go:20 Worker.Perform 137 | github.com/Gurpartap/logrus-stack/example/main.go:36 main 138 | /usr/local/Cellar/go/1.7.1/libexec/src/runtime/proc.go:183 main 139 | /usr/local/Cellar/go/1.7.1/libexec/src/runtime/asm_amd64.s:2086 goexit 140 | ``` 141 | 142 | Hello 👋 143 | 144 | Follow me on https://twitter.com/Gurpartap 145 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/Gurpartap/logrus-stack" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type Worker struct { 12 | JobID string 13 | } 14 | 15 | func (w Worker) Perform() { 16 | logrus.WithField("jod_id", w.JobID).Infoln("Now working") 17 | 18 | err := errors.New("I don't know what to do yet") 19 | if err != nil { 20 | logrus.Errorln(err) 21 | return 22 | } 23 | 24 | // ... 25 | } 26 | 27 | func main() { 28 | // Setup logrus. 29 | logrus.SetFormatter(&logrus.JSONFormatter{}) 30 | logrus.SetOutput(os.Stderr) 31 | 32 | // Add the stack hook. 33 | logrus.AddHook(logrus_stack.StandardHook()) 34 | 35 | // Let's try it. 36 | Worker{"123"}.Perform() 37 | } 38 | -------------------------------------------------------------------------------- /logrus-stack-hook.go: -------------------------------------------------------------------------------- 1 | package logrus_stack 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/facebookgo/stack" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | // NewHook is the initializer for LogrusStackHook{} (implementing logrus.Hook). 11 | // Set levels to callerLevels for which "caller" value may be set, providing a 12 | // single frame of stack. Set levels to stackLevels for which "stack" value may 13 | // be set, providing the full stack (minus logrus). 14 | func NewHook(callerLevels []logrus.Level, stackLevels []logrus.Level) LogrusStackHook { 15 | return LogrusStackHook{ 16 | CallerLevels: callerLevels, 17 | StackLevels: stackLevels, 18 | } 19 | } 20 | 21 | // StandardHook is a convenience initializer for LogrusStackHook{} with 22 | // default args. 23 | func StandardHook() LogrusStackHook { 24 | return LogrusStackHook{ 25 | CallerLevels: logrus.AllLevels, 26 | StackLevels: []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}, 27 | } 28 | } 29 | 30 | // LogrusStackHook is an implementation of logrus.Hook interface. 31 | type LogrusStackHook struct { 32 | // Set levels to CallerLevels for which "caller" value may be set, 33 | // providing a single frame of stack. 34 | CallerLevels []logrus.Level 35 | 36 | // Set levels to StackLevels for which "stack" value may be set, 37 | // providing the full stack (minus logrus). 38 | StackLevels []logrus.Level 39 | } 40 | 41 | // Levels provides the levels to filter. 42 | func (hook LogrusStackHook) Levels() []logrus.Level { 43 | return logrus.AllLevels 44 | } 45 | 46 | // Fire is called by logrus when something is logged. 47 | func (hook LogrusStackHook) Fire(entry *logrus.Entry) error { 48 | var skipFrames int 49 | if len(entry.Data) == 0 { 50 | // When WithField(s) is not used, we have 8 logrus frames to skip. 51 | skipFrames = 8 52 | } else { 53 | // When WithField(s) is used, we have 6 logrus frames to skip. 54 | skipFrames = 6 55 | } 56 | 57 | var frames stack.Stack 58 | 59 | // Get the complete stack track past skipFrames count. 60 | _frames := stack.Callers(skipFrames) 61 | 62 | // Remove logrus's own frames that seem to appear after the code is through 63 | // certain hoops. e.g. http handler in a separate package. 64 | // This is a workaround. 65 | for _, frame := range _frames { 66 | if !strings.Contains(frame.File, "github.com/sirupsen/logrus") { 67 | frames = append(frames, frame) 68 | } 69 | } 70 | 71 | if len(frames) > 0 { 72 | // If we have a frame, we set it to "caller" field for assigned levels. 73 | for _, level := range hook.CallerLevels { 74 | if entry.Level == level { 75 | entry.Data["caller"] = frames[0] 76 | break 77 | } 78 | } 79 | 80 | // Set the available frames to "stack" field. 81 | for _, level := range hook.StackLevels { 82 | if entry.Level == level { 83 | entry.Data["stack"] = frames 84 | break 85 | } 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | --------------------------------------------------------------------------------