推荐由异常处理器统一接管日志记录,控制器仅在必要时用Log::channel()补充结构化业务日志,须脱敏敏感字段且不可替代report()职责。

在 ThinkPHP6 控制器中记录异常日志,不推荐直接在控制器里零散调用 Log::write() 或 try-catch 手动写日志——容易遗漏、结构混乱、无法关联请求上下文,且违背分层职责。真正可靠的做法是:让控制器专注业务,把异常日志交由框架统一的异常处理机制接管,仅在必要时做补充记录。
控制器内需谨慎补充日志的场景
某些业务异常需要额外上下文(如订单号、用户操作链路),可在控制器中主动记录,但必须满足:
- 使用
Log::channel('xxx')显式指定日志渠道,避免混入默认日志 - 日志内容结构化,至少包含:
admin_id(或user_id)、order_no、ip、url、param(已脱敏) - 敏感字段(如
password、token、card_no)必须过滤或替换为[REDACTED] - 不替代
report()职责,只作增强,不处理堆栈、不触发告警
例如:
use think\facade\Log;
try {
$order = OrderService::create($data);
} catch (BusinessException $e) {
Log::channel('business')->error('订单创建失败', [
'order_no' => $data['order_no'] ?? '',
'user_id' => session('user_id'),
'ip' => request()->ip(),
'url' => request()->url(),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'params' => array_filter($data, function($k) {
return !in_array($k, ['password', 'token']);
}, ARRAY_FILTER_USE_KEY),
]);
throw $e; // 仍需抛出,交由 Handler 统一上报
}
真正起效的日志记录不在控制器,而在 Handler.php
控制器不是异常日志的主战场。ThinkPHP6 的异常日志核心逻辑应放在 app/exception/Handler.php 中:
-
report()方法负责记录与上报:判断是否需上报、写入本地日志、推送至 Sentry/ARMS 等 -
render()方法只负责响应输出:返回 JSON 或视图,不执行任何 I/O 操作
关键配置要点:
- 在
shouldReport()中排除预期异常(如ValidateException、HttpException404/405) -
report()中对Error类型(如ParseError、FatalError)显式处理,TP6.1 默认不捕获 -
$dontReport数组明确声明无需日志的类:protected $dontReport = [ ValidateException::class, TokenExpiredException::class, \think\exception\HttpException::class, // 404/405 等不记为错误 ];
避免常见错误
- ❌ 在控制器里写
Log::write('xxx')而不指定 channel → 日志进single文件,难检索、无结构 - ❌ 直接记录
$request->param()全量数据 → 泄露密码、token 等敏感字段 - ❌
catch后只打日志却不throw→ 异常被吞掉,report()不触发,线上问题无声消失 - ❌ 用
input()替代原始参数记录 → 已被 trim / htmlspecialchars 处理,审计失真
补充建议:上传、登录等特殊操作单独埋点
控制器中确有例外场景需手动日志,比如:
- 文件上传失败:必须在
catch块内用Log::error()记录原始文件名、大小、错误原因 - 登录失败:配合行为钩子(如
Hook::listen('login_fail', [...])),不写在控制器逻辑深处 - 敏感操作(删除用户):可加
Log::channel('audit')->notice(),但主体仍走中间件统一拦截
日志要能查、能筛、能追溯,靠的是结构和位置,不是行数多少。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











