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