Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/metrics/opentel"
A metrics.Client backed by OpenTelemetry, covering metrics and distributed tracing through one client.
m, err := opentel.New(ctx, "myservice", "1.4.2")
defer m.Close()
Both signals, one construction
This is what separates it from the other two backends. prometheus and statsd do metrics. This one also produces spans, and the instrumentation points are shared: wrapping an HTTP handler records a duration histogram and starts a server span; wrapping a round tripper does the same on the client side and injects trace context into the outbound request.
So the trace and the metric for a request come from the same wrapper, with the same attributes, and a latency spike in a dashboard leads to the traces that produced it.
New configures and registers the global OpenTelemetry providers (tracer, meter, propagator), creates the default counters, and records shutdown functions.
Exporters come from the environment
There is no exporter endpoint argument. Selection is driven by the standard OpenTelemetry variables:
| Variable | Effect |
|---|---|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | OTLP/HTTP for traces |
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT | OTLP/HTTP for metrics |
OTEL_EXPORTER_OTLP_ENDPOINT | both, when the specific ones are unset |
With none of them set, both signals go to a stdout exporter. That keeps a developer machine working with no collector running. In a container it means an unset endpoint puts telemetry in the log stream rather than discarding it.
Resource attributes resolve the same way, from OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION, OTEL_DEPLOYMENT_ENVIRONMENT_NAME and OTEL_RESOURCE_ATTRIBUTES, combined with the name and version passed to New.
Configuring through the environment means the collector endpoint is a deployment concern rather than a code change, which is how the OpenTelemetry ecosystem expects it to be wired.
Global state, and what follows from it
New registers global providers. Two consequences worth planning around:
Calling it twice replaces the providers, so one call per process, in main.
Tests that construct a client mutate process-wide state, so they cannot run in parallel with anything that reads it. The function options exist partly for this: WithTracerProviderFn, WithMeterProviderFn, WithSDKResourceFn and WithPropagator let a test inject its own providers instead of the environment-driven ones.
Close, and why it takes a context
Both Close and CloseCtx are available. Exporters buffer, and the OTLP exporter batches spans before sending, so terminating without flushing loses whatever was queued, which is typically the telemetry from the shutdown itself.
CloseCtx takes a context so the flush is bounded. Shutdown that hangs because a collector is unreachable turns a clean deployment into a stuck pod, and a context with a deadline is what prevents it. Wire it into the shutdown path from bootstrap.
SQL
SqlOpen and InstrumentDB use otelsql, so database calls appear as spans inside the request trace rather than as an unexplained gap. This is where tracing repays the setup: a slow endpoint shows the specific query, in the specific request, that took the time.
Dependency weight
This is the heaviest package in the module by dependency count: the OpenTelemetry SDK, the OTLP exporters, the instrumentation libraries and otelsql. prometheus is considerably lighter and statsd lighter still.
Because Go resolves dependencies per package, that weight lands only on a binary that imports this one. See /docs/dependency-footprint/.
Dependencies
Reaches the OpenTelemetry SDK, OTLP exporters, otelhttp and otelsql.
Links
- Reference documentation
- Source code
- The contract: metrics
- Other backends: prometheus, statsd
- Header-based request IDs instead: traceid
- All nurago packages