Hyperf容器重启日志丢失的根本原因是日志默认写入容器临时文件系统runtime/logs/,需挂载runtime目录至宿主机或使用远程日志;挂载时须设useLocking=>false、避免NFS轮转异常、注意SELinux权限及格式化配置。

Hyperf 容器重启后日志丢失,根本原因不是框架问题,而是日志默认写入容器内部 runtime/logs/ 目录——该路径随容器销毁而清空。必须将日志落盘到宿主机或远程日志系统,否则必然丢失。
为什么挂载 runtime/logs 卷是最低成本方案
Hyperf 日志默认输出到 BASE_PATH . '/runtime/logs/app.log',这个路径在容器内是临时文件系统(如 overlay2)。不挂载就等于“写进内存,关机即焚”。
- 必须挂载整个
runtime目录,不能只挂载logs子目录——因为 Hyperf 启动时会初始化runtime下多个子目录(如cache、container),部分组件依赖目录存在性 - Docker Compose 示例中,
volumes必须用绝对路径或命名卷,相对路径在某些 Docker 版本下会静默失败 - 宿主机目录需提前创建并赋予容器运行用户可写权限(如
chown -R 1001:1001 /host/logs,对应 Hyperf 默认 UID) - 挂载后仍要检查
config/autoload/logger.php中的filename是否指向挂载后的路径(通常是/runtime/logs/app.log,无需改)
RotatingFileHandler 在挂载卷下必须关掉 useLocking
Hyperf 使用 Monolog 的 RotatingFileHandler 做日志轮转,但 Swoole 协程环境下启用文件锁(useLocking => true)会导致轮转卡死或句柄泄漏——尤其在多 worker 进程+挂载卷场景下更明显。
- 务必在
logger.php配置中显式设为'useLocking' => false - 轮转依赖原子性 rename 操作,挂载卷(尤其是 NFS 或某些云盘)可能不完全支持,此时应降低
maxFiles值(如设为7而非30),减少旧日志堆积 - 如果发现轮转后旧日志文件残留且未被压缩/删除,大概率是挂载卷的 unlink 权限或 inode 处理异常,可改用
StreamHandler+ 外部 logrotate 工具接管
用 ELK 收集日志时,别直接让 Hyperf 写 stdout
很多人以为“把日志打到 stdout,再由 Filebeat 抓取”最简单,但在 Hyperf 中这反而容易丢日志或格式错乱。
- Hyperf 的协程日志写入不是线性的,多个协程并发写
php://stdout会导致行首混叠(比如两行日志挤成一行,或 JSON 格式被截断) - 若使用
StreamHandler输出到 stdout,必须配LineFormatter并开启allowInlineLineBreaks => true,否则换行符会被吃掉 - 更稳的做法是:继续写文件(挂载卷),然后用 Filebeat 的
tail模式监听日志文件——它支持 inotify、支持断点续传、能处理轮转 rename - ELK 端注意配置
json.parse或dissect过滤器,因为 Hyperf 默认日志不是纯 JSON;若要结构化,应在logger.php中切到JsonFormatter并确保所有上下文数据可序列化
真正容易被忽略的是:挂载卷后,runtime 目录的初始权限和 SELinux 上下文。在 CentOS/RHEL 主机上,即使 chmod 正确,容器也可能因 SELinux 策略被拒写——此时得加 :z 或 :Z 标签(如 /host/runtime:/runtime:z),否则日志目录创建失败,Hyperf 会静默降级到 /tmp 或直接报错退出。


















