Workerman协程日志混乱主因是误用Swoole协程混搭或未隔离上下文,需先确认Swoole协程启用及代码中是否存在go、Co::create等调用;解决方法包括:强制添加cid与request_id标识、封装co_log自动绑定协程上下文、禁用file_put_contents/echo改用Worker::log、升级为JSON结构化日志。

Workerman协程日志打印混乱时,无法靠肉眼分辨哪条日志属于哪个协程、哪个连接或哪次请求,排查问题像在迷雾中找路。你看到的可能是多条请求日志交织成一行、时间戳错乱、JSON结构被截断,甚至关键字段(如uid、request_id)完全丢失。
确认是否真在用协程
Workerman本身不原生支持协程,所谓“协程日志混乱”,90%情况是误用了Swoole协程模式混搭Workerman,或在Worker进程内手动启用了Swoole\Coroutine::create()但未隔离日志上下文。先执行php --ri swoole,检查输出中是否有coroutine => enabled且version => 5.0+;再搜索代码里是否出现Swoole\Coroutine::create、go function()或Co\run()。没有这些,就不是协程问题,而是多进程写文件交织——跳转到下一节。
给每条日志强制打上唯一身份标签
协程间共享全局资源(如$fp文件句柄、静态变量)是日志混乱的根源。必须让每条日志自带可追溯的“身份证”。
方法一:用Swoole内置协程ID + 请求标识
在协程启动前立即获取并注入上下文:
$cid = Swoole\Coroutine::getuid();$requestId = bin2hex(random_bytes(6));Co::set(['request_id' => $requestId, 'cid' => $cid]);
后续所有Worker::log()或error_log()都拼接[cid:{$cid} req:{$requestId}]前缀。注意:【不能在协程外调用Swoole\Coroutine::getuid(),会返回-1】。
方法二:封装日志函数,自动绑定当前协程上下文
定义一个co_log($msg)函数,内部用Swoole\Coroutine::getContext()取当前协程私有数据,若无则初始化并存入request_id和cid,再格式化输出。这比每次手动拼接更可靠,也避免漏加。
禁用直接file_put_contents和echo
协程环境下file_put_contents($file, $msg, FILE_APPEND)看似方便,实则危险:它每次调用都重新open/fwrite/close,而协程切换可能发生在fwrite中途,导致日志碎片化。同样,echo输出到stdout在守护进程模式下根本不可见,还可能被其他协程抢占缓冲区。
立刻删除所有裸写的file_put_contents和echo,统一走Worker::log()或专用日志通道。Worker::log()底层调用error_log(),当error_log配置为文件路径时,PHP会复用文件句柄,配合O_APPEND原子性,在≤4096字节前提下能保持单行完整——这是目前最轻量级的协程安全方案。
升级到结构化日志+独立日志进程
第一步:改用JSON格式输出,确保机器可解析
把Worker::log("uid:{$uid} action:login")替换成:
Worker::log(json_encode(['time' => date('Y-m-d H:i:s.u'),'cid' => Swoole\Coroutine::getuid(),'request_id' => $requestId ?? 'unknown','level' => 'info','message' => "user login",'uid' => $uid,'ip' => $connection->getRemoteIp() ?? '',
</p> <p>第二步:启动一个专用日志协程,所有日志消息通过channel投递过去<br>在Worker启动前初始化:</p> <p><code>$logChannel = new Swoole\Coroutine\Channel(1024);
Swoole\Coroutine::create(function () use ($logChannel) { $fp = fopen('/var/log/workerman/co.log', 'a'); while ($log = $logChannel->pop()) { fwrite($fp, $log . "\n"); fflush($fp); }});
第三步:业务协程中改用$logChannel->push($jsonLog)发送,不再碰文件IO。这彻底解耦了日志写入与业务逻辑,协程再多也不影响日志顺序。


















