or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

attributes.mdbridges.mdcontext-propagation.mdexporters-other.mdexporters-otlp.mdindex.mdlogging.mdmetrics.mdsdk-log.mdsdk-metric.mdsdk-resource.mdsdk-trace.mdsemantic-conventions.mdtracing.md
tile.json

sdk-metric.mddocs/

SDK Metric Implementation

The OpenTelemetry Go SDK metric implementation provides concrete implementations of the metric API interfaces, including MeterProvider, readers (periodic and manual), exporters, views, and aggregations.

Package Import

import sdkmetric "go.opentelemetry.io/otel/sdk/metric"

Overview

The SDK metric package provides:

  1. MeterProvider: Factory for creating instrumented Meters
  2. Reader: Collect and export metric data (PeriodicReader, ManualReader)
  3. Exporter: Export metrics to external systems
  4. View: Transform and filter metrics
  5. Aggregation: Configure how measurements are aggregated

MeterProvider

The MeterProvider is the SDK implementation of the metric.MeterProvider interface.

Type Definition

type MeterProvider struct {
	// Has unexported fields
}

Creating a MeterProvider

func NewMeterProvider(options ...Option) *MeterProvider

Default Configuration:

  • Resource: resource.Default()
  • No Readers (no metrics will be exported)

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

MeterProvider Methods

// 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) error

MeterProvider Options

type 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) Option

Example:

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

Readers collect and export metric data. The SDK provides two built-in readers.

Reader Interface

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

PeriodicReader collects and exports metric data at a defined interval.

func NewPeriodicReader(exporter Exporter, options ...PeriodicReaderOption) *PeriodicReader

Default Configuration:

  • Interval: 60 seconds
  • Timeout: 30 seconds

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
}

PeriodicReaderOption

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) ReaderOption

ManualReader

ManualReader allows an application to read metrics on demand.

func NewManualReader(opts ...ManualReaderOption) *ManualReader

Example:

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
}

ManualReaderOption

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) ReaderOption

Exporters

Exporters send metric data to external systems.

Exporter Interface

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

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
)

TemporalitySelector

type TemporalitySelector func(InstrumentKind) metricdata.Temporality

// DefaultTemporalitySelector returns CumulativeTemporality for all instruments
func DefaultTemporalitySelector(InstrumentKind) metricdata.Temporality

Example:

// 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

Views transform and filter metrics before they are exported.

View Type

type View struct {
	// Has unexported fields
}

func NewView(criteria Instrument, mask Stream) (View, error)

Instrument (Criteria)

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
}

Stream (Transformation)

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
}

View Examples

Rename Metric

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 Aggregation

// 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},
		},
	},
)

Filter Attributes

// Keep only specific attributes
view, err := metric.NewView(
	metric.Instrument{Name: "requests"},
	metric.Stream{
		AttributeFilter: attribute.NewAllowKeysFilter(
			"http.method",
			"http.status_code",
		),
	},
)

Drop Metrics

// Drop a metric entirely
view, err := metric.NewView(
	metric.Instrument{Name: "debug.*"},
	metric.Stream{Aggregation: metric.AggregationDrop{}},
)

Wildcard Matching

// 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

Aggregations define how measurements are combined.

Aggregation Interface

type Aggregation interface {
	// Has unexported methods
}

Built-in Aggregations

AggregationDefault

Uses the default aggregation for the instrument kind.

type AggregationDefault struct{}

AggregationDrop

Drops all measurements.

type AggregationDrop struct{}

AggregationSum

Aggregates measurements as a sum.

type AggregationSum struct{}

AggregationLastValue

Keeps only the last measurement.

type AggregationLastValue struct{}

AggregationExplicitBucketHistogram

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,
}

AggregationBase2ExponentialHistogram

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
}

AggregationSelector

type AggregationSelector func(InstrumentKind) Aggregation

// DefaultAggregationSelector returns default aggregations for each instrument kind
func DefaultAggregationSelector(ik InstrumentKind) Aggregation

Default Aggregations:

  • Counter: Sum
  • UpDownCounter: Sum
  • Histogram: ExplicitBucketHistogram
  • ObservableCounter: Sum
  • ObservableUpDownCounter: Sum
  • ObservableGauge: LastValue

Complete Example

Full Setup with Views and Custom 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"),
		),
	)
}

Environment Variables

The SDK respects the following environment variables:

Periodic Reader Configuration

# Export interval in milliseconds
OTEL_METRIC_EXPORT_INTERVAL=60000

# Export timeout in milliseconds
OTEL_METRIC_EXPORT_TIMEOUT=30000

Best Practices

1. Use PeriodicReader for Production

// Good: Automatic periodic export
reader := sdkmetric.NewPeriodicReader(exporter)

// Bad: Manual collection is error-prone
reader := sdkmetric.NewManualReader()

2. Configure Appropriate Export Intervals

// Balance between freshness and overhead
reader := sdkmetric.NewPeriodicReader(
	exporter,
	sdkmetric.WithInterval(30*time.Second), // Adjust based on needs
)

3. Use Views to Control Cardinality

// 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
		),
	},
)

4. Customize Histogram Boundaries

// 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},
		},
	},
)

5. Always Shutdown MeterProvider

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

Related Documentation

  • Metrics API: Core metrics API interfaces
  • SDK Resource: Resource detection and configuration
  • OTLP Exporters: OTLP metric exporters
  • Other Exporters: Prometheus, stdout exporters
  • Semantic Conventions: Standard metric names