webman暴露/metrics路由是prometheus唯一抓取入口,需用prometheus/client_php安装官方客户端,在bootstrap.php中单例初始化collectorregistry,定义gauge和counter指标并在全局中间件after()中更新,响应头必须为content-type: text/plain; version=0.0.4,避免多进程重复注册导致指标混乱。

Webman 暴露 /metrics 路由是 Prometheus 抓取指标的唯一入口,不加这层路由,Prometheus 就什么都看不到——不是配置错,是根本没出口。
如何让 Webman 正确暴露 Prometheus 兼容的指标文本
关键不在“有没有指标”,而在“格式对不对”和“实例是否复用”。Prometheus 只认标准 OpenMetrics 文本格式(以 # HELP 开头、name{label="value"} value timestamp 行式结构),且要求所有指标注册在同一个 CollectorRegistry 实例上。
- 必须用
composer require prometheus/client_php安装官方客户端,别用非标封装库 -
CollectorRegistry初始化只能做一次,建议放在app/Bootstrap.php中,绑定到Container或全局静态属性,避免中间件里每次 new - 定义指标时优先用
Gauge(如webman_http_request_duration_seconds)记录瞬时值,用Counter(如webman_http_requests_total)累加请求次数,别把耗时也用 Counter - 在全局中间件(如
app/middleware/RequestLog.php)的after()钩子中调用$gauge->observe($duration)或$counter->inc(['status' => $code]),确保请求结束才写入
为什么 /metrics 返回 500 或空内容
最常见原因是指标收集逻辑抛了未捕获异常,或 render() 前 registry 被重置。Prometheus 客户端本身不处理 PHP 错误,一旦中间件里执行 observe() 时发生类型错误(比如传了 null 给 observe)、或标签值含非法字符(如空格、斜杠),render() 就会直接报 500。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 检查
app/controller/MetricsController.php的index()方法:必须先调用$registry->getMetricFamilySamples()或直接$registry->render(),不能漏掉 - 返回头必须设为
Content-Type: text/plain; version=0.0.4,少一个分号或错写成v0.0.4都会导致 Prometheus 抓取失败并记为INVALID - 上线前用
curl -v http://your-domain.com/metrics手动验证,看响应体是否为纯文本、是否有# TYPE行、每行是否符合name{...} \d+(\.\d+)?格式
如何避免多 Worker 进程下指标混乱
Webman 默认启多个 Worker 进程,如果每个进程都独立初始化 CollectorRegistry,Prometheus 抓到的就是 N 份重复指标,时间序列乱序、counter 跳变、Gauge 值抖动——这不是监控不准,是数据源本身就不可信。
- 绝对不要在
onWorkerStart或中间件构造函数里 newCollectorRegistry - 改用单例模式:在
Bootstrap.php中$registry = new CollectorRegistry(); Container::set('prometheus.registry', $registry);,后续全从容器取 - 若需跨进程共享计数(如总请求数),必须换方案:用 Redis + Lua 原子 incr,再由一个专用 HTTP 接口聚合后暴露,别硬塞进 Prometheus 客户端
- Worker 数量变化时(如 reload),旧进程的指标不会自动清理,所以
/metrics响应里看到的其实是“当前存活 Worker 的指标之和”,这点要在 Grafana 查询时用sum by(job, instance)显式聚合
真正难的不是写几行 observe(),而是理解 Prometheus 的 pull 模型如何与 Webman 的多进程模型共存——指标必须收敛到一个逻辑实例,而那个实例不能依赖进程生命周期。










