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.