Instrumenting a Python app

Prometheus · Practical · 14 min

The most valuable metrics are the ones only your application knows: business events, queue depth, request latency.

Official client libraries exist for Go, Java, Python, Ruby and Rust, and community ones for most other languages. The pattern is the same everywhere:

  • Create metrics once, at module level, with a name, help text and the label names.
  • Update them in code: inc() counters, set()/inc()/dec() gauges, observe() histograms (or use a timer helper).
  • Expose /metrics: a built-in HTTP server, or a handler mounted in your web framework.

What to instrument, following the RED method for services: Rate of requests, Errors, Duration (a histogram). For resources such as queues and pools, the USE method: utilization, saturation, errors.

Label hygiene matters most here: use the route template (/users/{id}), never the actual path, and keep status codes or status classes. Choose histogram buckets around your latency objectives (for example 50 ms, 100 ms, 250 ms, 500 ms, 1 s).

Multi-process servers such as Gunicorn need the client library's multiprocess mode, since each worker has its own memory. For batch jobs that finish before a scrape, push final values to the Pushgateway instead.

In Python, prometheus_client also exposes default process and Python runtime metrics automatically.

Example

# pip install prometheus-client
import random, time
from prometheus_client import Counter, Histogram, start_http_server

REQUESTS = Counter("shop_requests_total", "Requests handled", ["route", "status"])
LATENCY = Histogram("shop_request_duration_seconds", "Request latency",
                    ["route"], buckets=[0.05, 0.1, 0.25, 0.5, 1])

def handle(route):
    with LATENCY.labels(route).time():          # observes the duration
        time.sleep(random.random() / 10)
    REQUESTS.labels(route, "200").inc()

start_http_server(8000)                          # serves /metrics on :8000
for _ in range(50):
    handle("/products/{id}")                     # route template, not real path
# curl -s localhost:8000/metrics | grep shop_requests
# shop_requests_total{route="/products/{id}",status="200"} 50.0

Remember

  • Define metrics once, update them in code, expose /metrics
  • RED: rate, errors, duration histogram for every service
  • Label with route templates and bounded values

Watch out for

  • Creating a new metric object per request, causing duplicate registration errors or leaks.

Check yourself

  1. Which label value is safe for an HTTP request counter?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: prometheus.io/docs/instrumenting/clientlibs/