Skip to main content
The dedicated opentelemetry-prometheus crate has been discontinued as of version 0.29. It depends on an unmaintained protobuf crate with known security vulnerabilities.
For Prometheus integration, use the OTLP exporter instead. Prometheus natively supports the OTLP protocol, providing a more stable, secure, and actively maintained solution.

Why OTLP?

  • Active maintenance: Part of the OpenTelemetry specification
  • Security: No dependency on unmaintained libraries
  • Native support: Prometheus 2.47+ has built-in OTLP receiver
  • Consistency: Same exporter works with multiple backends
  • Future-proof: Official OpenTelemetry standard

Quick Start

1. Run Prometheus with OTLP Support

Create a minimal prometheus.yml configuration:
global:
  scrape_interval: 15s
Run Prometheus with OTLP receiver enabled:
docker run -p 9090:9090 \
  -v ./prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --web.enable-otlp-receiver
Prometheus is now ready to accept OTLP metrics at: http://localhost:9090/api/v1/otlp/v1/metrics

2. Configure Your Application

Add dependencies to Cargo.toml:
[dependencies]
opentelemetry = "0.31"
opentelemetry_sdk = "0.31"
opentelemetry-otlp = { version = "0.31", features = ["http-proto", "metrics"] }

3. Export Metrics to Prometheus

use opentelemetry::global;
use opentelemetry::metrics::Meter;
use opentelemetry::KeyValue;
use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::Resource;

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    // Create OTLP metric exporter configured for Prometheus
    let exporter = MetricExporter::builder()
        .with_http()
        .with_protocol(Protocol::HttpBinary)
        .with_endpoint("http://localhost:9090/api/v1/otlp/v1/metrics")
        .build()?;

    // Create meter provider
    let meter_provider = SdkMeterProvider::builder()
        .with_periodic_exporter(exporter)
        .with_resource(
            Resource::builder()
                .with_service_name("my-service")
                .build()
        )
        .build();

    global::set_meter_provider(meter_provider.clone());

    // Get a meter
    let meter = global::meter("my-meter");

    // Create and use metrics
    let counter = meter
        .u64_counter("http_requests_total")
        .with_description("Total HTTP requests")
        .with_unit("requests")
        .build();

    counter.add(1, &[KeyValue::new("method", "GET")]);
    counter.add(1, &[KeyValue::new("method", "POST")]);

    // Shutdown to flush metrics
    meter_provider.shutdown()?;

    Ok(())
}

4. Query Metrics in Prometheus

Open the Prometheus web UI at http://localhost:9090 and query:
http_requests_total
You should see your metrics with labels.

Complete Example with Multiple Metric Types

use opentelemetry::global;
use opentelemetry::KeyValue;
use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::Resource;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    // Initialize meter provider
    let exporter = MetricExporter::builder()
        .with_http()
        .with_protocol(Protocol::HttpBinary)
        .with_endpoint("http://localhost:9090/api/v1/otlp/v1/metrics")
        .build()?;

    let meter_provider = SdkMeterProvider::builder()
        .with_periodic_exporter(exporter)
        .with_resource(
            Resource::builder()
                .with_service_name("metrics-demo")
                .build()
        )
        .build();

    global::set_meter_provider(meter_provider.clone());

    let meter = global::meter("example-meter");

    // Counter - monotonically increasing value
    let request_counter = meter
        .u64_counter("http_requests_total")
        .with_description("Total number of HTTP requests")
        .with_unit("requests")
        .build();

    // Histogram - distribution of values
    let request_duration = meter
        .f64_histogram("http_request_duration_seconds")
        .with_description("HTTP request duration")
        .with_unit("s")
        .build();

    // Observable Gauge - current value at collection time
    let active_connections = meter
        .u64_observable_gauge("active_connections")
        .with_description("Number of active connections")
        .with_unit("connections")
        .build();

    // Simulate application metrics
    for i in 0..10 {
        // Record requests
        request_counter.add(1, &[
            KeyValue::new("method", "GET"),
            KeyValue::new("endpoint", "/api/users"),
        ]);

        // Record request duration
        request_duration.record(0.150 + (i as f64 * 0.01), &[
            KeyValue::new("method", "GET"),
            KeyValue::new("status", "200"),
        ]);

        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    // Shutdown to export final metrics
    meter_provider.shutdown()?;

    Ok(())
}

Migration from Old Prometheus Exporter

If you’re migrating from the discontinued opentelemetry-prometheus crate:

Before (Old Approach)

use opentelemetry_prometheus::exporter;
use prometheus::{Registry, TextEncoder};

let registry = Registry::new();
let exporter = exporter()
    .with_registry(registry.clone())
    .build()?;

let provider = SdkMeterProvider::builder()
    .with_reader(exporter)
    .build();

After (New Approach with OTLP)

use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};

let exporter = MetricExporter::builder()
    .with_http()
    .with_protocol(Protocol::HttpBinary)
    .with_endpoint("http://localhost:9090/api/v1/otlp/v1/metrics")
    .build()?;

let provider = SdkMeterProvider::builder()
    .with_periodic_exporter(exporter)
    .build();

Metric Types Mapping

OpenTelemetry metrics map to Prometheus as follows:
OpenTelemetryPrometheusUse Case
CounterCounterMonotonically increasing values (requests, errors)
HistogramHistogramDistribution of values (latency, response sizes)
ObservableGaugeGaugePoint-in-time values (CPU usage, memory)
UpDownCounterGaugeValues that go up and down (queue length)

Naming Conventions

Follow Prometheus naming best practices:
// Counter - use _total suffix
let counter = meter.u64_counter("http_requests_total").build();

// Histogram - use base unit suffix
let histogram = meter.f64_histogram("http_request_duration_seconds").build();

// Gauge - descriptive name
let gauge = meter.u64_observable_gauge("active_connections").build();
Naming rules:
  • Use snake_case
  • Include unit suffix (_seconds, _bytes, _total)
  • Be descriptive and specific
  • Avoid redundant prefixes

Configuration

Custom Export Interval

By default, metrics are exported every 60 seconds. Customize this:
use opentelemetry_sdk::metrics::PeriodicReaderBuilder;
use std::time::Duration;

let reader = PeriodicReaderBuilder::new(exporter)
    .with_interval(Duration::from_secs(30))  // Export every 30 seconds
    .build();

let provider = SdkMeterProvider::builder()
    .with_reader(reader)
    .build();

Resource Attributes

Add resource attributes that appear as labels:
use opentelemetry::KeyValue;
use opentelemetry_sdk::Resource;

let resource = Resource::builder()
    .with_service_name("my-service")
    .with_service_version("1.0.0")
    .with_attributes([
        KeyValue::new("environment", "production"),
        KeyValue::new("region", "us-west-2"),
    ])
    .build();

let provider = SdkMeterProvider::builder()
    .with_periodic_exporter(exporter)
    .with_resource(resource)
    .build();

Production Deployment

Using OpenTelemetry Collector

For production, use the OpenTelemetry Collector as an intermediary:
# collector-config.yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [prometheus]
Run the collector:
docker run -p 4318:4318 -p 8889:8889 \
  -v ./collector-config.yaml:/etc/otel-collector-config.yaml \
  otel/opentelemetry-collector:latest \
  --config=/etc/otel-collector-config.yaml
Configure Prometheus to scrape the collector:
# prometheus.yml
scrape_configs:
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['localhost:8889']
Update your application to send to the collector:
let exporter = MetricExporter::builder()
    .with_http()
    .with_endpoint("http://localhost:4318")  // Collector endpoint
    .build()?;
Benefits:
  • Buffering and retry logic
  • Multiple backend support
  • Metric transformation and filtering
  • Service discovery integration

Troubleshooting

Metrics Not Appearing

  1. Verify Prometheus OTLP receiver is enabled:
    curl http://localhost:9090/api/v1/status/config | grep otlp
    
  2. Check endpoint URL:
    • Must include full path: /api/v1/otlp/v1/metrics
    • Default: http://localhost:9090/api/v1/otlp/v1/metrics
  3. Ensure shutdown is called:
    meter_provider.shutdown()?;  // Flushes pending metrics
    

Missing Labels

Ensure attributes are added when recording metrics:
counter.add(1, &[KeyValue::new("label_name", "label_value")]);

Version Compatibility

Prometheus OTLP support requires:
  • Prometheus 2.47.0 or later
  • OpenTelemetry Rust SDK 0.20.0 or later

Legacy Prometheus Exporter (Deprecated)

This section is for reference only. The prometheus exporter is deprecated and should not be used in new projects.
The old opentelemetry-prometheus crate provided a pull-based exporter that exposed metrics via an HTTP endpoint. This approach is no longer recommended. For pull-based metrics, use the OpenTelemetry Collector with the Prometheus exporter as shown in the Production Deployment section.

Next Steps

OTLP Exporter

Learn more about OTLP configuration

Metrics Guide

Comprehensive guide to metrics in Rust

Build docs developers (and LLMs) love