gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

README.md (12280B)


      1 # A minimal logging API for Go
      2 
      3 [![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
      4 
      5 logr offers an(other) opinion on how Go programs and libraries can do logging
      6 without becoming coupled to a particular logging implementation.  This is not
      7 an implementation of logging - it is an API.  In fact it is two APIs with two
      8 different sets of users.
      9 
     10 The `Logger` type is intended for application and library authors.  It provides
     11 a relatively small API which can be used everywhere you want to emit logs.  It
     12 defers the actual act of writing logs (to files, to stdout, or whatever) to the
     13 `LogSink` interface.
     14 
     15 The `LogSink` interface is intended for logging library implementers.  It is a
     16 pure interface which can be implemented by logging frameworks to provide the actual logging
     17 functionality.
     18 
     19 This decoupling allows application and library developers to write code in
     20 terms of `logr.Logger` (which has very low dependency fan-out) while the
     21 implementation of logging is managed "up stack" (e.g. in or near `main()`.)
     22 Application developers can then switch out implementations as necessary.
     23 
     24 Many people assert that libraries should not be logging, and as such efforts
     25 like this are pointless.  Those people are welcome to convince the authors of
     26 the tens-of-thousands of libraries that *DO* write logs that they are all
     27 wrong.  In the meantime, logr takes a more practical approach.
     28 
     29 ## Typical usage
     30 
     31 Somewhere, early in an application's life, it will make a decision about which
     32 logging library (implementation) it actually wants to use.  Something like:
     33 
     34 ```
     35     func main() {
     36         // ... other setup code ...
     37 
     38         // Create the "root" logger.  We have chosen the "logimpl" implementation,
     39         // which takes some initial parameters and returns a logr.Logger.
     40         logger := logimpl.New(param1, param2)
     41 
     42         // ... other setup code ...
     43 ```
     44 
     45 Most apps will call into other libraries, create structures to govern the flow,
     46 etc.  The `logr.Logger` object can be passed to these other libraries, stored
     47 in structs, or even used as a package-global variable, if needed.  For example:
     48 
     49 ```
     50     app := createTheAppObject(logger)
     51     app.Run()
     52 ```
     53 
     54 Outside of this early setup, no other packages need to know about the choice of
     55 implementation.  They write logs in terms of the `logr.Logger` that they
     56 received:
     57 
     58 ```
     59     type appObject struct {
     60         // ... other fields ...
     61         logger logr.Logger
     62         // ... other fields ...
     63     }
     64 
     65     func (app *appObject) Run() {
     66         app.logger.Info("starting up", "timestamp", time.Now())
     67 
     68         // ... app code ...
     69 ```
     70 
     71 ## Background
     72 
     73 If the Go standard library had defined an interface for logging, this project
     74 probably would not be needed.  Alas, here we are.
     75 
     76 ### Inspiration
     77 
     78 Before you consider this package, please read [this blog post by the
     79 inimitable Dave Cheney][warning-makes-no-sense].  We really appreciate what
     80 he has to say, and it largely aligns with our own experiences.
     81 
     82 ### Differences from Dave's ideas
     83 
     84 The main differences are:
     85 
     86 1. Dave basically proposes doing away with the notion of a logging API in favor
     87 of `fmt.Printf()`.  We disagree, especially when you consider things like output
     88 locations, timestamps, file and line decorations, and structured logging.  This
     89 package restricts the logging API to just 2 types of logs: info and error.
     90 
     91 Info logs are things you want to tell the user which are not errors.  Error
     92 logs are, well, errors.  If your code receives an `error` from a subordinate
     93 function call and is logging that `error` *and not returning it*, use error
     94 logs.
     95 
     96 2. Verbosity-levels on info logs.  This gives developers a chance to indicate
     97 arbitrary grades of importance for info logs, without assigning names with
     98 semantic meaning such as "warning", "trace", and "debug."  Superficially this
     99 may feel very similar, but the primary difference is the lack of semantics.
    100 Because verbosity is a numerical value, it's safe to assume that an app running
    101 with higher verbosity means more (and less important) logs will be generated.
    102 
    103 ## Implementations (non-exhaustive)
    104 
    105 There are implementations for the following logging libraries:
    106 
    107 - **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr)
    108 - **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr)
    109 - **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
    110 - **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr)
    111 - **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting)
    112 - **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
    113 - **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr)
    114 - **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
    115 - **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend)
    116 - **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr)
    117 - **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr)
    118 - **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0)
    119 - **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing)
    120 
    121 ## FAQ
    122 
    123 ### Conceptual
    124 
    125 #### Why structured logging?
    126 
    127 - **Structured logs are more easily queryable**: Since you've got
    128   key-value pairs, it's much easier to query your structured logs for
    129   particular values by filtering on the contents of a particular key --
    130   think searching request logs for error codes, Kubernetes reconcilers for
    131   the name and namespace of the reconciled object, etc.
    132 
    133 - **Structured logging makes it easier to have cross-referenceable logs**:
    134   Similarly to searchability, if you maintain conventions around your
    135   keys, it becomes easy to gather all log lines related to a particular
    136   concept.
    137 
    138 - **Structured logs allow better dimensions of filtering**: if you have
    139   structure to your logs, you've got more precise control over how much
    140   information is logged -- you might choose in a particular configuration
    141   to log certain keys but not others, only log lines where a certain key
    142   matches a certain value, etc., instead of just having v-levels and names
    143   to key off of.
    144 
    145 - **Structured logs better represent structured data**: sometimes, the
    146   data that you want to log is inherently structured (think tuple-link
    147   objects.)  Structured logs allow you to preserve that structure when
    148   outputting.
    149 
    150 #### Why V-levels?
    151 
    152 **V-levels give operators an easy way to control the chattiness of log
    153 operations**.  V-levels provide a way for a given package to distinguish
    154 the relative importance or verbosity of a given log message.  Then, if
    155 a particular logger or package is logging too many messages, the user
    156 of the package can simply change the v-levels for that library.
    157 
    158 #### Why not named levels, like Info/Warning/Error?
    159 
    160 Read [Dave Cheney's post][warning-makes-no-sense].  Then read [Differences
    161 from Dave's ideas](#differences-from-daves-ideas).
    162 
    163 #### Why not allow format strings, too?
    164 
    165 **Format strings negate many of the benefits of structured logs**:
    166 
    167 - They're not easily searchable without resorting to fuzzy searching,
    168   regular expressions, etc.
    169 
    170 - They don't store structured data well, since contents are flattened into
    171   a string.
    172 
    173 - They're not cross-referenceable.
    174 
    175 - They don't compress easily, since the message is not constant.
    176 
    177 (Unless you turn positional parameters into key-value pairs with numerical
    178 keys, at which point you've gotten key-value logging with meaningless
    179 keys.)
    180 
    181 ### Practical
    182 
    183 #### Why key-value pairs, and not a map?
    184 
    185 Key-value pairs are *much* easier to optimize, especially around
    186 allocations.  Zap (a structured logger that inspired logr's interface) has
    187 [performance measurements](https://github.com/uber-go/zap#performance)
    188 that show this quite nicely.
    189 
    190 While the interface ends up being a little less obvious, you get
    191 potentially better performance, plus avoid making users type
    192 `map[string]string{}` every time they want to log.
    193 
    194 #### What if my V-levels differ between libraries?
    195 
    196 That's fine.  Control your V-levels on a per-logger basis, and use the
    197 `WithName` method to pass different loggers to different libraries.
    198 
    199 Generally, you should take care to ensure that you have relatively
    200 consistent V-levels within a given logger, however, as this makes deciding
    201 on what verbosity of logs to request easier.
    202 
    203 #### But I really want to use a format string!
    204 
    205 That's not actually a question.  Assuming your question is "how do
    206 I convert my mental model of logging with format strings to logging with
    207 constant messages":
    208 
    209 1. Figure out what the error actually is, as you'd write in a TL;DR style,
    210    and use that as a message.
    211 
    212 2. For every place you'd write a format specifier, look to the word before
    213    it, and add that as a key value pair.
    214 
    215 For instance, consider the following examples (all taken from spots in the
    216 Kubernetes codebase):
    217 
    218 - `klog.V(4).Infof("Client is returning errors: code %v, error %v",
    219   responseCode, err)` becomes `logger.Error(err, "client returned an
    220   error", "code", responseCode)`
    221 
    222 - `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v",
    223   seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after
    224   response when requesting url", "attempt", retries, "after
    225   seconds", seconds, "url", url)`
    226 
    227 If you *really* must use a format string, use it in a key's value, and
    228 call `fmt.Sprintf` yourself.  For instance: `log.Printf("unable to
    229 reflect over type %T")` becomes `logger.Info("unable to reflect over
    230 type", "type", fmt.Sprintf("%T"))`.  In general though, the cases where
    231 this is necessary should be few and far between.
    232 
    233 #### How do I choose my V-levels?
    234 
    235 This is basically the only hard constraint: increase V-levels to denote
    236 more verbose or more debug-y logs.
    237 
    238 Otherwise, you can start out with `0` as "you always want to see this",
    239 `1` as "common logging that you might *possibly* want to turn off", and
    240 `10` as "I would like to performance-test your log collection stack."
    241 
    242 Then gradually choose levels in between as you need them, working your way
    243 down from 10 (for debug and trace style logs) and up from 1 (for chattier
    244 info-type logs.)
    245 
    246 #### How do I choose my keys?
    247 
    248 Keys are fairly flexible, and can hold more or less any string
    249 value. For best compatibility with implementations and consistency
    250 with existing code in other projects, there are a few conventions you
    251 should consider.
    252 
    253 - Make your keys human-readable.
    254 - Constant keys are generally a good idea.
    255 - Be consistent across your codebase.
    256 - Keys should naturally match parts of the message string.
    257 - Use lower case for simple keys and
    258   [lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for
    259   more complex ones. Kubernetes is one example of a project that has
    260   [adopted that
    261   convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments).
    262 
    263 While key names are mostly unrestricted (and spaces are acceptable),
    264 it's generally a good idea to stick to printable ascii characters, or at
    265 least match the general character set of your log lines.
    266 
    267 #### Why should keys be constant values?
    268 
    269 The point of structured logging is to make later log processing easier.  Your
    270 keys are, effectively, the schema of each log message.  If you use different
    271 keys across instances of the same log line, you will make your structured logs
    272 much harder to use.  `Sprintf()` is for values, not for keys!
    273 
    274 #### Why is this not a pure interface?
    275 
    276 The Logger type is implemented as a struct in order to allow the Go compiler to
    277 optimize things like high-V `Info` logs that are not triggered.  Not all of
    278 these implementations are implemented yet, but this structure was suggested as
    279 a way to ensure they *can* be implemented.  All of the real work is behind the
    280 `LogSink` interface.
    281 
    282 [warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging