The Prometheus and StatsD receivers in OpenTelemetry Contrib are essentially adapters, translating metrics from their native formats into OpenTelemetry’s internal representation, making them available for processing and export.

Let’s see them in action. Imagine you have a Node.js application exposing metrics via Prometheus.

const express = require('express');
const client = require('prom-client');

const app = express();
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });

const httpRequestCounter = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests received',
  labelNames: ['method', 'path', 'status_code']
});

app.get('/metrics', async (req, res) => {
  res.setHeader('Content-Type', client.register.contentType);
  res.end(await client.register.metrics());
});

app.get('/', (req, res) => {
  httpRequestCounter.inc({ method: req.method, path: req.path, status_code: 200 });
  res.send('Hello, World!');
});

app.listen(8080, () => {
  console.log('Server listening on port 8080');
});

And you also have a StatsD daemon running, receiving metrics from another service.

# Example of a Python service sending StatsD metrics
import statsd

client = statsd.StatsClient('localhost', 8125, prefix='myapp')

for _ in range(10):
    client.incr('login_attempts')
    client.gauge('active_users', 150)
    client.timer('request_latency', 250).send()

Now, you configure an OpenTelemetry Collector to receive these metrics.

receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: 'my-node-app'
          static_configs:
            - targets: ['localhost:8080']
          metrics_path: '/metrics'
  statsd:
    protocols:
      udp:
        listen_address: "0.0.0.0:8125"

The prometheus receiver, configured with scrape_configs, tells the collector to periodically poll the /metrics endpoint on localhost:8080. It understands the Prometheus exposition format (text-based key-value pairs with labels) and translates each metric line into an OpenTelemetry Metric data point. For instance, a line like http_requests_total{method="GET",path="/",status_code="200"} 1 becomes a Counter metric with labels.

The statsd receiver, listening on UDP port 8125, parses the StatsD protocol. This protocol is a simpler, UDP-based text format. When it receives myapp.login_attempts:1|c, it understands it as a counter increment for myapp.login_attempts. Similarly, myapp.active_users:150|g is parsed as a gauge, and myapp.request_latency:250|ms as a timer. The receiver then converts these into their corresponding OpenTelemetry metric types.

Both receivers then pass these translated metrics to the collector’s pipeline. This allows you to then process them further (e.g., with a filter processor to keep only specific metrics) or export them to various backends (like Prometheus, Jaeger, or cloud-specific monitoring services) using different exporters. The key is that the receivers abstract away the complexity of the original metric formats, presenting a unified view to the rest of the OpenTelemetry pipeline.

The most surprising truth about these receivers is how seamlessly they can integrate disparate metric systems into a single observability data stream, often without requiring changes to the applications generating the metrics themselves. They act as universal translators, but the underlying implementation details of how they map specific metric types and labels can sometimes lead to subtle differences in how data appears downstream. For example, the Prometheus receiver directly scrapes metrics, while the StatsD receiver listens for UDP packets; this difference in interaction model means that network issues or packet loss can affect StatsD data more directly than scraped Prometheus metrics, though Prometheus scrape failures are also a common issue.

When a Prometheus metric has a very long label value, or a label value contains characters that are not valid in an OpenTelemetry metric attribute (like spaces or certain punctuation), the Prometheus receiver might sanitize or drop that label. This means a metric you expect with a specific label might arrive without it, or with a modified version, which can be confusing if you’re not aware of the sanitization rules. The StatsD receiver, due to the UDP nature, can also lose metrics if the network is congested or the collector is overwhelmed, and since it’s connectionless, there’s no built-in acknowledgment of receipt.

The next concept to explore is how to effectively use processors to manipulate these incoming metrics, such as renaming, filtering, or aggregating them before they are exported.

Want structured learning?

Take the full Opentelemetry course →