应改用Monolog StreamHandler实现流式非缓冲写入:安装monolog后,在config/log.php中配置custom驱动,返回自定义Logger并push StreamHandler;需禁用系统写缓冲、校验绝对路径权限、标准化路径分隔符及换行符。

如果您在ThinkPHP中希望避免日志写入阻塞主线程,尤其是高并发场景下因FileHandler同步I/O导致请求卡顿,则需改用Monolog原生的StreamHandler实现流式、非缓冲的日志写入。以下是配置此处理器的具体步骤:
一、启用Monolog StreamHandler并禁用缓冲
StreamHandler直接将日志内容逐条写入指定流(如文件或标准输出),不经过内存缓冲,可规避BufferHandler满载后阻塞的问题;关键在于显式关闭底层fwrite缓冲,确保每次write调用立即落盘。
1、确认项目已安装monolog/monolog:执行composer require monolog/monolog。
2、在config/log.php中定义custom驱动,返回自定义MonologLogger实例。
立即学习“PHP免费学习笔记(深入)”;
3、构造StreamHandler时传入资源句柄,例如fopen(runtime/log/app.log, 'a'),并设置第二个参数为MonologLogger::DEBUG级别。
4、对返回的StreamHandler调用setFormatter()指定格式器,避免默认格式含冗余字段。
5、调用stream_set_write_buffer($fp, 0)强制禁用该文件指针的系统级写缓冲,确保每条日志 fwrite 后立即刷入磁盘,不依赖 fclose 触发刷新。
二、配置绝对路径与运行时权限校验
异步或流式写入场景下,日志路径若为相对路径,可能因CLI与Web请求工作目录不同导致写入失败且无报错;必须使用绝对路径,并在初始化前验证目录可写。
1、使用__DIR__ . '/../runtime/log/'拼接日志目录绝对路径。
2、在创建StreamHandler前,执行is_writable($logDir)判断目录权限。
3、若不可写,抛出RuntimeException并包含$logDir完整路径,防止静默丢日志且难以定位。
4、确保runtime/log/目录由Web服务器用户(如www-data)或CLI执行用户拥有写权限,避免chmod 777,推荐chown -R www-data:www-data runtime后设chmod 755 runtime及chmod 755 runtime/log。
三、替换默认FileHandler为StreamHandler实例
ThinkPHP 6+ 日志门面不支持直接调用Log::driver()->useHandler()动态切换,必须在配置层彻底替换handler,否则仍走FileHandler逻辑。
1、在config/log.php中将'default' => 'file'改为'default' => 'custom'。
2、在'channels' => ['custom' => [...]]中,'driver'键值设为'custom','via'键值指向一个闭包函数。
3、该闭包内手动实例化MonologLogger,并调用pushHandler()压入新构建的StreamHandler对象。
4、闭包返回该Logger实例,禁止在闭包外提前new Logger或复用全局实例,否则多请求间handler状态冲突。
四、处理Windows与Linux路径差异及换行符
StreamHandler在Windows下若未显式指定换行符,可能因PHP_EOL为 导致日志行末出现双换行;同时路径分隔符差异易引发open失败。
1、日志消息末尾统一用"
"硬编码换行,禁用PHP_EOL。
2、构造文件路径时,对str_replace('\', '/', $path)标准化分隔符,再用realpath()解析为绝对路径。
3、在fopen()调用前,检查路径是否存在,不存在则递归创建:mkdir($dir, 0755, true)。
4、打开文件时使用'a'模式而非'c',避免Windows下flock竞争导致部分日志被截断。



















