必须用 prometheus_client sdk 启动独立 http 服务(如 start_http_server)或 asgi 集成,正确选型 counter/gauge/histogram,克制使用标签避免高基数,通过中间件统一埋点确保异常下指标不丢失。

用 prometheus_client 注册并暴露自定义指标
Python 应用导出指标,核心是启动一个独立的 HTTP 服务端(通常在 /metrics 路径),由 Prometheus 定期拉取。必须用 prometheus_client 的 Python SDK,不能自己拼接文本格式——它负责处理指标类型、标签、并发安全和 OpenMetrics 兼容性。
常见错误是直接在主线程里调用 start_http_server() 后继续执行业务逻辑,结果阻塞住;正确做法是用子线程或异步方式启动指标服务:
- 推荐用
start_http_server(port=8000, addr='0.0.0.0')单独起一个线程,不干扰主应用流程 - 若应用本身是异步(如 FastAPI/Starlette),改用
make_asgi_app()集成到 ASGI 生命周期中,避免端口冲突 - 不要在多个地方重复调用
Counter('my_requests_total', ...)—— 指标注册是全局单例,重复会报ValueError: Duplicated timeseries in CollectorRegistry
选对指标类型:Counter、Gauge、Histogram 的实际区别
类型选错会导致查询语义失效。比如用 Gauge 记录请求数总量,Prometheus 的 rate() 函数就完全无法计算每秒增长率;反过来,用 Counter 记录当前内存使用量,累加后数值只会越来越大,失去参考意义。
典型用法:
-
Counter:只增不减的累计值,如http_requests_total{method="POST",status="200"},适合 rate() / increase() 分析 -
Gauge:可升可降的瞬时值,如process_resident_memory_bytes、连接池空闲数,适合直接看当前值或变化趋势 -
Histogram:分桶统计延迟,如http_request_duration_seconds,自动产生_bucket、_sum、_count三个时间序列,支持histogram_quantile()
别手写分位数计算——Histogram 自带桶机制,observe(0.15) 就够了;手动算 95% 分位再打点,既不准又破坏 Prometheus 原生聚合能力。
给指标加标签要克制,避免高基数问题
标签(label)是 Prometheus 多维查询的基础,但每个唯一标签组合都会生成独立时间序列。例如给请求路径加 path="/user/123" 标签,用户量一多,序列数爆炸,内存和查询性能立刻崩。
安全实践:
- 只对有限、稳定、有聚合意义的维度打标签:如
method、status、endpoint(指路由模板/user/{id},不是具体 ID) - 绝对避免将用户 ID、订单号、UUID、毫秒级时间戳等作为标签值
- 如果必须追踪单次请求,用日志(配合 Loki)或链路追踪(Jaeger),而不是 Prometheus 标签
- 上线前用
count({job="myapp"})粗略估算序列数,超过 10 万就要警惕
在 Flask/FastAPI 中注入指标采集逻辑
Web 框架里埋点最容易犯的错是把指标更新写在视图函数内部,导致异常未捕获时计数丢失。更可靠的方式是统一中间件拦截,确保无论成功失败都记录状态。
以 FastAPI 为例:
from prometheus_client import Counter, Histogram
import time
<p>REQUESTS_TOTAL = Counter('http_requests_total', 'Total HTTP Requests', ['method', 'status'])
REQUEST_DURATION = Histogram('http_request_duration_seconds', 'HTTP Request Duration', ['method', 'endpoint'])</p><p>@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
start_time = time.time()
try:
response = await call_next(request)
REQUESTS_TOTAL.labels(method=request.method, status=str(response.status_code)).inc()
return response
finally:
REQUEST_DURATION.labels(method=request.method, endpoint=request.url.path).observe(time.time() - start_time)</p>
注意:finally 块保证即使发生未捕获异常,耗时也能记录;而 status 标签必须从 response.status_code 取,不能靠 try/except 判断——因为某些异常可能让响应根本没生成。
Flask 类似,用 @app.after_request 和 @app.teardown_request 组合覆盖成功与异常路径。
真正麻烦的从来不是怎么注册一个 Counter,而是指标含义是否和业务监控目标一致、标签设计是否经得起流量规模考验、以及当 Prometheus 报 target is down 时,第一反应是不是去查 Python 进程是否卡死在某个阻塞 I/O 上——这些细节比语法重要得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











