gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/common/log/doc.go (about)

     1  // Copyright 2018 The aquachain Authors
     2  // This file is part of the aquachain library.
     3  //
     4  // The aquachain library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The aquachain library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the aquachain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  /*
    18  Package log15 provides an opinionated, simple toolkit for best-practice logging that is
    19  both human and machine readable. It is modeled after the standard library's io and net/http
    20  packages.
    21  
    22  This package enforces you to only log key/value pairs. Keys must be strings. Values may be
    23  any type that you like. The default output format is logfmt, but you may also choose to use
    24  JSON instead if that suits you. Here's how you log:
    25  
    26      log.Info("page accessed", "path", r.URL.Path, "user_id", user.id)
    27  
    28  This will output a line that looks like:
    29  
    30       lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9
    31  
    32  Getting Started
    33  
    34  To get started, you'll want to import the library:
    35  
    36      import log "github.com/inconshreveable/log15"
    37  
    38  
    39  Now you're ready to start logging:
    40  
    41      func main() {
    42          log.Info("Program starting", "args", os.Args())
    43      }
    44  
    45  
    46  Convention
    47  
    48  Because recording a human-meaningful message is common and good practice, the first argument to every
    49  logging method is the value to the *implicit* key 'msg'.
    50  
    51  Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so
    52  will the current timestamp with key 't'.
    53  
    54  You may supply any additional context as a set of key/value pairs to the logging function. log15 allows
    55  you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for
    56  logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate
    57  in the variadic argument list:
    58  
    59      log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val)
    60  
    61  If you really do favor your type-safety, you may choose to pass a log.Ctx instead:
    62  
    63      log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val})
    64  
    65  
    66  Context loggers
    67  
    68  Frequently, you want to add context to a logger so that you can track actions associated with it. An http
    69  request is a good example. You can easily create new loggers that have context that is automatically included
    70  with each log line:
    71  
    72      requestlogger := log.New("path", r.URL.Path)
    73  
    74      // later
    75      requestlogger.Debug("db txn commit", "duration", txnTimer.Finish())
    76  
    77  This will output a log line that includes the path context that is attached to the logger:
    78  
    79      lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12
    80  
    81  
    82  Handlers
    83  
    84  The Handler interface defines where log lines are printed to and how they are formated. Handler is a
    85  single interface that is inspired by net/http's handler interface:
    86  
    87      type Handler interface {
    88          Log(r *Record) error
    89      }
    90  
    91  
    92  Handlers can filter records, format them, or dispatch to multiple other Handlers.
    93  This package implements a number of Handlers for common logging patterns that are
    94  easily composed to create flexible, custom logging structures.
    95  
    96  Here's an example handler that prints logfmt output to Stdout:
    97  
    98      handler := log.StreamHandler(os.Stdout, log.LogfmtFormat())
    99  
   100  Here's an example handler that defers to two other handlers. One handler only prints records
   101  from the rpc package in logfmt to standard out. The other prints records at Error level
   102  or above in JSON formatted output to the file /var/log/service.json
   103  
   104      handler := log.MultiHandler(
   105          log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())),
   106          log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler())
   107      )
   108  
   109  Logging File Names and Line Numbers
   110  
   111  This package implements three Handlers that add debugging information to the
   112  context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's
   113  an example that adds the source file and line number of each logging call to
   114  the context.
   115  
   116      h := log.CallerFileHandler(log.StdoutHandler)
   117      log.Root().SetHandler(h)
   118      ...
   119      log.Error("open file", "err", err)
   120  
   121  This will output a line that looks like:
   122  
   123      lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42
   124  
   125  Here's an example that logs the call stack rather than just the call site.
   126  
   127      h := log.CallerStackHandler("%+v", log.StdoutHandler)
   128      log.Root().SetHandler(h)
   129      ...
   130      log.Error("open file", "err", err)
   131  
   132  This will output a line that looks like:
   133  
   134      lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]"
   135  
   136  The "%+v" format instructs the handler to include the path of the source file
   137  relative to the compile time GOPATH. The github.com/go-stack/stack package
   138  documents the full list of formatting verbs and modifiers available.
   139  
   140  Custom Handlers
   141  
   142  The Handler interface is so simple that it's also trivial to write your own. Let's create an
   143  example handler which tries to write to one handler, but if that fails it falls back to
   144  writing to another handler and includes the error that it encountered when trying to write
   145  to the primary. This might be useful when trying to log over a network socket, but if that
   146  fails you want to log those records to a file on disk.
   147  
   148      type BackupHandler struct {
   149          Primary Handler
   150          Secondary Handler
   151      }
   152  
   153      func (h *BackupHandler) Log (r *Record) error {
   154          err := h.Primary.Log(r)
   155          if err != nil {
   156              r.Ctx = append(ctx, "primary_err", err)
   157              return h.Secondary.Log(r)
   158          }
   159          return nil
   160      }
   161  
   162  This pattern is so useful that a generic version that handles an arbitrary number of Handlers
   163  is included as part of this library called FailoverHandler.
   164  
   165  Logging Expensive Operations
   166  
   167  Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay
   168  the price of computing them if you haven't turned up your logging level to a high level of detail.
   169  
   170  This package provides a simple type to annotate a logging operation that you want to be evaluated
   171  lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler
   172  filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example:
   173  
   174      func factorRSAKey() (factors []int) {
   175          // return the factors of a very large number
   176      }
   177  
   178      log.Debug("factors", log.Lazy{factorRSAKey})
   179  
   180  If this message is not logged for any reason (like logging at the Error level), then
   181  factorRSAKey is never evaluated.
   182  
   183  Dynamic context values
   184  
   185  The same log.Lazy mechanism can be used to attach context to a logger which you want to be
   186  evaluated when the message is logged, but not when the logger is created. For example, let's imagine
   187  a game where you have Player objects:
   188  
   189      type Player struct {
   190          name string
   191          alive bool
   192          log.Logger
   193      }
   194  
   195  You always want to log a player's name and whether they're alive or dead, so when you create the player
   196  object, you might do:
   197  
   198      p := &Player{name: name, alive: true}
   199      p.Logger = log.New("name", p.name, "alive", p.alive)
   200  
   201  Only now, even after a player has died, the logger will still report they are alive because the logging
   202  context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation
   203  of whether the player is alive or not to each log message, so that the log records will reflect the player's
   204  current state no matter when the log message is written:
   205  
   206      p := &Player{name: name, alive: true}
   207      isAlive := func() bool { return p.alive }
   208      player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive})
   209  
   210  Terminal Format
   211  
   212  If log15 detects that stdout is a terminal, it will configure the default
   213  handler for it (which is log.StdoutHandler) to use TerminalFormat. This format
   214  logs records nicely for your terminal, including color-coded output based
   215  on log level.
   216  
   217  Error Handling
   218  
   219  Becasuse log15 allows you to step around the type system, there are a few ways you can specify
   220  invalid arguments to the logging functions. You could, for example, wrap something that is not
   221  a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries
   222  are typically the mechanism by which errors are reported, it would be onerous for the logging functions
   223  to return errors. Instead, log15 handles errors by making these guarantees to you:
   224  
   225  - Any log record containing an error will still be printed with the error explained to you as part of the log record.
   226  
   227  - Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily
   228  (and if you like, automatically) detect if any of your logging calls are passing bad values.
   229  
   230  Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers
   231  are encouraged to return errors only if they fail to write their log records out to an external source like if the
   232  syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures
   233  like the FailoverHandler.
   234  
   235  Library Use
   236  
   237  log15 is intended to be useful for library authors as a way to provide configurable logging to
   238  users of their library. Best practice for use in a library is to always disable all output for your logger
   239  by default and to provide a public Logger instance that consumers of your library can configure. Like so:
   240  
   241      package yourlib
   242  
   243      import "github.com/inconshreveable/log15"
   244  
   245      var Log = log.New()
   246  
   247      func init() {
   248          Log.SetHandler(log.DiscardHandler())
   249      }
   250  
   251  Users of your library may then enable it if they like:
   252  
   253      import "github.com/inconshreveable/log15"
   254      import "example.com/yourlib"
   255  
   256      func main() {
   257          handler := // custom handler setup
   258          yourlib.Log.SetHandler(handler)
   259      }
   260  
   261  Best practices attaching logger context
   262  
   263  The ability to attach context to a logger is a powerful one. Where should you do it and why?
   264  I favor embedding a Logger directly into any persistent object in my application and adding
   265  unique, tracing context keys to it. For instance, imagine I am writing a web browser:
   266  
   267      type Tab struct {
   268          url string
   269          render *RenderingContext
   270          // ...
   271  
   272          Logger
   273      }
   274  
   275      func NewTab(url string) *Tab {
   276          return &Tab {
   277              // ...
   278              url: url,
   279  
   280              Logger: log.New("url", url),
   281          }
   282      }
   283  
   284  When a new tab is created, I assign a logger to it with the url of
   285  the tab as context so it can easily be traced through the logs.
   286  Now, whenever we perform any operation with the tab, we'll log with its
   287  embedded logger and it will include the tab title automatically:
   288  
   289      tab.Debug("moved position", "idx", tab.idx)
   290  
   291  There's only one problem. What if the tab url changes? We could
   292  use log.Lazy to make sure the current url is always written, but that
   293  would mean that we couldn't trace a tab's full lifetime through our
   294  logs after the user navigate to a new URL.
   295  
   296  Instead, think about what values to attach to your loggers the
   297  same way you think about what to use as a key in a SQL database schema.
   298  If it's possible to use a natural key that is unique for the lifetime of the
   299  object, do so. But otherwise, log15's ext package has a handy RandId
   300  function to let you generate what you might call "surrogate keys"
   301  They're just random hex identifiers to use for tracing. Back to our
   302  Tab example, we would prefer to set up our Logger like so:
   303  
   304          import logext "github.com/inconshreveable/log15/ext"
   305  
   306          t := &Tab {
   307              // ...
   308              url: url,
   309          }
   310  
   311          t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl})
   312          return t
   313  
   314  Now we'll have a unique traceable identifier even across loading new urls, but
   315  we'll still be able to see the tab's current url in the log messages.
   316  
   317  Must
   318  
   319  For all Handler functions which can return an error, there is a version of that
   320  function which will return no error but panics on failure. They are all available
   321  on the Must object. For example:
   322  
   323      log.Must.FileHandler("/path", log.JsonFormat)
   324      log.Must.NetHandler("tcp", ":1234", log.JsonFormat)
   325  
   326  Inspiration and Credit
   327  
   328  All of the following excellent projects inspired the design of this library:
   329  
   330  code.google.com/p/log4go
   331  
   332  github.com/op/go-logging
   333  
   334  github.com/technoweenie/grohl
   335  
   336  github.com/Sirupsen/logrus
   337  
   338  github.com/kr/logfmt
   339  
   340  github.com/spacemonkeygo/spacelog
   341  
   342  golang's stdlib, notably io and net/http
   343  
   344  The Name
   345  
   346  https://xkcd.com/927/
   347  
   348  */
   349  package log