github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/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/tendermint/tendermint/issues/358), [375](https://github.com/tendermint/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://web.archive.org/web/20210902161539/https://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1). 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).