thinkphp 6.0 默认无 /metrics 接口,因其仅为web框架而非监控组件,不自动注册路由、不采集qps/耗时等指标、不输出prometheus标准文本格式;所有指标暴露需手动集成promphp/client_php,通过psr-15中间件打点并独立路由暴露。

ThinkPHP 6.0 本身不暴露 Prometheus 指标,必须手动集成指标采集逻辑,否则 Grafana 什么都查不到。
为什么 TP6.0 默认没有 /metrics 接口
TP6.0 是一个 Web 框架,不是监控组件。它不会自动注册 /metrics 路由、不会自动收集请求耗时、QPS、错误率等指标,更不会按 Prometheus 文本格式输出。所有指标暴露都得你写代码实现——这不是配置开关的事,是补全缺失链路的事。
- 官方未内置任何 metrics 中间件或 Exporter 组件
-
think-queue、think-swoole等扩展也默认不打点 - 直接访问
/metrics会返回 404,不是端口没开,是根本没定义这个路由
用 middleware + promphp 实现指标暴露
最轻量、侵入最小的做法:引入 promphp/prometheus_client_php,在全局中间件中记录 HTTP 请求指标。注意别用过时的 domnikl/statsd 或自行拼接文本格式——Prometheus 只认标准 exposition 格式。
- 安装客户端:
composer require promphp/prometheus_client_php - 在
app/middleware/下新建PrometheusMiddleware.php,handle()中调用$counter->inc(['status' => $response->getCode()])和$histogram->observe($duration) - 务必在
app/middleware.php中注册该中间件,并确保它在日志、鉴权等中间件之后(否则统计不准) - 暴露路由:在
route/app.php添加Route::get('metrics', 'PrometheusController@export'),控制器里调用CollectorRegistry::getDefault()->render()
示例关键片段:
use Prometheus\CollectorRegistry; use Prometheus\Storage\APC; // ⚠️ APC 需启用,否则多进程下计数错乱;若用 Swoole,改用 RedisStorage
Grafana 查不到 TP6 指标?先确认这三件事
很多人卡在“数据源连上了但图表空”,问题几乎都出在底层链路断点上,而不是 Grafana 配置。
- Prometheus 是否真能抓到
http://your-tp6-app/metrics?用curl -v http://your-tp6-app/metrics看返回是否含# TYPE行和_total、_bucket等合法指标行 - Prometheus 的
scrape_configs是否配了 job?目标地址是否写成targets: ['your-tp6-app:80']而不是['localhost:80'](容器网络下尤其容易错) - Grafana 数据源选的是不是你刚配的 Prometheus 实例?不是 Alertmanager,也不是另一个测试环境的 Prometheus
查不到指标时,别急着调面板,先去 Prometheus 的 Status > Targets 页面看这个 job 的状态是不是 UP,抓取失败日志里有没有 server returned HTTP status 500 或 connection refused。
TP6 的业务指标怎么加?别硬塞到 HTTP 中间件
订单创建成功率、缓存命中率、数据库慢查询次数——这类业务指标不能靠请求中间件捕获,得在具体逻辑里主动打点。
- 在 Service 层或 Model 方法里获取
CollectorRegistry::getDefault()->getCounter(...)实例,调用inc()或set() - 避免在高频循环里反复调用
getCounter(),应提前注入或静态缓存实例 - 指标名遵守命名规范:
tp6_order_create_total(小写字母+下划线),不要用TP6_Order_Create或带空格 - Label 值别传敏感信息(如用户 ID、手机号),Label 维度爆炸会导致 Prometheus 内存暴涨
一个易被忽略的细节:Swoole 长连接环境下,CollectorRegistry 默认是进程级单例,但若用多 Worker 进程且共享存储(如 Redis),需显式指定 StorageInterface,否则各进程指标互不可见。











