Back to Monitoring & Observability Series

Prometheus Deep Dive Part 15: OpenTelemetry & the Future of Prometheus

June 15, 2026 Wasil Zafar 30 min read

Prometheus and OpenTelemetry are converging. Explore native OTLP ingestion in Prometheus 3.x, native histograms that eliminate bucket explosion, the OpenTelemetry Collector as a universal metrics pipeline, and how unified observability reshapes the monitoring landscape.

Table of Contents

  1. The Convergence
  2. OTLP Native Ingestion
  3. Native Histograms
  4. OpenTelemetry Collector
  5. Prometheus 3.x Features
  6. Migration Strategies
  7. The Future Landscape
  8. Conclusion & Series Wrap-Up

The Convergence

Prometheus and OpenTelemetry started as separate projects with different models — pull vs push, metrics-focused vs multi-signal. Today, they’re converging:

Prometheus & OpenTelemetry Convergence Timeline
timeline
    title Prometheus + OpenTelemetry Convergence
    2012 : Prometheus created at SoundCloud
    2015 : Prometheus open-sourced
    2019 : OpenTelemetry formed (OpenTracing + OpenCensus merge)
    2021 : OTel Metrics GA
         : Prometheus remote_write receiver
    2023 : Native Histograms (experimental)
         : OTLP write receiver (experimental)
    2024 : Prometheus 3.0 released
         : Native OTLP ingestion (stable)
         : UTF-8 metric names
    2025 : OpenTelemetry Collector replaces custom receivers
         : Exemplars link metrics to traces
    2026 : Native histograms widespread
         : Unified observability standard
                            
Key Insight: OpenTelemetry is the future of instrumentation (how applications emit telemetry). Prometheus is the future of metrics storage and querying (how you store and alert on metrics). They’re complementary, not competing. You instrument with OTel SDKs and store in Prometheus.

OTLP Native Ingestion

Prometheus 3.x supports native OTLP ingestion — applications can push metrics directly to Prometheus using the OpenTelemetry Protocol without needing a Collector or remote_write adapter:

# prometheus.yml — Enable OTLP receiver (Prometheus 3.x)
otlp:
  # Enable OTLP gRPC endpoint
  protocols:
    grpc:
      endpoint: 0.0.0.0:4317
    http:
      endpoint: 0.0.0.0:4318

  # Translation settings
  resource_to_telemetry_conversion:
    enabled: true    # Map OTel resource attributes to Prometheus labels

  # Promote resource attributes to metric labels
  promote_resource_attributes:
    - service.name
    - service.namespace
    - deployment.environment
# Application instrumented with OpenTelemetry SDK
# Sends metrics directly to Prometheus via OTLP

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter

# Configure OTLP exporter pointing at Prometheus
exporter = OTLPMetricExporter(
    endpoint="http://prometheus:4317",
    insecure=True
)

reader = PeriodicExportingMetricReader(exporter, export_interval_millis=15000)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)

# Create metrics
meter = metrics.get_meter("payment-service", "1.0.0")

request_counter = meter.create_counter(
    "http.server.request.count",
    description="Total HTTP requests",
    unit="1"
)

request_duration = meter.create_histogram(
    "http.server.request.duration",
    description="HTTP request duration",
    unit="s"
)

# Use in application code
request_counter.add(1, {"http.method": "POST", "http.status_code": "200"})
request_duration.record(0.045, {"http.method": "POST", "http.route": "/api/payments"})

Native Histograms

Native histograms (also called exponential histograms) solve the classic Prometheus histogram problem — bucket explosion. Instead of pre-defined buckets that multiply cardinality, native histograms store a single time series with dynamic resolution:

Comparison

Classic vs Native Histograms

AspectClassic HistogramNative Histogram
Time series per metricN+2 (N buckets + sum + count)1 (single series)
With 10 label combos10 × (20+2) = 220 series10 series
Bucket selectionManual, static, must predict distributionAutomatic, dynamic, adapts to data
Quantile accuracyDepends on bucket placementConfigurable resolution (schema)
Storage efficiencyPoor (many mostly-empty buckets)Excellent (compact encoding)
PromQLhistogram_quantile()histogram_quantile() (same!)
PerformanceCardinality
# Enable native histograms in Prometheus 3.x
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'my-service'
    # Scrape native histograms from /metrics endpoint
    scrape_classic_histograms: false    # Don't also scrape classic format
    native_histogram_bucket_limit: 256  # Max bucket count per histogram
    static_configs:
      - targets: ['my-service:8080']
// Go application exposing native histograms
package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
    Name: "http_request_duration_seconds",
    Help: "HTTP request duration",
    // Native histogram config (replaces Buckets field)
    NativeHistogramBucketFactor:     1.1,   // ~10% bucket width
    NativeHistogramMaxBucketNumber:  160,
    NativeHistogramMinResetDuration: 1 * time.Hour,
})

func init() {
    prometheus.MustRegister(requestDuration)
}

func handler(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    // ... handle request ...
    requestDuration.Observe(time.Since(start).Seconds())
}

func main() {
    http.Handle("/metrics", promhttp.Handler())
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

OpenTelemetry Collector

The OpenTelemetry Collector acts as a vendor-agnostic telemetry pipeline — receiving, processing, and exporting metrics from any source to any destination:

OpenTelemetry Collector Pipeline
flowchart LR
    subgraph Sources["Sources"]
        A1[App OTLP]
        A2[Prometheus
Scrape] A3[StatsD] A4[Host Metrics] end subgraph Collector["OTel Collector"] R[Receivers] P[Processors] E[Exporters] R --> P --> E end subgraph Destinations["Destinations"] D1[Prometheus] D2[Grafana Mimir] D3[Datadog] D4[OTLP Backend] end A1 & A2 & A3 & A4 --> R E --> D1 & D2 & D3 & D4
# otel-collector-config.yaml
receivers:
  # Receive OTLP from applications
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  # Scrape Prometheus endpoints (replaces Prometheus server for collection)
  prometheus:
    config:
      scrape_configs:
        - job_name: 'kubernetes-pods'
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
              action: keep
              regex: true

  # Host metrics (replaces Node Exporter)
  hostmetrics:
    collection_interval: 15s
    scrapers:
      cpu: {}
      memory: {}
      disk: {}
      network: {}
      filesystem: {}

processors:
  # Add Kubernetes metadata
  k8sattributes:
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.pod.name
        - k8s.deployment.name

  # Batch for efficiency
  batch:
    send_batch_size: 10000
    timeout: 10s

  # Memory limiter (prevent OOM)
  memory_limiter:
    check_interval: 1s
    limit_mib: 2048
    spike_limit_mib: 512

  # Filter unwanted metrics
  filter:
    metrics:
      exclude:
        match_type: regexp
        metric_names:
          - "go_.*"
          - "process_.*"

exporters:
  # Send to Prometheus via remote write
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write
    resource_to_telemetry_conversion:
      enabled: true

  # Or send to Prometheus via OTLP (Prometheus 3.x)
  otlp/prometheus:
    endpoint: prometheus:4317
    tls:
      insecure: true

  # Also send to Grafana Cloud
  otlphttp/grafana:
    endpoint: https://otlp-gateway-prod-us-east-0.grafana.net/otlp
    headers:
      Authorization: "Basic ${GRAFANA_CLOUD_TOKEN}"

service:
  pipelines:
    metrics:
      receivers: [otlp, prometheus, hostmetrics]
      processors: [memory_limiter, k8sattributes, filter, batch]
      exporters: [prometheusremotewrite, otlphttp/grafana]

Prometheus 3.x Features

Prometheus 3.x

Key Features in Prometheus 3.x

FeatureImpactStatus
Native OTLP ingestionPush metrics directly without adaptersStable
Native histograms1 series instead of N+2 per histogramStable
UTF-8 metric namesSupport dots and other characters in namesStable
New UIRebuilt from scratch with modern UXStable
Remote write 2.0Protobuf with metadata, ~50% less bandwidthExperimental
Created timestampsTrack when a metric was first scrapedStable
ExemplarsLink metric samples to trace IDsStable
Prometheus 32024-2026
# UTF-8 metric names — OTel conventions work natively
# Before (Prometheus convention): http_server_request_duration_seconds
# After (OTel convention): http.server.request.duration

# Prometheus 3.x accepts both formats
# PromQL works with either:
# rate(http.server.request.duration_count[5m])
# rate(http_server_request_duration_seconds_count[5m])
# Exemplars — linking metrics to traces
# When querying, exemplars show trace_id for specific samples

# In Grafana: click a data point → "View trace" jumps to Tempo/Jaeger
# In PromQL: query returns exemplars alongside regular data

# Application exposes exemplars with metrics
# Go example:
# httpDuration.WithLabelValues("200").
#     (Exemplar(prometheus.Labels{"trace_id": traceID}, duration))

# Query with exemplars API:
curl 'http://prometheus:9090/api/v1/query_exemplars' \
  --data-urlencode 'query=http_request_duration_seconds_bucket' \
  --data-urlencode 'start=2026-06-15T00:00:00Z' \
  --data-urlencode 'end=2026-06-15T01:00:00Z'

Migration Strategies

Migration Path: Prometheus Pull → OTel + Prometheus
  1. Phase 1: Deploy OTel Collector alongside existing Prometheus. Collector scrapes the same targets (dual-read).
  2. Phase 2: Instrument new services with OTel SDK. Route OTLP to Collector → Prometheus.
  3. Phase 3: Migrate existing services to OTel SDK. Remove Prometheus client libraries.
  4. Phase 4: Use Collector as the single scraper. Prometheus receives via OTLP/remote_write only.
# Phase 1: Dual collection — Collector mirrors Prometheus scraping
# Both Prometheus and Collector scrape the same targets
# Compare results to validate before switching

receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: 'dual-read-validation'
          static_configs:
            - targets: ['service-a:8080', 'service-b:8080']
          # Add a label to distinguish collector-scraped data
          relabel_configs:
            - target_label: __collector__
              replacement: otel

exporters:
  prometheusremotewrite:
    endpoint: http://prometheus-validation:9090/api/v1/write

The Future Landscape

Where Things Are Heading:
  • Instrumentation: OpenTelemetry SDKs become the default (replacing Prometheus client libraries)
  • Collection: OTel Collector replaces custom exporters and relabeling logic
  • Storage: Prometheus TSDB remains best-in-class for metrics (PromQL is irreplaceable)
  • Correlation: Exemplars and trace-to-metrics linking become standard
  • Multi-signal: Same pipeline carries metrics, traces, and logs (three pillars unified)
  • Standards: OTLP becomes the wire format; Prometheus exposition format lives on for /metrics endpoints

Conclusion & Series Wrap-Up

Prometheus Deep Dive Series Complete! Over 15 parts, we’ve covered:
  • Parts 1–3: Architecture, PromQL, and instrumentation fundamentals
  • Parts 4–6: Recording rules, service discovery, and alerting
  • Parts 7–8: Scaling (sharding, federation, HA) and performance tuning
  • Parts 9–10: Node Exporter deep dive and remote storage systems
  • Parts 11–12: Thanos for long-term storage and Jsonnet for config-as-code
  • Parts 13–14: CI/CD pipelines and SLO-based alerting
  • Part 15: OpenTelemetry convergence and the future
Final Takeaways:
  • OTel is the future of instrumentation — adopt OTel SDKs for new services
  • Prometheus is the future of metrics — PromQL and TSDB remain unmatched
  • Native histograms are a game-changer — eliminate cardinality explosion from latency metrics
  • The Collector is a universal pipeline — receive anything, process, export anywhere
  • Exemplars bridge metrics and traces — click a spike, jump to the trace
  • Migrate incrementally — OTel and Prometheus coexist perfectly during transition