Hyperf 日志需协程安全且能准确定位请求现场,关键在于异常被捕获至框架处理器、启用堆栈追踪、注入协程ID、显式传入上下文标识,并通过测试验证日志是否含完整堆栈及cid/rid字段。

Hyperf 默认日志驱动(Monolog + StreamHandler)本身协程安全,但协程内报错若没正确捕获或上下文未注入,就会表现为“报错无声”“堆栈丢失”“日志找不到对应请求”。核心不是换驱动,而是让日志能准确记录协程执行现场。
确保异常被框架异常处理器捕获
协程内抛出的异常(如 validate 失败、DB 查询异常)必须落到 Hyperf 的异常处理链里,否则会被协程 silently 吞掉。检查两处:
- 确认
config/autoload/exceptions.php中已注册对应 server name 的 handler(如'api' => [Hyperf\Validation\ValidationExceptionHandler::class]),且 name 与server.php中定义的完全一致、大小写敏感 - 自定义 handler 的
handle()方法中,必须调用$response->withStatus(422)等显式设状态码,并返回含 errors 字段的 JSON;不要只调$throwable->getMessage()
启用堆栈追踪并注入协程标识
默认 LineFormatter 不输出完整堆栈,且多协程下日志行不带上下文,无法定位哪条日志属于哪个协程。
- 修改
config/autoload/logger.php:在formatter的constructor中,将第四个参数设为true(include_stacktraces),第五个设为true(allow_inline_line_breaks) - 在请求入口(如中间件或控制器方法开头)手动注入协程 ID:
\Hyperf\Context\Context::set('correlation_id', uniqid('req_')) - 自定义 Formatter 时,从
\Hyperf\Context\Context::get('correlation_id')取值,拼入日志 message 或 context 字段
日志调用时强制携带协程与请求标识
不要依赖全局变量或静态上下文——协程环境下它们不可靠。
- 打印日志时,显式传入
cid和request_id:$this->logger->error('DB query failed', ['cid' => \Swoole\Coroutine::getCid(), 'rid' => $request->getAttribute('request_id') ?? 'unknown']) - 若用 AOP 自动记日志,切面中必须通过
ApplicationContext::get(ContextInterface::class)获取当前协程上下文,而非Context::get() - 避免在
@Before切面中提前拼接日志字符串,应统一在@Around中执行完方法后再组装,确保能拿到返回值和异常
验证是否生效的快速方式
写一个测试接口,主动触发协程内异常:
- 启动服务后访问该接口,观察
runtime/logs/hyperf.log是否出现带完整堆栈、含cid和rid字段的 error 日志行 - 对比错误写法:直接
$this->logger->error('oops')—— 日志里只有时间、级别、message,无堆栈、无上下文 - 若仍无堆栈,检查
logger.php中 formatter 配置是否真被加载(可临时加var_dump($formatter); die;在 formatter 构造函数中验证)



















