ThinkPHP 6 路由层日志应使用 beforeAction 钩子,在 Route::rule() 中传闭包获取 $request 和 $option,记录 URL、方法、参数及关键的 $option['rule'] 路由规则字符串,避免用中间件或事件。

ThinkPHP 路由层怎么加日志记录?别动控制器,用 beforeAction 钩子
直接在路由定义里挂日志逻辑,比在每个控制器方法里写 Log::write() 干净得多。ThinkPHP 6 的 beforeAction 是最贴合「请求入口打点」的钩子——它在控制器执行前触发,已解析完路由、参数、中间件,但尚未进业务逻辑,此时拿 $request->url()、$request->method()、$request->param() 都是准的。
常见错误是把日志塞进全局中间件:中间件可能被跳过(比如 allowCrossDomain 或静态资源路由),也可能重复执行(多个中间件链)。而路由钩子只对匹配到该路由规则的请求生效,粒度可控。
- 只对注册了钩子的路由生效,不影响未配置的接口
- 能拿到完整路由变量,比如
id、type这类绑定在 URL 中的参数,$request->param()已合并路由参数和 query - 若需记录响应耗时,得配合
afterAction钩子一起用,单靠beforeAction只能记开始时间
Route::rule() 里怎么写钩子?传闭包还是数组?
必须用闭包形式传入 beforeAction,数组方式(如 ['before_action' => 'log'])在 TP6+ 不被识别。闭包第一个参数是 $request 对象,第二个是当前路由的 $option 数组(含 route、var 等),别漏掉。
典型写法:
立即学习“PHP免费学习笔记(深入)”;
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
Route::rule('api/:controller/:action', 'api/:controller/:action')
->beforeAction(function ($request, $option) {
Log::write(sprintf('[ROUTE] %s %s | params: %s',
$request->method(),
$request->url(true),
json_encode($request->param(), JSON_UNESCAPED_UNICODE)
), 'info');
});
- 闭包里不能用
$this,没有上下文对象;所有依赖要提前注入或用门面(如Log::) - 如果路由用了域名绑定(
Route::domain()),钩子仍有效,但$request->url(true)返回的是带域名的完整地址,注意日志体积 - 避免在钩子里做阻断操作(如抛异常、
exit),TP 不保证钩子执行后的流程一致性
为什么不用 app_init 或 action_begin 事件?
app_init 太早,路由还没解析,$request->path() 是原始路径,$request->param() 为空;action_begin 又太晚,已进入控制器实例化阶段,部分中间件可能已修改请求数据,且无法区分是哪个路由规则匹配的。
更关键的是兼容性问题:action_begin 在 TP6.1+ 被标记为「不推荐」,官方文档明确建议用路由钩子替代。
-
beforeAction是唯一能稳定拿到「路由解析后、控制器执行前」上下文的位置 - 事件机制(
Event::trigger())有额外开销,尤其高并发下,钩子是原生调用,无反射、无监听器遍历 - 若项目启用了路由缓存(
route_cache),事件监听器可能因缓存失效而漏触发,钩子不受影响
记录内容里最容易丢的关键字段是什么?
不是 URL,也不是参数,而是 route_rule —— 即实际匹配上的那条路由规则字符串,比如 'user/:id' => 'index/user/read' 中的 user/:id。这个值不在 $request 里,得从钩子第二个参数 $option['rule'] 拿。
没记录它,日志就失去路由维度的聚合能力:你看到一百条 /user/123 请求,但不知道它们是走的 RESTful 规则、还是 fallback 规则、还是某个模块专用路由,排查分流策略或灰度发布时会卡住。
- 正确取法:
$option['rule'] ?? 'unknown',注意空值兜底 - 别用
$request->rule(),这个方法返回的是控制器/操作名映射,不是原始路由表达式 - 如果项目用了多级子域名或
Route::group(),$option里还会有domain、prefix字段,一并记下来能省掉后续关联查询


















