Swoole 5.0+ 废弃 swoole_error_log,须用 log_level + log_file 控制日志;log_level 需在 Server 实例化前设置,Worker 日志需动态 PID 命名隔离,error_log() 与 Swoole 日志冲突需避免,Docker 中日志丢失主因是 stdout/stderr 缓冲或重定向。

为什么 swoole_error_log 不生效?
因为 Swoole 5.0+ 已废弃该配置项,改用 log_level + log_file 组合控制。直接写 swoole_error_log 在 php.ini 或 swoole_set_process_name() 前调用,日志照样打到 stderr 或默认位置,根本不会隐藏。
实操建议:
-
log_level设为SWOOLE_LOG_WARNING或更高(如SWOOLE_LOG_ERROR),可过滤掉INFO和NOTICE级别日志,本质是“减少记录”,不是“隐藏” - 必须在
Swoole\Server实例化前设置,否则无效;若用Swoole\Http\Server,也要在 new 之后、start()之前调用set() - 若想彻底屏蔽某类日志(比如心跳检测的
recv: connection#X closed),不能靠配置,得重写onClose并空实现,或用log_file指向/dev/null(Linux)或NUL(Windows)
如何让 Worker 进程日志不混进主日志文件?
默认所有进程共用一个 log_file,高并发下会因文件锁或写入竞争导致日志错乱、截断,甚至覆盖关键错误。
实操建议:
- 用
getmypid()动态生成日志路径,例如:log_file => '/var/log/swoole/worker_'.getmypid().'.log' - 注意:PHP-FPM 场景下
getmypid()返回的是 FPM master 进程 PID,必须在onWorkerStart回调里获取真实 Worker PID - 如果用
SWOOLE_PROCESS模式,每个 Worker 是独立进程,可以安全使用getmypid();但SWOOLE_BASE模式下无 Worker 进程概念,此法不适用 - 避免用时间戳命名(如
date('His')),秒级重复会导致日志被覆盖
onReceive 中用 error_log() 会触发 Swoole 的日志冲突吗?
会。Swoole 自身日志系统和 PHP 原生 error_log() 默认都往 stderr 写,尤其在 Docker 容器里,两条日志流会互相穿插,一条完整请求日志可能被切成三段,排查时完全对不上上下文。
实操建议:
- 禁用
error_log(),统一走 Swoole 日志:用Swoole\Logger::info()或Swoole\Coroutine::getPId()+ 自定义格式写入独立文件 - 若必须用
error_log(),先调用ini_set('error_log', '/tmp/myapp_worker.log')切换目标,且确保该路径所有 Worker 进程有写权限 - 注意
error_log()不受log_level控制,哪怕设成SWOOLE_LOG_ERROR,error_log('debug')仍会输出
Docker 环境下日志“消失”的真正原因
不是 Swoole 配置错了,而是容器 stdout/stderr 被重定向或缓冲了。常见现象:本地跑得好好的,一上 Docker 就看不到任何日志,docker logs -f 空空如也。
实操建议:
- 启动容器时加
--log-driver=local并配--log-opt max-size=10m,避免默认 json-file 驱动在大日志量下丢数据 - 在
entrypoint脚本里加stdbuf -oL -eL php start.php,强制行缓冲,防止日志滞留在内存中不刷出 - 检查是否用了
nohup或&后台启动,这会让 stdout/stderr 断开,Swoole 日志直接丢失 - 最稳妥的做法:压根别依赖容器日志驱动,把
log_file显式设为挂载卷内的路径(如/data/logs/swoole.log),再用tail -f查看
日志管理真正的难点不在开关或路径,而在多进程、容器化、异步 IO 三者叠加后,输出流归属和时序完全不可控——这时候靠“隐藏”不如靠“隔离”。










