github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/README.md (about) 1 2 <table> 3 <tr> 4 <td> 5 6 [![go report](https://goreportcard.com/badge/github.com/facebookincubator/go-belt)](https://goreportcard.com/report/github.com/facebookincubator/go-belt) 7 [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 8 9 Out of the box tools (interfaces): 10 |Module|GoDoc|QuickStart| 11 |-|-|-| 12 |[Logger](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/types/logger.go#L37-L219)|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/logger?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger?tab=doc)|[example](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/examples/doc_test.go)| 13 |[Metrics](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/metrics/types/metrics.go#L20-L66) (experimental)|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/experimental/metrics?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/experimental/metrics?tab=doc)|[example](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/metrics/examples/doc_test.go)| 14 |[Tracer](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/tracer/tracer.go#L22-L69) (experimental)|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/experimental/tracer?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/experimental/tracer?tab=doc)|[example](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/tracer/examples/doc_test.go)| 15 |[ErrorMonitor](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/errmon/types/error_monitor.go#L47-L89) (experimental)|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/experimental/errmon?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/experimental/errmon?tab=doc)|[example](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/errmon/examples/doc_test.go)| 16 |[Belt](https://github.com/facebookincubator/go-belt/blob/main/belt.go#L21-L34)|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt?tab=doc)|| 17 18 Out of the box implementation examples: 19 |Module|Implementation|GoDoc|QuickStart| 20 |-|-|-|-| 21 |Logger|logrus|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/logger/implementation/logrus?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/logrus?tab=doc)|`logrus.Default()`| 22 |Logger|zap|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/logger/implementation/zap?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/zap?tab=doc)|`zap.Default()`| 23 |Logger|glog|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/logger/implementation/glog?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/glog?tab=doc)|`glog.New()`| 24 |Metrics|prometheus|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/experimental/metrics/implementation/prometheus?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/experimental/metrics/implementation/prometheus?tab=doc)|`prometheus.Default()`| 25 |ErrorMonitor|sentry|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry?tab=doc)|`sentry.New(sentryClient)`| 26 |Tracer|zipkin|[![GoDoc](https://godoc.org/github.com/facebookincubator/go-belt/tool/experimental/tracer/implementation/zipkin?status.svg)](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/experimental/tracer/implementation/zipkin?tab=doc)|`zipkin.New(zipkinTracer)`| 27 28 </td> 29 <td style='vertical-align:top'> 30 31 ![logo](doc/logo/variant2-small.png "logo") 32 33 </td> 34 <table> 35 36 # Index 37 1. [Mission](#mission) 38 2. [About](#about) 39 3. [Overview](#overview) 40 4. [Why should I use it?](#why-should-I-use-it) 41 5. [Quick start](#quick-start) 42 6. [Exotic cases](#exotic-cases) 43 44 # About 45 46 This package contains implementation-agnostic interfaces for application observability (such as logging, metrics and so on) and a collection of implementations of these interfaces. And all the observability tools are merged together into an "observability tool belt". 47 48 This project implements these ideas: 49 * **Dependency injection.** No observability tooling should be hardcoded into an application. 50 * A tool interface just represents aggregated **best practices.** 51 * **Easy to use.** Doing application observability comfortable in any project (is it a simple hobby project or a hyperscaler commercial project). 52 * Various observability tools share the idea of **structured data.** Let's take advantage of this. 53 54 This crosses with the ideas of [OpenTelemetry](https://github.com/open-telemetry/opentelemetry.io/blob/main/content/en/community/mission.md), but much more focused on "easy to use", contextual structured data, injected dependencies and deeper decoupling. Also OpenTelemetry [is more focused on metrics and tracing](https://github.com/open-telemetry/opentelemetry-go/blob/1cbd4c2b7726ded5e7a9a18997cb25977137a0b1/README.md), while this package pays more attention to logging. These projects does not compete, the opposite: for example, one may implement go-belt interfaces with OpenTelemetry SDK. 55 56 # Mission 57 58 This package intended to improve the culture of application observability in applications written in Go and to standardize approaches used across the community. In other words this is an attempt to accumulate (opinionated but:) the most generic best practices of how to handle observability (and first of all: **logging,** metrics, tracing and error monitoring). 59 60 We do not want to just propose some additional solution. We do want to collect all the best trade-offs 61 together and be open for changes. Please do not hesitate to propose any changes (even the most drastic ones) if you believe that will address this mission. We will try to find the best trade-offs for a generic use case and continuously improve these packages. This is the whole point of the project. 62 63 *Just in case a reminder: "the best" -- does not mean "perfect", it means "the most practical". Also more drastic the change is, more reasoning it requires.* 64 65 # Overview 66 67 There are 5 main components here: 68 69 * [Logger](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/README.md) 70 * [Metrics](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/metrics/README.md) 71 * [Distributed Tracer](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/tracer/README.md) 72 * [Error Monitor](https://github.com/facebookincubator/go-belt/blob/main/tool/experimental/errmon/README.md) 73 * and optionally the "[Belt](https://pkg.go.dev/github.com/facebookincubator/go-belt)" to access all of these and more. 74 75 All of these components are generic and abstracted from specific implementation. And some implementations are provided for each of those. For example there are implementations for [Logger](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger) based on: [zap](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/zap), [logrus](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/logrus), [glog](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/glog) and [standard Go's log package](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/stdlib). 76 77 And if one needs only the best practices (accumulated so far) for logging, just go to [Logger](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger) and disregard everything else in here. Pick an existing [implementation](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation) or write a new one (and create a PR to push it here). All this applies to any other tool. 78 79 But if one needs a sane control over multiple tools at the same time then use the `Belt`. This also applies to tools not mentioned here. 80 81 # Why should I use it? 82 83 * Provide **observability tooling as a dependency injection,** instead of hardcoding an implementation. For example the most of IT companies have internal infra with their-specific observability infra -- this package makes code implementation-agnostic, so that any implementation could be injected using the same shared code (see example: [ConTest](https://github.com/linuxboot/contest)). 84 * To make code **reusable among different projects**. To do not reinvent the same wheels over and over again working in different projects with people of different opinions about observability. In this package we try to cover the most of popular ways to do the logging, hoping it will be a good enough compromise for everybody. 85 * To have an **application with observability in mind**. Even if the application is already implemented without having observability tooling in mind, it is easily fixable by this package. See the [Quick start](#quick-start) section. Or if one does not plan to add proper observability at a specific moment they still can already start using this package (it does not create essential coding overhead), and in any moment in the future it will be very easy to add all the desired observability. 86 * To be aligned with **the best practices**. It is highly encouraged to constructively question and discuss the approaches applied here. If something is not aligned with the best practices then the goal of this project is to adapt. 87 88 # Quick start 89 90 ## Logger 91 92 See also more detailed info on using `Logger` in [its README.md](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/README.md). 93 94 ### Approach "contextual logger" 95 96 ```go 97 import ( 98 "github.com/facebookincubator/go-belt/belt" 99 "github.com/facebookincubator/go-belt/tool/logger/implementation/zap" 100 ) 101 102 func main() { 103 ... 104 ctx = logger.CtxWithLogger(ctx, zap.Default()) 105 ... 106 someFunc(ctx) 107 ... 108 } 109 110 func someFunc(ctx context.Context) { 111 ... 112 ctx = belt.WithField(ctx, "user_id", user.ID) 113 ... 114 anotherFunc(ctx) 115 ... 116 } 117 118 func anotherFunc(ctx context.Context) { 119 ... 120 logger.Debug(ctx, "hello world!") // user_id will also be logged here 121 ... 122 } 123 ``` 124 125 Also contexts usually are already propagated through a lot of codebases. Thus, one may take advantage of that in an existing codebase. 126 127 ### Approach "safer contextual logger" 128 There is a major argument against the approach above: 129 * `context.Context` is pretty generic entity and may be generated by anybody, thus there could be lack of guarantee of having `Logger` in the context (because it is unclear where the specific context came from). This issue is partly mitigated through default `Logger` and default `Belt`, but in some cases there could be higher guarantee requirements. 130 131 To be sure we work with something correctly setup, it is possible to use `Belt` directly (instead of using it through a context): 132 ```go 133 import ( 134 "github.com/facebookincubator/go-belt" 135 "github.com/facebookincubator/go-belt/tool/logger/implementation/zap" 136 ) 137 138 func main() { 139 ... 140 belt := belt.New() 141 belt = logger.BeltWithLogger(belt, zap.Default()) 142 ... 143 someFunc(ctx, belt) 144 ... 145 } 146 147 func someFunc(ctx context.Context, belt *belt.Belt) { 148 ... 149 belt = belt.WithField("user_id", user.ID) 150 ... 151 anotherFunc(belt) 152 ... 153 } 154 155 func anotherFunc(ctx context.Context, belt *belt.Belt) { 156 ... 157 logger.FromBelt(belt).Debug("hello world!") // user_id will also be logged here 158 ... 159 } 160 ``` 161 here we always will be sure we work with the entity where Logger is correctly initialized. 162 163 ### Approach "just give me structured logger" 164 165 OK-OK. Instead of injecting logger into context, you may just use it directly: 166 ```go 167 // import "github.com/facebookincubator/go-belt/tool/logger/implementation/zap" 168 169 logger := zap.Default() 170 ... 171 fn(logger) // the function here is agnostic of specific logger implementation 172 ``` 173 174 And this logger will be based on [Uber's zap](https://github.com/uber-go/zap). 175 176 ### Approach "standard global logger" 177 ```go 178 // import "github.com/facebookincubator/go-belt/tool/logger/implementation/stdlib" 179 180 stdlib.Default().Debug("Hello world!") 181 ``` 182 Even though this approach is discouraged, it still keeps possibility to get proper structured logging and all the fancy stuff at any moment without any difficult changes in the code. 183 184 ## Metrics 185 186 Let's say we used the "contextual logger" approach for logging, and now we want to add metrics. 187 To do so we need only to add something like this to the initialization code: 188 ```go 189 // import ( 190 // promadapter "github.com/facebookincubator/go-belt/tool/experimental/metrics/implementation/prometheus" 191 // "github.com/prometheus/client_golang/prometheus" 192 // ) 193 promRegistry := prometheus.NewRegistry() 194 ctx = metrics.CtxWithMetrics(ctx, promadapter.New(promRegistry)) 195 ``` 196 197 and that's it. Now you can use prometheus metrics, for example: 198 ```go 199 metrics.FromCtx(ctx).Count("requests").Add(1) 200 ``` 201 202 It will also include all the structured fields (added for example with `WithField`) allowed for metrics as labels for the prometheus metric. For example: 203 204 ```go 205 import "github.com/facebookincubator/go-belt/tool/experimental/metrics" 206 207 func someFunc(ctx context.Context) { 208 ... 209 ctx = belt.WithField(ctx, "user_id", user.ID, metrics.FieldPropInclude) 210 ... 211 processRequest(ctx, req) 212 ... 213 } 214 215 func processRequest(ctx context.Context, req Request) { 216 defer metrics.FromCtx(ctx).GaugeInt("concurrent_request").Add(1).Add(-1) // "user_id" will be used here as a prometheus label. 217 ... 218 219 logger.FromCtx(ctx).Debug("hello world!") // and also "user_id" will be logged here as well. 220 } 221 ``` 222 223 It is required to add `metrics.FieldPropInclude` for fields which are used for metrics, because the amount of actual metrics proportional to the multiplication of all used values in all the labels. And some structured fields may be pretty random causing to generate unlimited amount of metrics and consume all the memory. 224 225 ## Error monitor 226 227 Now let's organize application errors. It is doable through just something like: 228 ```go 229 // import "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry" 230 ctx = errmon.CtxWithErrorMonitor(ctx, sentry.New(sentryClient)) 231 ``` 232 in the initialization code, and then using the error monitor where it is required. For example: 233 ```go 234 func someFunc(ctx context.Context) { 235 defer func(){ errmon.ObserveRecoverCtx(ctx, recover()) }() 236 237 ... 238 _, err := writer.Write(b) 239 errmon.ObserveErrorCtx(ctx, err) 240 ... 241 } 242 ``` 243 244 Specifically this code will send to [Sentry](https://sentry.io/) all the errors observed from `Write` and panics in `someFunc`. 245 246 Again, all the fields (for example added through `WithField`) will also be logged in a structured way as part of the event. 247 248 Other features (like `Breadcrumb`-s) are also supported. For example: 249 ```go 250 ctx = belt.WithField("breadcrumb_user.fetch", &errmon.Breadcrumb{ 251 TS: time.Now() 252 Path: []string{"user", "fetch"} 253 Categories: []string{"user", "network"} 254 Data: fetchUserErr, 255 }) 256 ``` 257 258 And: 259 ```go 260 errmon.ObserveErrorCtx(ctx, err) 261 ``` 262 now will also send the breadcrumb to the Sentry (or another error monitor implementation injected). 263 264 ## Distributed tracing 265 266 Again, first initializing it: 267 ```go 268 ctx = tracer.CtxWithTracer(ctx, zipkinadapter.New(zipkinClient)) 269 ``` 270 271 And use it: 272 ```go 273 func mysqlQuery(ctx context.Context, query string, args ...any) { 274 span, ctx := tracer.StartChildSpanFromCtx(ctx, "MySQL-query") 275 defer span.Finish() 276 // ..do the MySQL query here.. 277 } 278 ``` 279 That's it. Of course there are more features to cover the most generic needs. 280 281 ## Other tooling 282 283 Other observability tooling could be easily introduced into the `Belt`. Actually any of `logger`, `metrics`, `tracer` and `errmon` could have been provided by external projects and it would have work absolutely the same. In other words one may use these examples to create theirown standardized observability tooling and it will just work. And if you believe you created a good example of an observability tool then feel free to make a Pull Request to add it to `tool`-s here. 284 285 # More examples 286 287 See [`examples`](https://github.com/facebookincubator/go-belt/tree/main/examples) directory. 288 289 # Performance 290 291 The package is pretty much high-performance-aware despite being generic. For example using of `logrus` through this package makes it multiple times **FASTER** than using it directly. It makes even `zap` somewhat faster in a non-noop cases. This happens due to another design of handling fields, which avoids a lot of computation duplication for structured fields and provide already compiled (in a faster way) structures to the backend logger. For example on some synthetic tests it makes `zap` 15 times [faster](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/implementation/zap/BENCHMARKS.txt): 292 ``` 293 Benchmark/prod/depth205/WithField/callLog-false/bare_zap-16 128 914923 ns/op 4883118 B/op 3286 allocs/op 294 Benchmark/prod/depth205/WithField/callLog-false/adapted_zap-16 1870 60388 ns/op 103320 B/op 1845 allocs/op 295 Benchmark/prod/depth205/WithField/callLog-true/bare_zap-16 126 901277 ns/op 4886755 B/op 3290 allocs/op 296 Benchmark/prod/depth205/WithField/callLog-true/adapted_zap-16 840 141649 ns/op 247172 B/op 1866 allocs/op 297 ``` 298 299 # Exotic cases 300 301 ## Type-assertion 302 303 It is still allowed (though discouraged) to do type-assertion of an observability tool if it is necessary. For example: 304 305 ```go 306 logrusEntry := logger.GetEmitter(ctx).(*logrusadapter.Emitter).LogrusEntry 307 logrusEntry = logrusEntry.WithFields(logrus.Fields{ 308 "oldFashionLogrusFieldKey": "some value", 309 }) 310 logrusEntry.Debugf("hey!") 311 ``` 312 313 ## Writing a custom Logger 314 315 Depending on how many features your logger is ready to provide it should implement one of: 316 * `interface{ Printf(format string, args ...any)` or just be of type `func(string, ...any)` 317 * [`Emitter`](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/types/logger.go#L221-L251) 318 * [`CompactLogger`](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/adapter/compact_logger.go#L21-L131) 319 * [`Logger`](https://github.com/facebookincubator/go-belt/blob/main/tool/logger/types/logger.go#L37-L219) 320 321 And then you may call `adapter.LoggerFromAny` and it will convert your logger to `Logger` by adding everything what is missing in a naive generic way.