Pragmatic and minimalistic module for collecting and exporting trace data from the Go code.
prometheus/client_golang but for Traces
NOTE: This project follows semver, but it is in experimental v0.x.y phase. API might change.
This library was born from the fact that the current state of Go clients for tracing are far from perfection, especially on simplicity and API front. For now it's meant for demostration purposes on how simple API can be to satisfy most of the tracing needs for the manual instrumentation.
We can take inspiration from the success of the Prometheus client_golang library (package used in more than 51,000 repositories) was in some way thanks to the simplicity, stability and efficiency of that Go client for metrics. Strict compatibility, clear API and error semantics, no scope creep and single module are the things that enabled massive value to so many people and organizations in the community. The key is to make the best user (end developer) experience possible.
The above learnings was what motivated the creation of github.com/bwplotka/tracing-go
.
- No global state, only
context
based usage. - Manual span instrumentation with clear error semantics.
- You can only create sub-spans from context (only one way of creating spans).
- Export of traces (spans) to the desired tracing backend or collector:
- Using gRPC OTLP protocol
- Using Jaeger Thrift Collector, because Jaeger does not support OTLP yet 🙃
- Writing to file e.g. stdout/stderr.
net/http
instrumentation (checkhttp
directory withtracinghttp
package).
This project wraps multiple https://github.com/open-telemetry/opentelemetry-go modules, (almost) fully hiding those from the public interface. Yet, if you import github.com/bwplotka/tracing-go
module you will transiently import OpenTelemetry modules.
First, create tracer with exporter(s) you want e.g. Jaeger HTTP Thrift.
// import "github.com/bwplotka/tracing-go/tracing"
// import "github.com/bwplotka/tracing-go/tracing/exporters/jaeger"
tr, closeFn, err := tracing.NewTracer(jaeger.Exporter(jaegerEndpoint), tracing.WithServiceName("app"))
if err != nil {
// Handle err...
}
defer closeFn()
Then use it to create root span that also gives context that can be used to create more sub-spans. NOTE: Only context has power to create sub spans.
// import "github.com/bwplotka/tracing-go/tracing"
ctx, root := tr.StartSpan("app")
defer root.End(nil)
// ...
ctx, span := tracing.StartSpan(ctx, "dummy operation")
defer func() {
span.End(err)
}()
Use DoInSpan
if you want to do something in the dedicated span.
// import "github.com/bwplotka/tracing-go/tracing"
ctx, root := tr.StartSpan("app")
defer root.End(nil)
tracing.DoInSpan(ctx, "sub operation1", func(ctx context.Context) error {
// ...
})
tracing.DoInSpan(ctx, "sub operation2", func(ctx context.Context) error {
// ...
})
Use GetSpan
to get current span (without starting) from current context. For example to add attributes to span.
NOTE: It's ONLY .StartSpan
method caller responsibility to end span.
// import "github.com/bwplotka/tracing-go/tracing"
ctx, root := tr.StartSpan("app")
defer root.End(nil)
// ...
anotherCopyOfRootSpan := tracing.GetSpan(ctx)
anotherCopyOfRootSpan.SetAttributtes("key", "value")
// ...
See (and run if you want) an example instrumented application using our docker based e2e suite.
E2e example should sent spans to in-memory Jaeger and present view like this:
- Initial version of this library was written for @AnaisUrlichs and @bwplotka demo of monitoring Argo Rollout jobs
- OpenTelemetry project for providing OTLP trace protocol and Go implementation for writing gRPC OTLP.