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