必须用copytruncate,否则Workerman因不重开文件描述符会持续写入旧inode导致日志丢失;它通过复制内容后截断原文件,保持fd有效,确保日志不中断、不丢失。

Logrotate 配置里必须用 copytruncate,否则 Workerman 会写丢日志
Workerman 默认不重开文件描述符,它把 stdout(比如 /var/log/workerman.log)打开后就一直往里写,不会感知到 Logrotate 的 move 或 rename 操作。如果你不用 copytruncate,Logrotate 会把原文件移走再新建一个同名空文件,而 Workerman 还在往旧的 inode 里写——结果就是新日志文件永远为空,旧日志被切走后内容丢失。
正确做法是让 Logrotate 先复制一份再清空原文件,这样 Workerman 的 fd 仍有效:
/var/log/workerman.log {
daily
missingok
rotate 30
compress
delaycompress
copytruncate
notifempty
}
注意几点:
-
copytruncate是关键,不能省; -
notifempty避免空文件被误切; -
delaycompress防止刚切完就压缩,方便排查问题; - 别加
create,Workerman 自己建的日志文件权限/属主可能被覆盖。
Workerman 启动时要确保 stdout 被重定向到具体文件,而不是管道或 /dev/null
很多人用 php start.php start -d 启动后发现日志没生成,其实是没显式重定向 stdout。Workerman 默认把 echo、var_dump 等输出打到终端,后台运行时这些输出会被丢弃,除非你主动重定向。
启动命令必须带重定向:
php start.php start -d >> /var/log/workerman.log 2>&1
或者更稳妥地,在代码里用 file_put_contents + STDOUT 替代直接 echo,但最简单可靠的方式还是 shell 层重定向。注意:
- 不要只写
>>不写2>&1,否则错误日志(如 Notice、Warning)不会进日志文件; - 路径得是绝对路径,相对路径在 daemon 模式下容易指向错目录;
- 确保运行 Workerman 的用户对
/var/log/workerman.log有写权限(比如用www-data运行,就得chown www-data:www-data /var/log/workerman.log)。
Logrotate 手动测试切割是否生效,别等自动触发才发现问题
Logrotate 默认每天凌晨触发,等一天才能验证配置是否有效,太慢。应该立刻手动跑一次:
logrotate -f /etc/logrotate.d/workerman
然后检查:
- 原
/var/log/workerman.log是否变为空(copytruncate的表现); - 是否有类似
/var/log/workerman.log.1的归档文件生成; - Workerman 进程是否还在持续写入(
tail -f /var/log/workerman.log看实时输出); - 如果没写入,用
lsof -p $(pgrep -f "start.php") | grep log确认进程是否还持有旧文件句柄(正常情况应指向当前workerman.log,不是.1)。
避免和 Workerman 内置日志组件冲突,禁用 Worker::$logFile 或保持路径一致
有些项目启用了 Workerman 的 Worker::$logFile = '/path/to/log';,它会调用 error_log() 写文件,和 shell 重定向的 stdout 是两套机制。混用会导致日志重复、时间戳错乱、甚至因并发写同一个文件引发内容损坏。
建议统一渠道:
- 如果用 Logrotate 管理,就关掉
Worker::$logFile(设为null或注释掉); - 如果保留
Worker::$logFile,那 Logrotate 配置目标就得改成它指定的路径,且同样必须加copytruncate; - 千万别让
Worker::$logFile和 shell 重定向指向不同文件又都开着——你会看到一半日志在 A 文件、一半在 B 文件,查问题时直接抓瞎。
Logrotate 本身不复杂,但和 Workerman 这种长期运行、不 reload fd 的进程配合时,copytruncate 是绕不开的硬约束,漏掉它,一切配置都是白搭。


















