otlp

package
v5.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 25 Imported by: 0

README

OpenTelemetry OTLP Exporter for stats

This package provides OpenTelemetry Protocol (OTLP) export support for the stats library using the official OpenTelemetry SDK.

Features

  • Multiple Transport Protocols: Support for both gRPC and HTTP/Protobuf
  • Full OpenTelemetry SDK Integration: Uses official OTel SDK exporters
  • Environment Variable Support: Respects all standard OTEL_* environment variables
  • Resource Detection: Detects host and process information by default, plus attributes from OTEL_RESOURCE_ATTRIBUTES/OTEL_SERVICE_NAME. Cloud and Kubernetes detection is opt-in via the contrib/detectors/* packages (see below)
  • All Metric Types: Counter, Gauge, and Histogram support
  • Flexible Configuration: Configure via code or environment variables

Installation

go get github.com/segmentio/stats/v5

This package is part of the main github.com/segmentio/stats/v5 module, so no separate go get is needed if you already depend on stats. Note that it pulls in the OpenTelemetry SDK and gRPC, and requires Go 1.25 or later.

Quick Start

package main

import (
    "context"
    "log"

    "github.com/segmentio/stats/v5"
    "github.com/segmentio/stats/v5/otlp"
)

func main() {
    ctx := context.Background()

    // Create handler with gRPC transport
    handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
        Protocol: otlp.ProtocolGRPC,
        EndpointURL: "http://localhost:4317",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer handler.Shutdown(ctx)

    // Register with stats engine
    stats.Register(handler)
    defer stats.Flush()

    // Use stats as normal
    stats.Incr("requests.count")
}
Using HTTP
handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolHTTPProtobuf,
    EndpointURL: "http://localhost:4318",
})
Using Environment Variables (Simplest)
// Just set environment variables:
// export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
// export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
// export OTEL_SERVICE_NAME=my-service

handler, err := otlp.NewSDKHandlerFromEnv(ctx)

Configuration

SDKConfig Options
type SDKConfig struct {
    // Protocol: "grpc" or "http/protobuf" (default: "grpc")
    Protocol Protocol

    // EndpointURL: Full OTLP collector endpoint URL (with http:// or https:// scheme)
    // gRPC: "localhost:4317"
    // HTTP: "http://localhost:4318"
    Endpoint string

    // Resource: Custom resource attributes (optional)
    // If nil, uses automatic detection
    Resource *resource.Resource

    // ExportInterval: How often to export (default: 10s)
    ExportInterval time.Duration

    // ExportTimeout: Timeout for exports (default: 30s)
    ExportTimeout time.Duration

    // HTTPOptions: Additional HTTP options
    HTTPOptions []otlpmetrichttp.Option

    // GRPCOptions: Additional gRPC options
    GRPCOptions []otlpmetricgrpc.Option

    // ExponentialHistogram: Enable exponential histogram aggregation
    // (default: false, uses explicit bucket histograms)
    ExponentialHistogram bool

    // ExponentialHistogramMaxSize: Max buckets for exponential histograms
    // (default: 160 if ExponentialHistogram is true)
    ExponentialHistogramMaxSize int32

    // ExponentialHistogramMaxScale: Resolution for exponential histograms
    // Valid range: -10 to 20 (default: 20 if ExponentialHistogram is true)
    ExponentialHistogramMaxScale int32

    // TemporalitySelector: Determines temporality (cumulative vs delta)
    // (default: nil, which uses cumulative for all - Prometheus-compatible)
    TemporalitySelector sdkmetric.TemporalitySelector
}
Supported Environment Variables

The handler respects all standard OpenTelemetry environment variables:

  • OTEL_EXPORTER_OTLP_ENDPOINT - Base endpoint URL
  • OTEL_EXPORTER_OTLP_PROTOCOL - Transport protocol (grpc, http/protobuf); OTEL_EXPORTER_OTLP_METRICS_PROTOCOL takes precedence when both are set
  • OTEL_EXPORTER_OTLP_HEADERS - Custom headers for authentication
  • OTEL_EXPORTER_OTLP_TIMEOUT - Export timeout
  • OTEL_EXPORTER_OTLP_COMPRESSION - Compression algorithm (gzip, none)
  • OTEL_SERVICE_NAME - Service name
  • OTEL_RESOURCE_ATTRIBUTES - Additional resource attributes

The endpoint, headers, timeout, and compression variables (and their _METRICS_ variants) are read by the underlying OTLP exporters. The protocol variables are resolved by this package. OTEL_METRICS_EXPORTER and other autoexport-only variables are not consulted.

See OpenTelemetry Environment Variables for the complete list.

Advanced Usage

Custom gRPC Options
import (
    "google.golang.org/grpc/credentials"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
)

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolGRPC,
    EndpointURL: "http://collector.example.com:4317",
    GRPCOptions: []otlpmetricgrpc.Option{
        // Use TLS
        otlpmetricgrpc.WithTLSCredentials(
            credentials.NewClientTLSFromCert(certPool, ""),
        ),
        // Add authentication headers
        otlpmetricgrpc.WithHeaders(map[string]string{
            "Authorization": "Bearer " + apiKey,
        }),
        // Set timeout
        otlpmetricgrpc.WithTimeout(30 * time.Second),
    },
})
Custom HTTP Options
import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolHTTPProtobuf,
    EndpointURL: "https://collector.example.com:4318",
    HTTPOptions: []otlpmetrichttp.Option{
        // Add custom headers
        otlpmetrichttp.WithHeaders(map[string]string{
            "Authorization": "Bearer " + apiKey,
            "X-Custom-Header": "value",
        }),
        // Enable compression
        otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression),
        // Set timeout
        otlpmetrichttp.WithTimeout(30 * time.Second),
    },
})
Custom Resource Attributes
import (
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

res, err := resource.New(ctx,
    resource.WithAttributes(
        semconv.ServiceName("my-service"),
        semconv.ServiceVersion("1.0.0"),
        semconv.DeploymentEnvironment("production"),
    ),
    resource.WithFromEnv(),   // Also include env vars
    resource.WithHost(),       // Include host info
    resource.WithProcess(),    // Include process info
)

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolGRPC,
    EndpointURL: "http://localhost:4317",
    Resource: res,
})
Cloud Resource Detectors

OpenTelemetry provides resource detectors for major cloud providers that automatically detect and add cloud-specific metadata.

AWS Resource Detector
import (
    "go.opentelemetry.io/contrib/detectors/aws/ec2"
    "go.opentelemetry.io/contrib/detectors/aws/ecs"
    "go.opentelemetry.io/contrib/detectors/aws/eks"
    "go.opentelemetry.io/contrib/detectors/aws/lambda"
)

// Detect AWS EC2 instance metadata
res, err := resource.New(ctx,
    resource.WithDetectors(ec2.NewResourceDetector()),
    resource.WithAttributes(
        semconv.ServiceName("my-service"),
    ),
)

// Detected attributes include:
// - cloud.provider: "aws"
// - cloud.platform: "aws_ec2"
// - cloud.region: "us-west-2"
// - cloud.availability_zone: "us-west-2a"
// - cloud.account.id: "123456789012"
// - host.id: "i-0123456789abcdef0"
// - host.type: "t3.medium"

Install AWS detectors:

go get go.opentelemetry.io/contrib/detectors/aws/ec2
go get go.opentelemetry.io/contrib/detectors/aws/ecs
go get go.opentelemetry.io/contrib/detectors/aws/eks
go get go.opentelemetry.io/contrib/detectors/aws/lambda

ECS/Fargate:

res, err := resource.New(ctx,
    resource.WithDetectors(ecs.NewResourceDetector()),
    // Detects: container.id, aws.ecs.task.arn, aws.ecs.cluster.arn, etc.
)

EKS:

res, err := resource.New(ctx,
    resource.WithDetectors(eks.NewResourceDetector()),
    // Detects: k8s.cluster.name, cloud.provider, cloud.platform
)

Lambda:

res, err := resource.New(ctx,
    resource.WithDetectors(lambda.NewResourceDetector()),
    // Detects: faas.name, faas.version, cloud.region, etc.
)
GCP Resource Detector
import "go.opentelemetry.io/contrib/detectors/gcp"

res, err := resource.New(ctx,
    resource.WithDetectors(gcp.NewDetector()),
    resource.WithAttributes(
        semconv.ServiceName("my-service"),
    ),
)

// Detected attributes include:
// - cloud.provider: "gcp"
// - cloud.platform: "gcp_compute_engine"
// - cloud.region: "us-central1"
// - cloud.availability_zone: "us-central1-a"
// - host.id: "123456789"
// - host.type: "n1-standard-1"

Install:

go get go.opentelemetry.io/contrib/detectors/gcp
Azure Resource Detector
import "go.opentelemetry.io/contrib/detectors/azure/azurevm"

res, err := resource.New(ctx,
    resource.WithDetectors(azurevm.New()),
    resource.WithAttributes(
        semconv.ServiceName("my-service"),
    ),
)

// Detected attributes include:
// - cloud.provider: "azure"
// - cloud.platform: "azure_vm"
// - cloud.region: "eastus"
// - host.id: "..."
// - azure.vm.size: "Standard_D2s_v3"

Install:

go get go.opentelemetry.io/contrib/detectors/azure/azurevm
Multiple Detectors

Combine multiple detectors for comprehensive metadata:

import (
    "go.opentelemetry.io/contrib/detectors/aws/ec2"
    "go.opentelemetry.io/contrib/detectors/aws/eks"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

res, err := resource.New(ctx,
    // Service metadata
    resource.WithAttributes(
        semconv.ServiceName("my-api"),
        semconv.ServiceVersion("1.2.3"),
        semconv.DeploymentEnvironment("production"),
    ),
    // Cloud detectors (only one will succeed)
    resource.WithDetectors(
        ec2.NewResourceDetector(),
        eks.NewResourceDetector(),
    ),
    // Environment variables
    resource.WithFromEnv(),
    // Host and process info
    resource.WithHost(),
    resource.WithProcess(),
    resource.WithProcessRuntimeName(),
    resource.WithProcessRuntimeVersion(),
    // Container info (if applicable)
    resource.WithContainer(),
    resource.WithContainerID(),
    // OS info
    resource.WithOS(),
    // OTel SDK version
    resource.WithTelemetrySDK(),
)

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolGRPC,
    EndpointURL: "http://localhost:4317",
    Resource: res,
})

Note: Detectors are executed sequentially and only the first successful detector provides cloud metadata. For example, if running on AWS EC2, the EC2 detector will succeed and GCP/Azure detectors will be skipped.

Complete Example with AWS
package main

import (
    "context"
    "log"

    "github.com/segmentio/stats/v5"
    "github.com/segmentio/stats/v5/otlp"

    "go.opentelemetry.io/contrib/detectors/aws/ec2"
    "go.opentelemetry.io/contrib/detectors/aws/eks"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

func main() {
    ctx := context.Background()

    // Build resource with AWS detection
    res, err := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceName("payment-api"),
            semconv.ServiceVersion("2.1.0"),
            semconv.DeploymentEnvironment("production"),
        ),
        resource.WithDetectors(
            ec2.NewResourceDetector(),  // Detect EC2 metadata
            eks.NewResourceDetector(),  // Or EKS metadata
        ),
        resource.WithFromEnv(),
        resource.WithHost(),
        resource.WithProcess(),
        resource.WithContainer(),
    )
    if err != nil {
        log.Fatalf("failed to create resource: %v", err)
    }

    // Create handler with detected resources
    handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
        Protocol: otlp.ProtocolGRPC,
        EndpointURL: "http://collector.us-west-2.amazonaws.com:4317",
        Resource: res,
    })
    if err != nil {
        log.Fatalf("failed to create handler: %v", err)
    }
    defer handler.Shutdown(ctx)

    stats.Register(handler)
    defer stats.Flush()

    // Metrics will include all detected AWS metadata
    stats.Incr("payment.processed", stats.T("amount", "100"))
}
Multiple Handlers

Send metrics to multiple destinations:

// Send to local collector
localHandler, _ := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolGRPC,
    EndpointURL: "http://localhost:4317",
})

// Send to cloud service
cloudHandler, _ := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolHTTPProtobuf,
    EndpointURL: "https://api.example.com/v1/metrics",
    HTTPOptions: []otlpmetrichttp.Option{
        otlpmetrichttp.WithHeaders(map[string]string{
            "Authorization": "Bearer " + apiKey,
        }),
    },
})

// Register both
stats.Register(localHandler)
stats.Register(cloudHandler)

Testing with OpenTelemetry Collector

Using Docker
# Start an OpenTelemetry Collector
docker run -p 4317:4317 -p 4318:4318 \
    otel/opentelemetry-collector:latest
Collector Configuration

Example otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  logging:
    loglevel: debug
  prometheus:
    endpoint: 0.0.0.0:8889

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [logging, prometheus]

Metric Types

Counter

Cumulative metrics that only increase:

stats.Incr("requests.count")
stats.Add("bytes.sent", 1024)
Gauge

Point-in-time values that can go up or down:

stats.Set("connections.active", 42)
stats.Set("memory.usage", 1024*1024*500)

Gauges are implemented using OpenTelemetry's native Float64Gauge instrument, which records instantaneous values.

Histogram

Distribution of values:

stats.Observe("request.duration", 0.250)
stats.Observe("response.size", 4096)
Exponential Histograms

By default, histograms use explicit bucket aggregation with fixed bucket boundaries. For better accuracy and lower memory overhead, you can enable exponential histograms:

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol:             otlp.ProtocolGRPC,
    EndpointURL:          "http://localhost:4317",
    ExponentialHistogram: true,  // Enable exponential histograms
})

Benefits of exponential histograms:

  • Better accuracy across wide value ranges
  • Lower memory overhead (adaptive buckets)
  • No need to pre-define bucket boundaries
  • Native support in modern observability backends

Advanced configuration:

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol:                      otlp.ProtocolGRPC,
    EndpointURL:                   "http://localhost:4317",
    ExponentialHistogram:          true,
    ExponentialHistogramMaxSize:   160,  // Max buckets (default: 160)
    ExponentialHistogramMaxScale:  20,   // Max resolution (default: 20)
})
  • MaxSize: Maximum number of buckets (larger = more accuracy, more memory)
  • MaxScale: Resolution from -10 to 20 (higher = finer granularity)

Temporality (Cumulative vs Delta)

The handler uses cumulative temporality by default, which is compatible with Prometheus and most observability backends.

What is Temporality?
  • Cumulative: Counter values accumulate over time (e.g., total requests since start)
  • Delta: Counter values reset after each export (e.g., requests in last 10 seconds)
Default Behavior

By default, all metrics use cumulative temporality:

  • Counters: Report total count since application start
  • Histograms: Report cumulative distribution
  • UpDownCounters (Gauges): Report current absolute value

This matches Prometheus semantics and works with most OTLP backends.

Custom Temporality

For advanced use cases, you can configure custom temporality:

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolGRPC,
    EndpointURL: "http://localhost:4317",
    TemporalitySelector: sdkmetric.DeltaTemporalitySelector, // Use delta for all metrics
})

Available selectors:

  • sdkmetric.DefaultTemporalitySelector - Cumulative for all (default, recommended)
  • sdkmetric.CumulativeTemporalitySelector - Cumulative for all
  • sdkmetric.DeltaTemporalitySelector - Delta for all
  • sdkmetric.LowMemoryTemporalitySelector - Delta for Counters/Histograms, Cumulative for UpDownCounters

Note: Most users should use the default cumulative temporality. Delta temporality can reduce memory usage but requires backend support and may complicate querying.

Batching and Export Behavior

The handler uses native OpenTelemetry SDK batching via PeriodicReader:

  • Automatic batching: Metrics are aggregated in-memory and exported periodically
  • Default interval: 10 seconds (configurable via ExportInterval)
  • No manual buffering: All batching is handled by the OTel SDK
  • Immediate recording: stats.Incr(), stats.Set(), etc. record immediately but export is deferred
  • Manual flush: Call handler.Flush() to force immediate export (useful before shutdown)

Example configuration:

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol:       otlp.ProtocolGRPC,
    EndpointURL:    "http://localhost:4317",
    ExportInterval: 5 * time.Second,  // Export every 5 seconds
    ExportTimeout:  15 * time.Second, // 15 second timeout per export
})

How it works internally:

  1. When you call stats.Incr("requests"), the metric is recorded to an OTel instrument
  2. The OTel SDK aggregates all metrics in memory (e.g., summing counters, collecting histogram samples)
  3. Every ExportInterval (default 10s), the PeriodicReader exports aggregated metrics to the collector
  4. After export, aggregations reset for the next interval (except cumulative metrics like counters)

This means:

  • Metrics are not sent immediately on every call
  • Network overhead is minimized through batching
  • You can safely record thousands of metrics per second
  • Call Flush() before application shutdown to ensure all metrics are exported

Performance

The SDK handler is optimized for production use:

  • Instruments are created once and reused
  • Lock-free reads for instrument lookup
  • Minimal overhead per metric recording
  • Configurable export intervals to balance freshness vs overhead

Benchmark results on Apple M1:

BenchmarkSDKHandler_HandleMeasures-8   2000000   600 ns/op   0 allocs/op

Comparison with Legacy Handler

This package includes two handlers:

  1. SDKHandler (Recommended - New): Uses official OTel SDK

    • ✅ Full OTel SDK support
    • ✅ Both gRPC and HTTP
    • ✅ All environment variables
    • ✅ Resource detection
    • ✅ Production-ready
  2. Handler (Legacy): Custom OTLP implementation

    • ⚠️ Status: Alpha
    • Limited features
    • gRPC dependencies but no gRPC client
    • HTTP client only

We recommend using SDKHandler for all new projects.

Troubleshooting

Connection Refused
failed to create gRPC exporter: connection refused

Ensure the collector is running and accessible:

# Test gRPC endpoint
grpcurl -plaintext localhost:4317 list

# Test HTTP endpoint
curl http://localhost:4318/v1/metrics
Insecure gRPC

If using an insecure gRPC connection:

import "google.golang.org/grpc/credentials/insecure"

GRPCOptions: []otlpmetricgrpc.Option{
    otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials()),
}
Metrics Not Appearing
  1. Check export interval - metrics are batched
  2. Call handler.Flush() before shutdown
  3. Enable debug logging in your collector
  4. Verify resource attributes match your queries

Examples

See example_test.go for complete working examples including:

  • gRPC and HTTP configuration
  • Environment variable usage
  • Custom options and headers
  • Multiple handlers
  • Struct-based metrics

References

License

Same as the parent stats package.

Documentation

Overview

Example (FromEnv)

Example_fromEnv demonstrates using environment variables for configuration. This is the simplest approach and follows OpenTelemetry best practices.

package main

import (
	"context"
	"log"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	// Set environment variables before running:
	// export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
	// export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
	// export OTEL_SERVICE_NAME=my-service
	// export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.0.0

	ctx := context.Background()

	// Handler automatically reads all OTEL_* environment variables
	handler, err := otlp.NewSDKHandlerFromEnv(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	stats.Incr("app.started")
}
Example (FullyConfiguredByEnvironment)

Example_fullyConfiguredByEnvironment demonstrates relying entirely on OTEL environment variables without specifying any configuration in code. This provides maximum flexibility for deployment environments to control OpenTelemetry configuration without code changes.

package main

import (
	"context"
	"log"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	// The SDK will use these standard OpenTelemetry environment variables:
	//
	// Required/Common:
	//   OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 (full URL with scheme)
	//   OTEL_EXPORTER_OTLP_PROTOCOL=grpc (or http/protobuf)
	//   OTEL_SERVICE_NAME=my-service
	//
	// Optional:
	//   OTEL_EXPORTER_OTLP_HEADERS=key1=value1,key2=value2
	//   OTEL_EXPORTER_OTLP_TIMEOUT=30s
	//   OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
	//   OTEL_METRIC_EXPORT_INTERVAL=60s
	//   OTEL_METRIC_EXPORT_TIMEOUT=30s
	//
	// If no environment variables are set, the SDK uses these defaults:
	//   - Endpoint: http://localhost:4317 (gRPC) or http://localhost:4318 (HTTP)
	//   - Protocol: grpc
	//   - Export Interval: 60 seconds
	//   - Export Timeout: 30 seconds

	ctx := context.Background()

	// Pass an empty config - SDK will read all configuration from environment
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	// Your application code remains environment-agnostic
	stats.Incr("requests.count", stats.T("method", "GET"))
	stats.Observe("request.duration", 0.125)
}
Example (GRPC)

Example_gRPC demonstrates using the OpenTelemetry SDK handler with gRPC transport.

package main

import (
	"context"
	"log"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Create handler with gRPC transport
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolGRPC,
		EndpointURL: "http://localhost:4317",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	// Register with the default stats engine
	stats.Register(handler)
	defer stats.Flush()

	// Your application metrics will now be exported via gRPC
	stats.Incr("requests.count", stats.T("method", "GET"), stats.T("status", "200"))
	stats.Observe("request.duration", 0.250, stats.T("endpoint", "/api/users"))
}
Example (GRPCWithOptions)

Example_gRPCWithOptions demonstrates advanced gRPC configuration.

package main

import (
	"context"
	"log"
	"time"

	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Create handler with custom gRPC options
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolGRPC,
		EndpointURL: "http://localhost:4317",
		GRPCOptions: []otlpmetricgrpc.Option{
			otlpmetricgrpc.WithInsecure(),
			otlpmetricgrpc.WithTimeout(30 * time.Second),
			// For TLS:
			// otlpmetricgrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(certPool, "")),
			// For custom headers:
			// otlpmetricgrpc.WithHeaders(map[string]string{
			//     "Authorization": "Bearer token",
			// }),
		},
		ExportInterval: 10 * time.Second,
		ExportTimeout:  30 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	stats.Incr("requests.total")
}
Example (HTTP)

Example_hTTP demonstrates using the OpenTelemetry SDK handler with HTTP transport.

package main

import (
	"context"
	"log"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Create handler with HTTP transport
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolHTTPProtobuf,
		EndpointURL: "http://localhost:4318",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	// Register with the default stats engine
	stats.Register(handler)
	defer stats.Flush()

	// Your application metrics will now be exported via HTTP
	stats.Incr("requests.count")
}
Example (HTTPWithOptions)

Example_hTTPWithOptions demonstrates advanced HTTP configuration.

package main

import (
	"context"
	"log"
	"time"

	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Create handler with custom HTTP options
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolHTTPProtobuf,
		EndpointURL: "http://localhost:4318",
		HTTPOptions: []otlpmetrichttp.Option{
			otlpmetrichttp.WithInsecure(),
			otlpmetrichttp.WithTimeout(30 * time.Second),
			// For custom headers:
			// otlpmetrichttp.WithHeaders(map[string]string{
			//     "Authorization": "Bearer token",
			//     "X-Custom-Header": "value",
			// }),
			// For compression:
			// otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression),
		},
		ExportInterval: 10 * time.Second,
		ExportTimeout:  30 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	stats.Incr("requests.total")
}
Example (MultipleHandlers)

Example_multipleHandlers demonstrates using multiple handlers simultaneously.

package main

import (
	"context"
	"log"

	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Send metrics to both gRPC and HTTP endpoints
	grpcHandler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolGRPC,
		EndpointURL: "http://localhost:4317",
		GRPCOptions: []otlpmetricgrpc.Option{
			otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials()),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer grpcHandler.Shutdown(ctx)

	httpHandler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolHTTPProtobuf,
		EndpointURL: "http://localhost:4318",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer httpHandler.Shutdown(ctx)

	// Register both handlers
	stats.Register(grpcHandler)
	stats.Register(httpHandler)
	defer stats.Flush()

	// Metrics will be sent to both endpoints
	stats.Incr("requests.count")
}
Example (StructBased)

Example_structBased demonstrates using struct-based metrics with OpenTelemetry.

package main

import (
	"context"
	"log"
	"time"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:    otlp.ProtocolGRPC,
		EndpointURL: "http://localhost:4317",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	// Define metrics using struct tags
	type ServerMetrics struct {
		RequestCount    int           `metric:"requests.count" type:"counter"`
		ActiveConns     int           `metric:"connections.active" type:"gauge"`
		RequestDuration time.Duration `metric:"request.duration" type:"histogram"`
	}

	metrics := ServerMetrics{
		RequestCount:    100,
		ActiveConns:     50,
		RequestDuration: 250 * time.Millisecond,
	}

	// Report all metrics from the struct
	stats.Report(metrics, stats.T("server", "web-1"), stats.T("region", "us-west-2"))
}

Index

Examples

Constants

View Source
const (
	// DefaultMaxMetrics is the default maximum of metrics kept in memory
	// by the handler.
	DefaultMaxMetrics = 5000

	// DefaultFlushInterval is the default interval to flush the metrics
	// to the OpenTelemetry destination.
	//
	// Metrics will be flushed to the destination when DefaultFlushInterval or
	// DefaultMaxMetrics are reached, whichever comes first.
	DefaultFlushInterval = 10 * time.Second
)
View Source
const (
	// DefaultHistogramMaxSize is the default maximum number of buckets used for
	// exponential histograms when ExponentialHistogram is enabled.
	DefaultHistogramMaxSize int32 = 160

	// DefaultHistogramMaxScale is the default maximum scale (resolution) used
	// for exponential histograms when ExponentialHistogram is enabled.
	DefaultHistogramMaxScale int32 = 20
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Client deprecated

type Client interface {
	Handle(context.Context, *colmetricpb.ExportMetricsServiceRequest) error
}

Deprecated: Client is deprecated and will be removed in v6. It is only used by the deprecated Handler. Use SDKHandler instead.

type HTTPClient deprecated

type HTTPClient struct {
	// contains filtered or unexported fields
}

Deprecated: HTTPClient is deprecated and will be removed in v6. Use SDKHandler with ProtocolHTTPProtobuf instead, which provides the official OpenTelemetry SDK with retry logic, proper timeout handling, and full OTLP support.

Migration example:

// Old (deprecated)
client := otlp.NewHTTPClient("http://localhost:4318/v1/metrics")
handler := &otlp.Handler{Client: client}

// New (recommended)
handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolHTTPProtobuf,
    EndpointURL: "http://localhost:4318",
})

HTTPClient implements the Client interface and is used to export metrics to an OpenTelemetry Collector through the HTTP interface.

The current implementation is a fire and forget approach where we do not retry or buffer any failed-to-flush data on the client.

func NewHTTPClient deprecated

func NewHTTPClient(endpoint string) *HTTPClient

Deprecated: NewHTTPClient is deprecated. Use SDKHandler with ProtocolHTTPProtobuf instead. See HTTPClient documentation for migration example.

func (*HTTPClient) Handle

type Handler deprecated

type Handler struct {
	Client        Client
	Context       context.Context
	FlushInterval time.Duration
	MaxMetrics    int
	// contains filtered or unexported fields
}

Deprecated: Handler is deprecated and will be removed in v6. Use SDKHandler instead, which provides the official OpenTelemetry SDK with full gRPC and HTTP support, environment variable configuration, and automatic resource detection.

Migration example:

// Old (deprecated)
handler := &otlp.Handler{
    Client: otlp.NewHTTPClient("http://localhost:4318"),
}

// New (recommended)
handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolHTTPProtobuf,
    EndpointURL: "http://localhost:4318",
})

Handler implements stats.Handler to forward metrics to an OpenTelemetry destination. Usually an OpenTelemetry Collector.

With the current implementation this Handler is targeting a Prometheus based backend or any backend expecting cumulative values.

This Handler leverages a doubly linked list with a map to implement a ring buffer with a lookup to ensure a low memory usage.

func NewHandler

func NewHandler(ctx context.Context, endpoint string) *Handler

NewHandler return an instance of Handler with the default client, flush interval and in-memory metrics limit.

func (*Handler) HandlerMeasure

func (h *Handler) HandlerMeasure(t time.Time, measures ...stats.Measure)

type Protocol

type Protocol string

Protocol defines the transport protocol for OTLP export.

const (
	// ProtocolGRPC uses gRPC transport.
	ProtocolGRPC Protocol = "grpc"
	// ProtocolHTTPProtobuf uses HTTP with protobuf encoding.
	ProtocolHTTPProtobuf Protocol = "http/protobuf"
)

type SDKConfig

type SDKConfig struct {
	// Protocol specifies the transport protocol (grpc or http/protobuf).
	// If empty, the OTEL_EXPORTER_OTLP_METRICS_PROTOCOL and
	// OTEL_EXPORTER_OTLP_PROTOCOL environment variables are consulted (in that
	// order), defaulting to gRPC when neither is set. An unrecognized
	// environment value causes NewSDKHandler to return an error.
	Protocol Protocol

	// EndpointURL specifies the full OTLP endpoint URL.
	//
	// Note: this is deliberately "EndpointURL", not "Endpoint". The underlying
	// exporters expose both WithEndpoint (host:port, no scheme) and
	// WithEndpointURL (full URL with scheme), and the two are easy to confuse.
	// This handler always uses WithEndpointURL, so the value MUST include the
	// scheme (http:// or https://).
	// For gRPC: "http://localhost:4317" or "https://api.example.com:4317"
	// For HTTP: "http://localhost:4318" or "https://api.example.com:4318"
	// If empty, uses OTEL_EXPORTER_OTLP_ENDPOINT environment variable
	// or SDK defaults (http://localhost:4317 for gRPC, http://localhost:4318 for HTTP)
	EndpointURL string

	// Resource specifies the resource attributes for all metrics.
	// If nil, a resource is built from the SDK defaults: environment
	// (OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME), telemetry SDK, host, and
	// process attributes. Cloud and Kubernetes detection is not included by
	// default; supply a Resource built with the relevant
	// go.opentelemetry.io/contrib/detectors/* packages to add it.
	Resource *resource.Resource

	// ExportInterval specifies how often to export metrics
	// If zero or not set, uses the SDK default (60 seconds)
	ExportInterval time.Duration

	// ExportTimeout specifies the maximum amount of time to wait for a single
	// export request to the server to complete. This is distinct from
	// ExportInterval, which controls how often exports happen.
	// If zero or not set, uses the SDK default (30 seconds)
	ExportTimeout time.Duration

	// HTTPOptions are additional options for HTTP protocol.
	// Only used when Protocol is ProtocolHTTPProtobuf.
	// See the available options at
	// https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp#Option
	HTTPOptions []otlpmetrichttp.Option

	// GRPCOptions are additional options for gRPC protocol.
	// Only used when Protocol is ProtocolGRPC.
	// See the available options at
	// https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc#Option
	GRPCOptions []otlpmetricgrpc.Option

	// ExponentialHistogram enables exponential histogram aggregation for histogram metrics.
	// When true, histograms use base-2 exponential buckets which provide better accuracy
	// and lower memory overhead compared to explicit bucket histograms.
	// Default: false (uses explicit bucket histograms)
	ExponentialHistogram bool

	// ExponentialHistogramMaxSize sets the maximum number of buckets for exponential histograms.
	// Larger values provide better accuracy but use more memory.
	// Default: DefaultHistogramMaxSize (if ExponentialHistogram is true)
	// Ignored if ExponentialHistogram is false
	ExponentialHistogramMaxSize int32

	// ExponentialHistogramMaxScale sets the maximum scale (resolution) for exponential histograms.
	// Higher values provide finer bucket granularity.
	// Valid range: -10 to 20
	// Default: DefaultHistogramMaxScale (if ExponentialHistogram is true)
	// Ignored if ExponentialHistogram is false
	ExponentialHistogramMaxScale int32

	// TemporalitySelector determines the temporality (cumulative vs delta) for each instrument kind.
	// If nil, uses DefaultTemporalitySelector which returns CumulativeTemporality for all instruments.
	// This is recommended for Prometheus and most OTLP backends.
	//
	// Available selectors:
	//   - sdkmetric.DefaultTemporalitySelector: Cumulative for all (default, Prometheus-compatible)
	//   - sdkmetric.CumulativeTemporalitySelector: Cumulative for all
	//   - sdkmetric.DeltaTemporalitySelector: Delta for all
	//   - sdkmetric.LowMemoryTemporalitySelector: Delta for Counters/Histograms, Cumulative for UpDownCounters
	TemporalitySelector sdkmetric.TemporalitySelector
}

SDKConfig contains configuration for the OpenTelemetry SDK handler.

type SDKHandler

type SDKHandler struct {
	// contains filtered or unexported fields
}

SDKHandler implements stats.Handler using the official OpenTelemetry SDK. It bridges stats metrics to OTel metrics and supports both HTTP and gRPC transports.

This handler supports all standard OpenTelemetry environment variables:

  • OTEL_EXPORTER_OTLP_ENDPOINT
  • OTEL_EXPORTER_OTLP_PROTOCOL (grpc, http/protobuf)
  • OTEL_EXPORTER_OTLP_HEADERS
  • OTEL_EXPORTER_OTLP_TIMEOUT
  • OTEL_RESOURCE_ATTRIBUTES
  • OTEL_SERVICE_NAME
  • And more...

Example usage:

handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
    Protocol: otlp.ProtocolGRPC,
    EndpointURL: "http://localhost:4317",
})
if err != nil {
    log.Fatal(err)
}
defer handler.Shutdown(ctx)
stats.Register(handler)
Example (ExponentialHistogram)
package main

import (
	"context"
	"log"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Create handler with exponential histogram support
	// Exponential histograms provide better accuracy and lower memory overhead
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:             otlp.ProtocolGRPC,
		EndpointURL:          "http://localhost:4317",
		ExponentialHistogram: true, // Enable exponential histograms
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	// Record histogram metrics - these will use exponential bucket aggregation
	stats.Observe("api.latency", 0.125, stats.T("endpoint", "/users"))
	stats.Observe("api.latency", 0.250, stats.T("endpoint", "/users"))
	stats.Observe("api.latency", 0.500, stats.T("endpoint", "/users"))

	// Exponential histograms automatically adapt to the value range
	// providing consistent accuracy without pre-defined bucket boundaries
	stats.Observe("db.query.duration", 0.001, stats.T("query", "SELECT"))
	stats.Observe("db.query.duration", 0.050, stats.T("query", "SELECT"))
	stats.Observe("db.query.duration", 1.500, stats.T("query", "SELECT"))
}
Example (ExponentialHistogramAdvanced)
package main

import (
	"context"
	"log"

	"github.com/segmentio/stats/v5"
	"github.com/segmentio/stats/v5/otlp"
)

func main() {
	ctx := context.Background()

	// Advanced exponential histogram configuration
	handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
		Protocol:                     otlp.ProtocolGRPC,
		EndpointURL:                  "http://localhost:4317",
		ExponentialHistogram:         true,
		ExponentialHistogramMaxSize:  160, // Max buckets (higher = more accuracy)
		ExponentialHistogramMaxScale: 20,  // Max resolution (higher = finer granularity)
	})
	if err != nil {
		log.Fatal(err)
	}
	defer handler.Shutdown(ctx)

	stats.Register(handler)
	defer stats.Flush()

	// Record response time metrics across wide value ranges
	// Exponential histograms handle this efficiently
	for _, duration := range []float64{0.001, 0.010, 0.100, 1.000, 10.000} {
		stats.Observe("response.time", duration, stats.T("service", "api"))
	}
}

func NewSDKHandler

func NewSDKHandler(ctx context.Context, config SDKConfig) (*SDKHandler, error)

NewSDKHandler creates a new handler using the OpenTelemetry SDK. It builds a resource from the SDK defaults (environment, telemetry SDK, host, and process attributes) and supports the standard OTEL environment variables.

func NewSDKHandlerFromEnv

func NewSDKHandlerFromEnv(ctx context.Context) (*SDKHandler, error)

NewSDKHandlerFromEnv creates a handler using only environment variables. This is the simplest way to create a handler with full OpenTelemetry support.

It respects all standard OTEL environment variables including:

  • OTEL_EXPORTER_OTLP_ENDPOINT (full URL with scheme, e.g., http://localhost:4317)
  • OTEL_EXPORTER_OTLP_PROTOCOL (grpc or http/protobuf)
  • OTEL_EXPORTER_OTLP_HEADERS
  • OTEL_RESOURCE_ATTRIBUTES
  • OTEL_SERVICE_NAME

func (*SDKHandler) Flush

func (h *SDKHandler) Flush()

Flush implements stats.Flusher.

func (*SDKHandler) HandleMeasures

func (h *SDKHandler) HandleMeasures(_ time.Time, measures ...stats.Measure)

HandleMeasures implements stats.Handler.

func (*SDKHandler) Shutdown

func (h *SDKHandler) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the handler and exports any remaining metrics.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL