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)
}
}()