hyperf异常处理需确保堆栈完整、上下文准确:必须将throwable对象作为logger最后一个参数传入;协程下需开启formatter的include_stacktraces;同时注入request_id和coroutine_id以精确定位。

在 Hyperf 中编写异常处理类并正确打印堆栈报错信息,关键不是“能不能打”,而是“打全、打准、不丢上下文”。默认情况下,若只调用 $e->getMessage() 或拼接字符串,堆栈会完全丢失;协程环境下还可能因上下文隔离失效导致日志错乱。
确保堆栈完整输出
必须将 Throwable 对象本身作为最后一个参数传给 logger,Monolog 才会自动展开完整堆栈。仅记录消息或手动调用 getTraceAsString() 是无效的。
- ✅ 正确写法:
$this->logger->error('用户登录失败, uid: {uid}', ['uid' => $uid], $throwable); - ❌ 错误写法:
$this->logger->error('用户登录失败: ' . $throwable->getMessage());(无堆栈) - ❌ 错误写法:
$this->logger->error($throwable->getTraceAsString());(无业务上下文,格式混乱)
协程环境需启用堆栈捕获支持
Hyperf 基于 Swoole 协程,默认日志 formatter 可能关闭堆栈输出。需显式开启:
- 打开
config/autoload/logger.php - 找到
'formatter' => Monolog\Formatter\LineFormatter::class对应的'constructor'配置 - 确保第四个参数为
true(include_stacktraces),第五个为true(allow_inline_line_breaks)
绑定请求与协程上下文
堆栈有了,但若多请求并发,日志仍难定位。必须把请求 ID 和协程 ID 注入日志上下文:
- 在控制器入口或中间件中设置:
\Hyperf\Context\Context::set('request_id', $request->getAttribute('request_id')); - 自定义 Formatter 时读取:
\Hyperf\Context\Context::get('request_id') ?: 'unknown' - 打印日志时一并传入:
$this->logger->error('DB 查询异常', ['rid' => $rid, 'cid' => \Swoole\Coroutine::getCid()], $e);
避免 stopPropagation() 干扰日志链路
调用 $this->stopPropagation() 是为了终止异常传递,但它不影响日志记录——只要你在 handle() 方法里完成了日志写入,就无需担心被截断。但注意:
- 不要在
isValid()返回true后却不打日志也不调用stopPropagation(),否则异常会继续往下走,可能被其他 handler 重复记录 - 若你希望保留框架默认日志(如 HttpExceptionHandler 的日志),就不要提前
stopPropagation(),而是让流程自然流转











