hyperf滚动日志(rotatingfilehandler)不自动记录长时间运行错误,需主动埋点检测耗时并结构化记录;应通过打点、慢sql拦截、协程/请求id上下文绑定及正确配置maxfiles等实现可追溯的长耗时监控。

Hyperf滚动日志(RotatingFileHandler)本身不记录“长时间运行错误”,它只按文件大小或时间轮转日志;所谓“长时间运行错误”需靠主动检测+结构化记录,不是靠日志轮转机制自动捕获。
明确问题根源:滚动日志 ≠ 执行超时监控
RotatingFileHandler 负责把日志写进 app.log、app-2026-08-12.log 这类文件,并在达到条件(如单个文件 10MB 或满 7 天)时归档旧日志——它不感知业务逻辑是否卡住、协程是否 hang 死、SQL 是否慢查。真正要记录“长时间运行”,得靠你主动埋点或拦截。
三步实现对长耗时操作的可追溯记录
-
在关键路径加耗时打点:比如控制器入口/出口、Job 的 handle() 开始和结束处,用
microtime(true)计算耗时,超过阈值(如 3s)就记一条 warn 或 error 日志,并带上上下文:$start = microtime(true);// ... 业务逻辑$cost = microtime(true) - $start;if ($cost > 3.0) { $logger->warning('Slow operation detected', ['cost_ms' => round($cost * 1000), 'action' => 'user_login']); } -
配合 SQL 慢查询日志独立输出:在
logger.php中单独定义'sql_slow'channel,绑定RotatingFileHandler,再通过DbQueryExecutedListener拦截执行时间 > 500ms 的 SQL,并写入slow_sql.log。这样既隔离又可轮转,避免污染主日志。 -
用协程 ID + 请求 ID 绑定上下文:否则多个请求的日志混在一起,根本看不出谁卡了。必须在日志调用时传入:
['cid' => \Swoole\Coroutine::getCid(), 'rid' => $request->getAttribute('request_id') ?? 'unknown']
同时确保LineFormatter启用了include_stacktraces => true,才能看到真实堆栈,定位是哪一层卡住。
滚动配置本身要防静默失效
如果 RotatingFileHandler 配置不对,日志可能看似轮转实则只写一个文件、或旧日志从不删除:
-
maxFiles必须显式传入构造参数,否则默认为 0(不轮转)。正确写法:'constructor' => [ BASE_PATH . '/runtime/logs/slow.log', Logger::WARNING, false, false, true, 30 ](最后一位就是maxFiles) -
确保
runtime/logs目录可写且属主匹配 Swoole 进程用户(如 www-data),否则日志写失败但无报错,文件为空。 -
不要复用 handler 到多个 channel:想让 slow 日志和 access 日志各自轮转,就得定义两个独立 channel(如
'slow'和'access'),每个配自己的RotatingFileHandler实例。
补充:异步队列中长任务怎么记
队列任务若执行超时(比如设置了 handle_timeout=60),Swoole 会硬杀协程,此时 try/catch 捕不到异常,finally 也不执行。唯一可靠方式是:
- 在
handle()开头手动记录 start 日志,带job_id和cid; - 开启
async_queue.php的'retry_seconds' => 60,让失败任务重试; - 重试后仍失败的任务会进入
failed队列,此时可通过监听Hyperf\AsyncQueue\Event\AfterHandleFailed事件,统一记录“任务多次超时”详情。











