334 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			334 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								Package log15 provides an opinionated, simple toolkit for best-practice logging that is
							 | 
						||
| 
								 | 
							
								both human and machine readable. It is modeled after the standard library's io and net/http
							 | 
						||
| 
								 | 
							
								packages.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This package enforces you to only log key/value pairs. Keys must be strings. Values may be
							 | 
						||
| 
								 | 
							
								any type that you like. The default output format is logfmt, but you may also choose to use
							 | 
						||
| 
								 | 
							
								JSON instead if that suits you. Here's how you log:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    log.Info("page accessed", "path", r.URL.Path, "user_id", user.id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This will output a line that looks like:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								     lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Getting Started
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								To get started, you'll want to import the library:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    import log "github.com/inconshreveable/log15"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Now you're ready to start logging:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    func main() {
							 | 
						||
| 
								 | 
							
								        log.Info("Program starting", "args", os.Args())
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Convention
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Because recording a human-meaningful message is common and good practice, the first argument to every
							 | 
						||
| 
								 | 
							
								logging method is the value to the *implicit* key 'msg'.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so
							 | 
						||
| 
								 | 
							
								will the current timestamp with key 't'.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								You may supply any additional context as a set of key/value pairs to the logging function. log15 allows
							 | 
						||
| 
								 | 
							
								you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for
							 | 
						||
| 
								 | 
							
								logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate
							 | 
						||
| 
								 | 
							
								in the variadic argument list:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If you really do favor your type-safety, you may choose to pass a log.Ctx instead:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Context loggers
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Frequently, you want to add context to a logger so that you can track actions associated with it. An http
							 | 
						||
| 
								 | 
							
								request is a good example. You can easily create new loggers that have context that is automatically included
							 | 
						||
| 
								 | 
							
								with each log line:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    requestlogger := log.New("path", r.URL.Path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // later
							 | 
						||
| 
								 | 
							
								    requestlogger.Debug("db txn commit", "duration", txnTimer.Finish())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This will output a log line that includes the path context that is attached to the logger:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Handlers
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The Handler interface defines where log lines are printed to and how they are formated. Handler is a
							 | 
						||
| 
								 | 
							
								single interface that is inspired by net/http's handler interface:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    type Handler interface {
							 | 
						||
| 
								 | 
							
								        Log(r *Record) error
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Handlers can filter records, format them, or dispatch to multiple other Handlers.
							 | 
						||
| 
								 | 
							
								This package implements a number of Handlers for common logging patterns that are
							 | 
						||
| 
								 | 
							
								easily composed to create flexible, custom logging structures.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Here's an example handler that prints logfmt output to Stdout:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    handler := log.StreamHandler(os.Stdout, log.LogfmtFormat())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Here's an example handler that defers to two other handlers. One handler only prints records
							 | 
						||
| 
								 | 
							
								from the rpc package in logfmt to standard out. The other prints records at Error level
							 | 
						||
| 
								 | 
							
								or above in JSON formatted output to the file /var/log/service.json
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    handler := log.MultiHandler(
							 | 
						||
| 
								 | 
							
								        log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())),
							 | 
						||
| 
								 | 
							
								        log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler())
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Logging File Names and Line Numbers
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This package implements three Handlers that add debugging information to the
							 | 
						||
| 
								 | 
							
								context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's
							 | 
						||
| 
								 | 
							
								an example that adds the source file and line number of each logging call to
							 | 
						||
| 
								 | 
							
								the context.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    h := log.CallerFileHandler(log.StdoutHandler)
							 | 
						||
| 
								 | 
							
								    log.Root().SetHandler(h)
							 | 
						||
| 
								 | 
							
								    ...
							 | 
						||
| 
								 | 
							
								    log.Error("open file", "err", err)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This will output a line that looks like:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Here's an example that logs the call stack rather than just the call site.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    h := log.CallerStackHandler("%+v", log.StdoutHandler)
							 | 
						||
| 
								 | 
							
								    log.Root().SetHandler(h)
							 | 
						||
| 
								 | 
							
								    ...
							 | 
						||
| 
								 | 
							
								    log.Error("open file", "err", err)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This will output a line that looks like:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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]"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The "%+v" format instructs the handler to include the path of the source file
							 | 
						||
| 
								 | 
							
								relative to the compile time GOPATH. The github.com/go-stack/stack package
							 | 
						||
| 
								 | 
							
								documents the full list of formatting verbs and modifiers available.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Custom Handlers
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The Handler interface is so simple that it's also trivial to write your own. Let's create an
							 | 
						||
| 
								 | 
							
								example handler which tries to write to one handler, but if that fails it falls back to
							 | 
						||
| 
								 | 
							
								writing to another handler and includes the error that it encountered when trying to write
							 | 
						||
| 
								 | 
							
								to the primary. This might be useful when trying to log over a network socket, but if that
							 | 
						||
| 
								 | 
							
								fails you want to log those records to a file on disk.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    type BackupHandler struct {
							 | 
						||
| 
								 | 
							
								        Primary Handler
							 | 
						||
| 
								 | 
							
								        Secondary Handler
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    func (h *BackupHandler) Log (r *Record) error {
							 | 
						||
| 
								 | 
							
								        err := h.Primary.Log(r)
							 | 
						||
| 
								 | 
							
								        if err != nil {
							 | 
						||
| 
								 | 
							
								            r.Ctx = append(ctx, "primary_err", err)
							 | 
						||
| 
								 | 
							
								            return h.Secondary.Log(r)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return nil
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This pattern is so useful that a generic version that handles an arbitrary number of Handlers
							 | 
						||
| 
								 | 
							
								is included as part of this library called FailoverHandler.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Logging Expensive Operations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay
							 | 
						||
| 
								 | 
							
								the price of computing them if you haven't turned up your logging level to a high level of detail.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This package provides a simple type to annotate a logging operation that you want to be evaluated
							 | 
						||
| 
								 | 
							
								lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler
							 | 
						||
| 
								 | 
							
								filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    func factorRSAKey() (factors []int) {
							 | 
						||
| 
								 | 
							
								        // return the factors of a very large number
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    log.Debug("factors", log.Lazy{factorRSAKey})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If this message is not logged for any reason (like logging at the Error level), then
							 | 
						||
| 
								 | 
							
								factorRSAKey is never evaluated.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Dynamic context values
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The same log.Lazy mechanism can be used to attach context to a logger which you want to be
							 | 
						||
| 
								 | 
							
								evaluated when the message is logged, but not when the logger is created. For example, let's imagine
							 | 
						||
| 
								 | 
							
								a game where you have Player objects:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    type Player struct {
							 | 
						||
| 
								 | 
							
								        name string
							 | 
						||
| 
								 | 
							
								        alive bool
							 | 
						||
| 
								 | 
							
								        log.Logger
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								You always want to log a player's name and whether they're alive or dead, so when you create the player
							 | 
						||
| 
								 | 
							
								object, you might do:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    p := &Player{name: name, alive: true}
							 | 
						||
| 
								 | 
							
								    p.Logger = log.New("name", p.name, "alive", p.alive)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Only now, even after a player has died, the logger will still report they are alive because the logging
							 | 
						||
| 
								 | 
							
								context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation
							 | 
						||
| 
								 | 
							
								of whether the player is alive or not to each log message, so that the log records will reflect the player's
							 | 
						||
| 
								 | 
							
								current state no matter when the log message is written:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    p := &Player{name: name, alive: true}
							 | 
						||
| 
								 | 
							
								    isAlive := func() bool { return p.alive }
							 | 
						||
| 
								 | 
							
								    player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Terminal Format
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If log15 detects that stdout is a terminal, it will configure the default
							 | 
						||
| 
								 | 
							
								handler for it (which is log.StdoutHandler) to use TerminalFormat. This format
							 | 
						||
| 
								 | 
							
								logs records nicely for your terminal, including color-coded output based
							 | 
						||
| 
								 | 
							
								on log level.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Error Handling
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Becasuse log15 allows you to step around the type system, there are a few ways you can specify
							 | 
						||
| 
								 | 
							
								invalid arguments to the logging functions. You could, for example, wrap something that is not
							 | 
						||
| 
								 | 
							
								a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries
							 | 
						||
| 
								 | 
							
								are typically the mechanism by which errors are reported, it would be onerous for the logging functions
							 | 
						||
| 
								 | 
							
								to return errors. Instead, log15 handles errors by making these guarantees to you:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- Any log record containing an error will still be printed with the error explained to you as part of the log record.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily
							 | 
						||
| 
								 | 
							
								(and if you like, automatically) detect if any of your logging calls are passing bad values.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers
							 | 
						||
| 
								 | 
							
								are encouraged to return errors only if they fail to write their log records out to an external source like if the
							 | 
						||
| 
								 | 
							
								syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures
							 | 
						||
| 
								 | 
							
								like the FailoverHandler.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Library Use
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								log15 is intended to be useful for library authors as a way to provide configurable logging to
							 | 
						||
| 
								 | 
							
								users of their library. Best practice for use in a library is to always disable all output for your logger
							 | 
						||
| 
								 | 
							
								by default and to provide a public Logger instance that consumers of your library can configure. Like so:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    package yourlib
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    import "github.com/inconshreveable/log15"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var Log = log.New()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    func init() {
							 | 
						||
| 
								 | 
							
								        Log.SetHandler(log.DiscardHandler())
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Users of your library may then enable it if they like:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    import "github.com/inconshreveable/log15"
							 | 
						||
| 
								 | 
							
								    import "example.com/yourlib"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    func main() {
							 | 
						||
| 
								 | 
							
								        handler := // custom handler setup
							 | 
						||
| 
								 | 
							
								        yourlib.Log.SetHandler(handler)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Best practices attaching logger context
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The ability to attach context to a logger is a powerful one. Where should you do it and why?
							 | 
						||
| 
								 | 
							
								I favor embedding a Logger directly into any persistent object in my application and adding
							 | 
						||
| 
								 | 
							
								unique, tracing context keys to it. For instance, imagine I am writing a web browser:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    type Tab struct {
							 | 
						||
| 
								 | 
							
								        url string
							 | 
						||
| 
								 | 
							
								        render *RenderingContext
							 | 
						||
| 
								 | 
							
								        // ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Logger
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    func NewTab(url string) *Tab {
							 | 
						||
| 
								 | 
							
								        return &Tab {
							 | 
						||
| 
								 | 
							
								            // ...
							 | 
						||
| 
								 | 
							
								            url: url,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            Logger: log.New("url", url),
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								When a new tab is created, I assign a logger to it with the url of
							 | 
						||
| 
								 | 
							
								the tab as context so it can easily be traced through the logs.
							 | 
						||
| 
								 | 
							
								Now, whenever we perform any operation with the tab, we'll log with its
							 | 
						||
| 
								 | 
							
								embedded logger and it will include the tab title automatically:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    tab.Debug("moved position", "idx", tab.idx)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								There's only one problem. What if the tab url changes? We could
							 | 
						||
| 
								 | 
							
								use log.Lazy to make sure the current url is always written, but that
							 | 
						||
| 
								 | 
							
								would mean that we couldn't trace a tab's full lifetime through our
							 | 
						||
| 
								 | 
							
								logs after the user navigate to a new URL.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Instead, think about what values to attach to your loggers the
							 | 
						||
| 
								 | 
							
								same way you think about what to use as a key in a SQL database schema.
							 | 
						||
| 
								 | 
							
								If it's possible to use a natural key that is unique for the lifetime of the
							 | 
						||
| 
								 | 
							
								object, do so. But otherwise, log15's ext package has a handy RandId
							 | 
						||
| 
								 | 
							
								function to let you generate what you might call "surrogate keys"
							 | 
						||
| 
								 | 
							
								They're just random hex identifiers to use for tracing. Back to our
							 | 
						||
| 
								 | 
							
								Tab example, we would prefer to set up our Logger like so:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        import logext "github.com/inconshreveable/log15/ext"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        t := &Tab {
							 | 
						||
| 
								 | 
							
								            // ...
							 | 
						||
| 
								 | 
							
								            url: url,
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl})
							 | 
						||
| 
								 | 
							
								        return t
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Now we'll have a unique traceable identifier even across loading new urls, but
							 | 
						||
| 
								 | 
							
								we'll still be able to see the tab's current url in the log messages.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Must
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For all Handler functions which can return an error, there is a version of that
							 | 
						||
| 
								 | 
							
								function which will return no error but panics on failure. They are all available
							 | 
						||
| 
								 | 
							
								on the Must object. For example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    log.Must.FileHandler("/path", log.JsonFormat)
							 | 
						||
| 
								 | 
							
								    log.Must.NetHandler("tcp", ":1234", log.JsonFormat)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Inspiration and Credit
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								All of the following excellent projects inspired the design of this library:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								code.google.com/p/log4go
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								github.com/op/go-logging
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								github.com/technoweenie/grohl
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								github.com/Sirupsen/logrus
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								github.com/kr/logfmt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								github.com/spacemonkeygo/spacelog
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								golang's stdlib, notably io and net/http
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The Name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								https://xkcd.com/927/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								package log
							 |