OpenTelemetry Metrics API for Go
This document covers the OpenTelemetry Metrics API for Go, including how to create instruments (Counter, UpDownCounter, Histogram, Observers) and configure them using options like metric.WithDescription(), metric.WithUnit(), and metric.WithAttributes(). To learn how to install and configure OpenTelemetry Go SDK, see Getting started with OpenTelemetry Go.
If you are not familiar with metrics terminology like timeseries or additive/synchronous/asynchronous instruments, read the introduction to OpenTelemetry Metrics first.
Getting started
To get started with metrics, you need a MeterProvider which you can use to create meters:
import "go.opentelemetry.io/otel"
// Meter can be a global/package variable.
var Meter = otel.Meter("app_or_package_name")
Using the meter, you can create instruments to measure performance. The simplest Counter instrument looks like this:
import "go.opentelemetry.io/otel/metric"
counter, _ := Meter.Int64Counter(
"test.my_counter",
metric.WithUnit("1"),
metric.WithDescription("Just a test counter"),
)
// Increment the counter.
counter.Add(ctx, 1, metric.WithAttributes(attribute.String("foo", "bar")))
counter.Add(ctx, 10, metric.WithAttributes(attribute.String("hello", "world")))
You can find more examples at GitHub.
Instrument Options
When creating instruments, you can configure them using several options:
metric.WithDescription()
Adds a human-readable description to your metric. This description appears in monitoring dashboards and helps other developers understand what the metric measures.
counter, _ := Meter.Int64Counter(
"http.server.requests",
metric.WithDescription("Total number of HTTP requests received"),
)
metric.WithUnit()
Specifies the unit of measurement. Common units include:
"1"- dimensionless (counts, ratios)"ms"- milliseconds"By"- bytes"s"- seconds"%"- percentage
histogram, _ := Meter.Int64Histogram(
"http.server.duration",
metric.WithUnit("ms"),
metric.WithDescription("HTTP request duration in milliseconds"),
)
metric.WithAttributes()
Adds attributes (labels) to individual measurements to distinguish between different dimensions of data:
counter.Add(ctx, 1, metric.WithAttributes(
attribute.String("http.method", "GET"),
attribute.String("http.status_code", "200"),
))
counter.Add(ctx, 1, metric.WithAttributes(
attribute.String("http.method", "POST"),
attribute.String("http.status_code", "500"),
))
Attributes allow you to slice and filter metrics in your monitoring system. For example, you can view request counts by HTTP method or status code.
Counter
Counter is a synchronous instrument that measures additive non-decreasing values.
// counter demonstrates how to measure non-decreasing numbers, for example,
// number of requests or connections.
func counter(ctx context.Context) {
counter, _ := Meter.Int64Counter(
"some.prefix.counter",
metric.WithUnit("1"),
metric.WithDescription("TODO"),
)
for {
counter.Add(ctx, 1)
time.Sleep(time.Millisecond)
}
}
You can get more interesting results by adding attributes to your measurements:
// counterWithLabels demonstrates how to add different labels ("hits" and "misses")
// to measurements. Using this simple trick, you can get number of hits, misses,
// sum = hits + misses, and hit_rate = hits / (hits + misses).
func counterWithLabels(ctx context.Context) {
counter, _ := Meter.Int64Counter(
"some.prefix.cache",
metric.WithDescription("Cache hits and misses"),
)
for {
if rand.Float64() < 0.3 {
// increment hits
counter.Add(ctx, 1, metric.WithAttributes(attribute.String("type", "hits")))
} else {
// increments misses
counter.Add(ctx, 1, metric.WithAttributes(attribute.String("type", "misses")))
}
time.Sleep(time.Millisecond)
}
}
UpDownCounter
UpDownCounter is a synchronous instrument which measures additive values that increase or decrease with time.
// upDownCounter demonstrates how to measure numbers that can go up and down, for example,
// number of goroutines or customers.
func upDownCounter(ctx context.Context) {
counter, _ := Meter.Int64UpDownCounter(
"some.prefix.up_down_counter",
metric.WithUnit("1"),
metric.WithDescription("TODO"),
)
for {
if rand.Float64() >= 0.5 {
counter.Add(ctx, +1)
} else {
counter.Add(ctx, -1)
}
time.Sleep(time.Second)
}
}
Histogram
Histogram is a synchronous instrument that produces a histogram from recorded values.
// histogram demonstrates how to record a distribution of individual values, for example,
// request or query timings. With this instrument you get total number of records,
// avg/min/max values, and heatmaps/percentiles.
func histogram(ctx context.Context) {
durRecorder, _ := meter.Int64Histogram(
"some.prefix.histogram",
metric.WithUnit("microseconds"),
metric.WithDescription("TODO"),
)
for {
dur := time.Duration(rand.NormFloat64()*5000000) * time.Microsecond
durRecorder.Record(ctx, dur.Microseconds())
time.Sleep(time.Millisecond)
}
}
CounterObserver
CounterObserver is an asynchronous instrument that measures additive non-decreasing values.
// counterObserver demonstrates how to measure monotonic (non-decreasing) numbers,
// for example, number of requests or connections.
func counterObserver(ctx context.Context) {
counter, _ := meter.Int64ObservableCounter(
"some.prefix.counter_observer",
metric.WithUnit("1"),
metric.WithDescription("TODO"),
)
var number int64
if _, err := meter.RegisterCallback(
// SDK periodically calls this function to collect data.
func(ctx context.Context, o metric.Observer) error {
number++
o.ObserveInt64(counter, number)
return nil
},
); err != nil {
panic(err)
}
}
UpDownCounterObserver
UpDownCounterObserver is an asynchronous instrument that measures additive values that can increase or decrease with time.
// upDownCounterObserver demonstrates how to measure numbers that can go up and down,
// for example, number of goroutines or customers.
func upDownCounterObserver(ctx context.Context) {
counter, err := meter.Int64ObservableUpDownCounter(
"some.prefix.up_down_counter_async",
metric.WithUnit("1"),
metric.WithDescription("TODO"),
)
if err != nil {
panic(err)
}
if _, err := meter.RegisterCallback(
func(ctx context.Context, o metric.Observer) error {
num := runtime.NumGoroutine()
o.ObserveInt64(counter, int64(num))
return nil
},
counter,
); err != nil {
panic(err)
}
}
GaugeObserver
GaugeObserver is an asynchronous instrument that measures non-additive values for which sum does not produce a meaningful correct result.
// gaugeObserver demonstrates how to measure non-additive numbers that can go up and down,
// for example, cache hit rate or memory utilization.
func gaugeObserver(ctx context.Context) {
gauge, _ := meter.Float64ObservableGauge(
"some.prefix.gauge_observer",
metric.WithUnit("1"),
metric.WithDescription("TODO"),
)
if _, err := meter.RegisterCallback(
func(ctx context.Context, o metric.Observer) error {
o.ObserveFloat64(gauge, rand.Float64())
return nil
},
gauge,
); err != nil {
panic(err)
}
}
OpenTelemetry backend
Uptrace is a DataDog competitor that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.
Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.
In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.