beforeAction无法准确记录RESTful接口日志,因其在响应生成前触发,此时statusCode默认为200、content为空,且无法感知序列化格式与真实状态;EVENT_AFTER_REQUEST才是唯一可靠入口,此时响应已完整生成。

beforeAction 不适合记录 RESTful 接口的完整请求日志,因为它拿不到真实响应状态和内容;必须用 EVENT_AFTER_REQUEST 配合手动打点才能准确记录 method、url、status、耗时等关键字段。
为什么 beforeAction 记录 RESTful 日志会出错
RESTful 接口通常由 yii
estActiveController 或自定义 action 处理,其响应生成逻辑比普通控制器更晚——比如序列化、格式化、状态码自动推导(如 201/404)都发生在 afterAction 甚至 send() 阶段。beforeAction 此时读 $response->statusCode 只能拿到默认的 200,$response->content 基本为空。
- 常见错误现象:所有接口日志 status 都是 200,哪怕实际返回了 422 或 500
- 并发下容易把 A 请求的 request ID 和 B 请求的 response 内容混在一起
- 如果用了
yii estSerializer或启用了 JSONP、XML 格式切换,beforeAction完全无法感知最终输出格式
EVENT_AFTER_REQUEST 是唯一可靠入口
这是 Yii 底层在 Application::run() 结束前触发的事件,此时 Yii::$app->response 已完成所有处理,包括 headers 设置、content 渲染、status 确定。但要注意两点:
-
getElapsedTime()返回的是从应用启动到当前的总耗时,不是单次请求耗时 → 必须自己用microtime(true)打点 -
$response->content在启用ob_start()、gzip、或使用StreamResponse时仍为空 → 不要无条件依赖它 - 避免在该事件里调用
$response->send()或修改 headers,否则会触发 “headers already sent” warning
推荐写法(放在 config/web.php 的 components 下):
'on afterRequest' => function ($event) {
$req = Yii::$app->request;
$res = Yii::$app->response;
$cost = (microtime(true) - YII_BEGIN_TIME) * 1000;
Yii::info([
'method' => $req->method,
'url' => $req->absoluteUrl,
'status' => $res->statusCode,
'cost_ms' => round($cost, 2),
'format' => $req->getAcceptableContentTypes()[0] ?? 'unknown',
], 'rest');
},
给 RESTful 日志加 request_id 并透传到文件目标
REST 接口调试最怕日志串行,必须绑定唯一标识。不能只靠行为类往 header 写 X-Request-ID,还要确保它进日志上下文。
- 行为类中设置
Yii::$app->log->targets['file']->exporter->context['request_id'] = $requestId是可行的,但注意:该赋值只对后续Yii::info()生效,且需确保 target 名字确实是file - 更稳妥的方式是在
LineFormatter的template中直接引用:"{level} [{date}] {message} [req:{request_id}]\n" - 如果用了多个 target(比如同时写文件和 DB),每个 target 的
exporter要单独设 context,不能共用
记录响应体要分场景判断
REST 接口常需查看原始 JSON 输出,但 $response->content 并非总是可用:
- 正常 JSON 响应(
ActiveController默认):content 可读,但需确认没被ob_flush()清空 - 大文件下载或流式响应(
sendFile()/StreamResponse):content 永远为空,强行读会丢数据或报错 - 启用了
yiiiltersContentNegotiator且 format 设为xml:content 是 XML 字符串,不是数组
安全做法是加开关控制:
if ($res->statusCode < 400 && !($res instanceof yiiwebStreamResponse)) {
$log['response'] = substr($res->content, 0, 2048); // 截断防日志爆炸
}
真正难的不是加一行日志,而是让每条日志能对应上一次真实 HTTP 交互——这要求你清楚知道 Yii 的响应生命周期在哪一环才“尘埃落定”,以及哪些字段在什么条件下才可信。


















