prometheus_client 是 Prometheus 官方的 Python 库,让任何 Python 应用
能在 /metrics 端点导出 Prometheus 格式的指标。
安装 + 最小可用
uv add prometheus-client
# 暴露在独立端口
from prometheus_client import start_http_server, Counter, Gauge, Histogram
req_count = Counter('http_requests_total', 'Total HTTP requests',
['method', 'path', 'status'])
start_http_server(8001) # 独立 :8001/metrics
# 然后做你的事 ...
req_count.labels(method='GET', path='/users', status='200').inc()
或挂在 FastAPI 路由:
from prometheus_client import make_asgi_app
app.mount('/metrics', make_asgi_app())
Django:
# urls.py
from prometheus_client import make_wsgi_app
from django.urls import path
from django.views.generic import View
class MetricsView(View):
def get(self, request):
... # 用 django-prometheus 包更省事
实际项目直接用 django-prometheus:
uv add django-prometheus
加 middleware 后内置一堆 Django 指标(视图延迟、SQL 时间、缓存命中等)。
4 种 metric 用法
Counter:单调递增
requests = Counter('requests_total', '...', ['method'])
requests.labels(method='GET').inc()
requests.labels(method='POST').inc(5)
PromQL:
rate(requests_total[5m]) # 每秒请求数(按 5 分钟窗口)
Gauge:可上可下
queue_size = Gauge('queue_size', '...')
queue_size.set(42)
queue_size.inc(); queue_size.dec()
# 用 callback 让 Prometheus 拉取时实时计算
queue_size.set_function(lambda: r.llen('jobs'))
适合:当前队列长度、连接数、温度、内存使用。
Histogram:分布
latency = Histogram('http_latency_seconds', 'HTTP latency',
['endpoint'],
buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25,
0.5, 1, 2.5, 5, 10])
# 上下文管理器自动测延迟
with latency.labels(endpoint='/users').time():
do_work()
PromQL 出 p95:
histogram_quantile(0.95,
sum(rate(http_latency_seconds_bucket[5m])) by (le, endpoint))
bucket 选 5-12 个,覆盖典型延迟范围。bucket 多了占资源,bucket 少了
分位数不准。
Summary:分位数(不推荐)
from prometheus_client import Summary
size = Summary('request_size_bytes', '...')
size.observe(2048)
Summary 在客户端算分位数,无法在多实例上做正确聚合。生产基本只用
Histogram,需要分位数时用 histogram_quantile() 在服务端算。
label 设计原则
label 的不同值组合决定了 series 的数量。label cardinality 高了
Prometheus 内存爆。
# 错: user_id 是无限基数
requests.labels(user_id=user.id).inc()
# 错: 完整 URL 含动态 ID
requests.labels(path='/users/123/posts').inc()
# 对: 路径模板化
requests.labels(path='/users/{id}/posts').inc()
label 数应该是个有限小集合:endpoint 模板、HTTP method、status code、
某几个固定 region 等。
一个完整中间件(FastAPI)
import time
from prometheus_client import Counter, Histogram
from starlette.middleware.base import BaseHTTPMiddleware
REQ = Counter('http_requests_total', '...',
['method', 'path', 'status'])
LATENCY = Histogram('http_latency_seconds', '...',
['method', 'path'],
buckets=[.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5])
class MetricsMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
t0 = time.perf_counter()
response = await call_next(request)
elapsed = time.perf_counter() - t0
# 用路由模板而不是实际路径
route = request.scope.get('route')
path = route.path if route else request.url.path
REQ.labels(method=request.method, path=path,
status=str(response.status_code)).inc()
LATENCY.labels(method=request.method, path=path).observe(elapsed)
return response
app.add_middleware(MetricsMiddleware)
request.scope.get('route').path 给出的是 /users/{user_id}/posts
这种模板,不是 /users/42/posts 这种实参。
内置 collector
prometheus_client 自带几个:
from prometheus_client import REGISTRY, GCCollector, PlatformCollector, ProcessCollector
# 默认这些已经注册(CPython 平台 + 进程信息)
# 可以手动反注册节省指标
REGISTRY.unregister(GCCollector(REGISTRY))
process_cpu_seconds_total、process_resident_memory_bytes 等都是
免费白送的。
多 worker 的坑
gunicorn / uvicorn 多 worker 时,每个 worker 是独立进程,独立的 metrics。
直接 scrape /metrics 只看到一个 worker 的数据。两种解法:
- mmap 共享:设
PROMETHEUS_MULTIPROC_DIR=/tmp/prom,prometheus_client
会把指标写到共享目录,import 时聚合所有 worker 的数据。 - 每个 worker 独立 scrape:让 Prometheus 单独抓每个 worker 端口
(复杂度高,不推荐)。
mmap 版本写法:
# 进程启动时
import os
os.environ['PROMETHEUS_MULTIPROC_DIR'] = '/tmp/prom'
# 暴露指标时用 MultiProcessCollector
from prometheus_client import multiprocess, CollectorRegistry, generate_latest
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)
output = generate_latest(registry)
worker 退出时调 multiprocess.mark_process_dead(pid)。
测试
def test_counter_increments():
REQ.labels(method='GET', path='/x', status='200').inc()
# 直接读 metric 内部值
val = REQ.labels(method='GET', path='/x', status='200')._value.get()
assert val == 1
踩过的坑
- 把 user-id / session-id 当 label:1M 用户 = 1M series,Prometheus 崩盘。
这种"高基数"信息应该用 log,不用 metric。 - Histogram bucket 改了:旧数据和新数据不可比较,需要双写或重设。建议
bucket 一开始想清楚。 /metrics端点不要走鉴权:Prometheus scrape 没有简单的 auth;
挂内网或加 IP 白名单。- 用
with管 Histogram timer:抛异常时也会记录,所以错误请求的延迟
也算进 p95。如果想只看成功请求的延迟,加 try/except 区分。
登录后参与评论。