github.com/number571/tendermint@v0.34.11-gost/docs/architecture/adr-001-logging.md (about) 1 # ADR 1: Logging 2 3 ## Context 4 5 Current logging system in Tendermint is very static and not flexible enough. 6 7 Issues: [358](https://github.com/number571/tendermint/issues/358), [375](https://github.com/number571/tendermint/issues/375). 8 9 What we want from the new system: 10 11 - per package dynamic log levels 12 - dynamic logger setting (logger tied to the processing struct) 13 - conventions 14 - be more visually appealing 15 16 "dynamic" here means the ability to set smth in runtime. 17 18 ## Decision 19 20 ### 1) An interface 21 22 First, we will need an interface for all of our libraries (`tmlibs`, Tendermint, etc.). My personal preference is go-kit `Logger` interface (see Appendix A.), but that is too much a bigger change. Plus we will still need levels. 23 24 ```go 25 # log.go 26 type Logger interface { 27 Debug(msg string, keyvals ...interface{}) error 28 Info(msg string, keyvals ...interface{}) error 29 Error(msg string, keyvals ...interface{}) error 30 31 With(keyvals ...interface{}) Logger 32 } 33 ``` 34 35 On a side note: difference between `Info` and `Notice` is subtle. We probably 36 could do without `Notice`. Don't think we need `Panic` or `Fatal` as a part of 37 the interface. These funcs could be implemented as helpers. In fact, we already 38 have some in `tmlibs/common`. 39 40 - `Debug` - extended output for devs 41 - `Info` - all that is useful for a user 42 - `Error` - errors 43 44 `Notice` should become `Info`, `Warn` either `Error` or `Debug` depending on the message, `Crit` -> `Error`. 45 46 This interface should go into `tmlibs/log`. All libraries which are part of the core (tendermint/tendermint) should obey it. 47 48 ### 2) Logger with our current formatting 49 50 On top of this interface, we will need to implement a stdout logger, which will be used when Tendermint is configured to output logs to STDOUT. 51 52 Many people say that they like the current output, so let's stick with it. 53 54 ``` 55 NOTE[2017-04-25|14:45:08] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 56 ``` 57 58 Couple of minor changes: 59 60 ``` 61 I[2017-04-25|14:45:08.322] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 62 ``` 63 64 Notice the level is encoded using only one char plus milliseconds. 65 66 Note: there are many other formats out there like [logfmt](https://brandur.org/logfmt). 67 68 This logger could be implemented using any logger - [logrus](https://github.com/sirupsen/logrus), [go-kit/log](https://github.com/go-kit/kit/tree/master/log), [zap](https://github.com/uber-go/zap), log15 so far as it 69 70 a) supports coloring output<br> 71 b) is moderately fast (buffering) <br> 72 c) conforms to the new interface or adapter could be written for it <br> 73 d) is somewhat configurable<br> 74 75 go-kit is my favorite so far. Check out how easy it is to color errors in red https://github.com/go-kit/kit/blob/master/log/term/example_test.go#L12. Although, coloring could only be applied to the whole string :( 76 77 ``` 78 go-kit +: flexible, modular 79 go-kit “-”: logfmt format https://brandur.org/logfmt 80 81 logrus +: popular, feature rich (hooks), API and output is more like what we want 82 logrus -: not so flexible 83 ``` 84 85 ```go 86 # tm_logger.go 87 // NewTmLogger returns a logger that encodes keyvals to the Writer in 88 // tm format. 89 func NewTmLogger(w io.Writer) Logger { 90 return &tmLogger{kitlog.NewLogfmtLogger(w)} 91 } 92 93 func (l tmLogger) SetLevel(level string() { 94 switch (level) { 95 case "debug": 96 l.sourceLogger = level.NewFilter(l.sourceLogger, level.AllowDebug()) 97 } 98 } 99 100 func (l tmLogger) Info(msg string, keyvals ...interface{}) error { 101 l.sourceLogger.Log("msg", msg, keyvals...) 102 } 103 104 # log.go 105 func With(logger Logger, keyvals ...interface{}) Logger { 106 kitlog.With(logger.sourceLogger, keyvals...) 107 } 108 ``` 109 110 Usage: 111 112 ```go 113 logger := log.NewTmLogger(os.Stdout) 114 logger.SetLevel(config.GetString("log_level")) 115 node.SetLogger(log.With(logger, "node", Name)) 116 ``` 117 118 **Other log formatters** 119 120 In the future, we may want other formatters like JSONFormatter. 121 122 ``` 123 { "level": "notice", "time": "2017-04-25 14:45:08.562471297 -0400 EDT", "module": "consensus", "msg": "ABCI Replay Blocks", "appHeight": 0, "storeHeight": 0, "stateHeight": 0 } 124 ``` 125 126 ### 3) Dynamic logger setting 127 128 https://dave.cheney.net/2017/01/23/the-package-level-logger-anti-pattern 129 130 This is the hardest part and where the most work will be done. logger should be tied to the processing struct, or the context if it adds some fields to the logger. 131 132 ```go 133 type BaseService struct { 134 log log15.Logger 135 name string 136 started uint32 // atomic 137 stopped uint32 // atomic 138 ... 139 } 140 ``` 141 142 BaseService already contains `log` field, so most of the structs embedding it should be fine. We should rename it to `logger`. 143 144 The only thing missing is the ability to set logger: 145 146 ``` 147 func (bs *BaseService) SetLogger(l log.Logger) { 148 bs.logger = l 149 } 150 ``` 151 152 ### 4) Conventions 153 154 Important keyvals should go first. Example: 155 156 ``` 157 correct 158 I[2017-04-25|14:45:08.322] ABCI Replay Blocks module=consensus instance=1 appHeight=0 storeHeight=0 stateHeight=0 159 ``` 160 161 not 162 163 ``` 164 wrong 165 I[2017-04-25|14:45:08.322] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 instance=1 166 ``` 167 168 for that in most cases you'll need to add `instance` field to a logger upon creating, not when u log a particular message: 169 170 ```go 171 colorFn := func(keyvals ...interface{}) term.FgBgColor { 172 for i := 1; i < len(keyvals); i += 2 { 173 if keyvals[i] == "instance" && keyvals[i+1] == "1" { 174 return term.FgBgColor{Fg: term.Blue} 175 } else if keyvals[i] == "instance" && keyvals[i+1] == "1" { 176 return term.FgBgColor{Fg: term.Red} 177 } 178 } 179 return term.FgBgColor{} 180 } 181 logger := term.NewLogger(os.Stdout, log.NewTmLogger, colorFn) 182 183 c1 := NewConsensusReactor(...) 184 c1.SetLogger(log.With(logger, "instance", 1)) 185 186 c2 := NewConsensusReactor(...) 187 c2.SetLogger(log.With(logger, "instance", 2)) 188 ``` 189 190 ## Status 191 192 Implemented 193 194 ## Consequences 195 196 ### Positive 197 198 Dynamic logger, which could be turned off for some modules at runtime. Public interface for other projects using Tendermint libraries. 199 200 ### Negative 201 202 We may loose the ability to color keys in keyvalue pairs. go-kit allow you to easily change foreground / background colors of the whole string, but not its parts. 203 204 ### Neutral 205 206 ## Appendix A. 207 208 I really like a minimalistic approach go-kit took with his logger https://github.com/go-kit/kit/tree/master/log: 209 210 ``` 211 type Logger interface { 212 Log(keyvals ...interface{}) error 213 } 214 ``` 215 216 See [The Hunt for a Logger Interface](https://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide). The advantage is greater composability (check out how go-kit defines colored logging or log-leveled logging on top of this interface https://github.com/go-kit/kit/tree/master/log).