doc.go 12 KB

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