OpenTelemetry Go implementation providing APIs for distributed tracing, metrics, and logging to instrument applications and send telemetry data to observability platforms
The OpenTelemetry Go SDK metric implementation provides concrete implementations of the metric API interfaces, including MeterProvider, readers (periodic and manual), exporters, views, and aggregations.
import sdkmetric "go.opentelemetry.io/otel/sdk/metric"The SDK metric package provides:
The MeterProvider is the SDK implementation of the metric.MeterProvider interface.
type MeterProvider struct {
// Has unexported fields
}func NewMeterProvider(options ...Option) *MeterProviderDefault Configuration:
Example:
package main
import (
"context"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func main() {
ctx := context.Background()
// Create resource
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceVersion("1.0.0"),
),
)
if err != nil {
log.Fatal(err)
}
// Create exporter
exporter, err := otlpmetrichttp.New(ctx)
if err != nil {
log.Fatal(err)
}
// Create meter provider
mp := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(metric.NewPeriodicReader(exporter)),
)
// Set as global meter provider
otel.SetMeterProvider(mp)
// Cleanup
defer func() {
if err := mp.Shutdown(ctx); err != nil {
log.Printf("Error shutting down meter provider: %v", err)
}
}()
// Use meter
meter := otel.Meter("my-meter")
counter, err := meter.Int64Counter("requests")
if err != nil {
log.Fatal(err)
}
counter.Add(ctx, 1)
}// Meter returns a Meter with the given name and options
func (mp *MeterProvider) Meter(name string, options ...metric.MeterOption) metric.Meter
// Shutdown shuts down the MeterProvider, flushing all pending telemetry
func (mp *MeterProvider) Shutdown(ctx context.Context) error
// ForceFlush flushes all pending telemetry
func (mp *MeterProvider) ForceFlush(ctx context.Context) errortype Option interface {
// Has unexported methods
}Available Options:
// WithResource configures the Resource for the MeterProvider
func WithResource(r *resource.Resource) Option
// WithReader configures a Reader for the MeterProvider
func WithReader(r Reader) Option
// WithView configures Views for the MeterProvider
func WithView(views ...View) OptionExample:
import (
"go.opentelemetry.io/otel/sdk/metric"
)
mp := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(metric.NewPeriodicReader(exporter)),
metric.WithView(
metric.NewView(
metric.Instrument{Name: "*.duration"},
metric.Stream{
Aggregation: metric.AggregationExplicitBucketHistogram{
Boundaries: []float64{0, 5, 10, 25, 50, 100},
},
},
),
),
)Readers collect and export metric data. The SDK provides two built-in readers.
type Reader interface {
// Collect gathers all metric data related to the Reader
Collect(ctx context.Context, rm *metricdata.ResourceMetrics) error
// Shutdown flushes all metric measurements and releases resources
Shutdown(ctx context.Context) error
}PeriodicReader collects and exports metric data at a defined interval.
func NewPeriodicReader(exporter Exporter, options ...PeriodicReaderOption) *PeriodicReaderDefault Configuration:
Example:
import (
"time"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/sdk/metric"
)
func setupPeriodicReader() *metric.PeriodicReader {
exporter, err := stdoutmetric.New()
if err != nil {
log.Fatal(err)
}
reader := metric.NewPeriodicReader(
exporter,
metric.WithInterval(30*time.Second),
metric.WithTimeout(10*time.Second),
)
return reader
}Options for configuring a PeriodicReader.
type PeriodicReaderOption interface {
// Has unexported methods
}Available Options:
// WithInterval configures the intervening time between exports
func WithInterval(d time.Duration) PeriodicReaderOption
// WithTimeout configures the time a reader waits for an export to complete
func WithTimeout(d time.Duration) PeriodicReaderOption
// WithProducer registers producers as external Producer of metric data
func WithProducer(p Producer) ReaderOptionManualReader allows an application to read metrics on demand.
func NewManualReader(opts ...ManualReaderOption) *ManualReaderExample:
import (
"context"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
func setupManualReader() *metric.ManualReader {
reader := metric.NewManualReader()
return reader
}
func collectMetrics(reader *metric.ManualReader) error {
ctx := context.Background()
var rm metricdata.ResourceMetrics
// Manually trigger collection
if err := reader.Collect(ctx, &rm); err != nil {
return err
}
// Process collected metrics
for _, scopeMetrics := range rm.ScopeMetrics {
for _, m := range scopeMetrics.Metrics {
log.Printf("Metric: %s", m.Name)
}
}
return nil
}Options for configuring a ManualReader.
type ManualReaderOption interface {
// Has unexported methods
}Available Options:
// WithTemporalitySelector configures the TemporalitySelector
func WithTemporalitySelector(selector TemporalitySelector) ManualReaderOption
// WithAggregationSelector configures the AggregationSelector
func WithAggregationSelector(selector AggregationSelector) ManualReaderOption
// WithProducer registers producers as external Producer of metric data
func WithProducer(p Producer) ReaderOptionExporters send metric data to external systems.
type Exporter interface {
// Temporality returns the Temporality to use for an instrument kind
Temporality(InstrumentKind) metricdata.Temporality
// Aggregation returns the Aggregation to use for an instrument kind
Aggregation(InstrumentKind) Aggregation
// Export serializes and transmits metric data to a receiver
Export(ctx context.Context, rm *metricdata.ResourceMetrics) error
// ForceFlush flushes any metric data held by an exporter
ForceFlush(ctx context.Context) error
// Shutdown flushes all metric data and releases resources
Shutdown(ctx context.Context) error
}Temporality defines how measurements are aggregated over time.
type Temporality int
const (
// CumulativeTemporality means data is aggregated from a fixed start time
CumulativeTemporality Temporality = iota
// DeltaTemporality means data is aggregated over the last collection interval
DeltaTemporality
)type TemporalitySelector func(InstrumentKind) metricdata.Temporality
// DefaultTemporalitySelector returns CumulativeTemporality for all instruments
func DefaultTemporalitySelector(InstrumentKind) metricdata.TemporalityExample:
// Custom temporality selector
func customTemporalitySelector(kind metric.InstrumentKind) metricdata.Temporality {
switch kind {
case metric.InstrumentKindCounter,
metric.InstrumentKindObservableCounter,
metric.InstrumentKindHistogram:
return metricdata.DeltaTemporality
default:
return metricdata.CumulativeTemporality
}
}
reader := metric.NewManualReader(
metric.WithTemporalitySelector(customTemporalitySelector),
)Views transform and filter metrics before they are exported.
type View struct {
// Has unexported fields
}
func NewView(criteria Instrument, mask Stream) (View, error)type Instrument struct {
// Name is the name of the instrument(s) to match. Supports wildcards "*"
Name string
// Description is the description to match
Description string
// Kind is the instrument kind to match
Kind InstrumentKind
// Unit is the unit to match
Unit string
// Scope is the instrumentation scope to match
Scope instrumentation.Scope
}type Stream struct {
// Name is the name to use for the resulting stream
Name string
// Description is the description to use for the resulting stream
Description string
// Aggregation is the aggregation to use
Aggregation Aggregation
// AttributeFilter is a function to filter attributes
AttributeFilter attribute.Filter
}import "go.opentelemetry.io/otel/sdk/metric"
// Rename a metric
view, err := metric.NewView(
metric.Instrument{Name: "old.metric.name"},
metric.Stream{Name: "new.metric.name"},
)
if err != nil {
log.Fatal(err)
}
mp := metric.NewMeterProvider(
metric.WithView(view),
)// Change histogram buckets
view, err := metric.NewView(
metric.Instrument{Name: "http.server.duration"},
metric.Stream{
Aggregation: metric.AggregationExplicitBucketHistogram{
Boundaries: []float64{0, 5, 10, 25, 50, 100, 250, 500, 1000},
},
},
)// Keep only specific attributes
view, err := metric.NewView(
metric.Instrument{Name: "requests"},
metric.Stream{
AttributeFilter: attribute.NewAllowKeysFilter(
"http.method",
"http.status_code",
),
},
)// Drop a metric entirely
view, err := metric.NewView(
metric.Instrument{Name: "debug.*"},
metric.Stream{Aggregation: metric.AggregationDrop{}},
)// Match all duration metrics
view, err := metric.NewView(
metric.Instrument{Name: "*.duration"},
metric.Stream{
Aggregation: metric.AggregationExplicitBucketHistogram{
Boundaries: []float64{0, 5, 10, 25, 50, 100},
},
},
)Aggregations define how measurements are combined.
type Aggregation interface {
// Has unexported methods
}Uses the default aggregation for the instrument kind.
type AggregationDefault struct{}Drops all measurements.
type AggregationDrop struct{}Aggregates measurements as a sum.
type AggregationSum struct{}Keeps only the last measurement.
type AggregationLastValue struct{}Aggregates measurements into histogram buckets.
type AggregationExplicitBucketHistogram struct {
// Boundaries are the increasing bucket boundary values
Boundaries []float64
// NoMinMax indicates whether to record min and max
NoMinMax bool
}Example:
agg := metric.AggregationExplicitBucketHistogram{
Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000},
NoMinMax: false,
}Aggregates measurements into exponentially-sized histogram buckets.
type AggregationBase2ExponentialHistogram struct {
// MaxSize is the maximum number of buckets
MaxSize int32
// MaxScale is the maximum scale factor
MaxScale int32
// NoMinMax indicates whether to record min and max
NoMinMax bool
}type AggregationSelector func(InstrumentKind) Aggregation
// DefaultAggregationSelector returns default aggregations for each instrument kind
func DefaultAggregationSelector(ik InstrumentKind) AggregationDefault Aggregations:
package main
import (
"context"
"log"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/metric"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func main() {
ctx := context.Background()
// Step 1: Create resource
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceVersion("1.0.0"),
),
)
if err != nil {
log.Fatalf("Failed to create resource: %v", err)
}
// Step 2: Create exporter
exporter, err := otlpmetrichttp.New(ctx,
otlpmetrichttp.WithEndpoint("localhost:4318"),
otlpmetrichttp.WithInsecure(),
)
if err != nil {
log.Fatalf("Failed to create exporter: %v", err)
}
// Step 3: Create periodic reader
reader := sdkmetric.NewPeriodicReader(
exporter,
sdkmetric.WithInterval(30*time.Second),
sdkmetric.WithTimeout(10*time.Second),
)
// Step 4: Create views
durationView, err := sdkmetric.NewView(
sdkmetric.Instrument{Name: "*.duration"},
sdkmetric.Stream{
Aggregation: sdkmetric.AggregationExplicitBucketHistogram{
Boundaries: []float64{0, 5, 10, 25, 50, 100, 250, 500, 1000},
},
},
)
if err != nil {
log.Fatalf("Failed to create duration view: %v", err)
}
requestsView, err := sdkmetric.NewView(
sdkmetric.Instrument{Name: "http.server.requests"},
sdkmetric.Stream{
AttributeFilter: attribute.NewAllowKeysFilter(
"http.method",
"http.status_code",
"http.route",
),
},
)
if err != nil {
log.Fatalf("Failed to create requests view: %v", err)
}
// Step 5: Create meter provider
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(reader),
sdkmetric.WithView(durationView, requestsView),
)
// Step 6: Set as global provider
otel.SetMeterProvider(mp)
// Step 7: Setup cleanup
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := mp.Shutdown(ctx); err != nil {
log.Printf("Failed to shutdown meter provider: %v", err)
}
}()
// Step 8: Use meter
meter := otel.Meter("my-component")
// Create instruments
requestCounter, err := meter.Int64Counter(
"http.server.requests",
metric.WithDescription("Number of HTTP requests"),
metric.WithUnit("{request}"),
)
if err != nil {
log.Fatal(err)
}
requestDuration, err := meter.Float64Histogram(
"http.server.duration",
metric.WithDescription("Duration of HTTP requests"),
metric.WithUnit("ms"),
)
if err != nil {
log.Fatal(err)
}
// Record measurements
requestCounter.Add(ctx, 1,
metric.WithAttributes(
attribute.String("http.method", "GET"),
attribute.Int("http.status_code", 200),
attribute.String("http.route", "/api/users"),
),
)
requestDuration.Record(ctx, 45.2,
metric.WithAttributes(
attribute.String("http.method", "GET"),
attribute.String("http.route", "/api/users"),
),
)
}The SDK respects the following environment variables:
# Export interval in milliseconds
OTEL_METRIC_EXPORT_INTERVAL=60000
# Export timeout in milliseconds
OTEL_METRIC_EXPORT_TIMEOUT=30000// Good: Automatic periodic export
reader := sdkmetric.NewPeriodicReader(exporter)
// Bad: Manual collection is error-prone
reader := sdkmetric.NewManualReader()// Balance between freshness and overhead
reader := sdkmetric.NewPeriodicReader(
exporter,
sdkmetric.WithInterval(30*time.Second), // Adjust based on needs
)// Filter high-cardinality attributes
view, _ := sdkmetric.NewView(
sdkmetric.Instrument{Name: "http.server.requests"},
sdkmetric.Stream{
AttributeFilter: attribute.NewAllowKeysFilter(
"http.method",
"http.status_code",
// Don't include user_id or request_id
),
},
)// Use boundaries appropriate for your data
view, _ := sdkmetric.NewView(
sdkmetric.Instrument{Name: "request.duration"},
sdkmetric.Stream{
Aggregation: sdkmetric.AggregationExplicitBucketHistogram{
Boundaries: []float64{0, 10, 50, 100, 500, 1000, 5000},
},
},
)mp := sdkmetric.NewMeterProvider(/*...*/)
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := mp.Shutdown(ctx); err != nil {
log.Printf("Error shutting down: %v", err)
}
}()Install with Tessl CLI
npx tessl i tessl/golang-go-opentelemetry-io--otel