PHP实现类bash tail -n0 -F功能的日志持续读取脚本需求
用PHP实现类似
tail -n0 -F的日志监控功能 需求
- 脚本启动后仅读取新增行,不读取文件中已有的内容(对应bash命令
tail -n0的效果) - 日志文件被轮转/清理后,脚本能自动恢复读取(对应bash命令
tail -F的效果)
已实现的Bash版本
tail -n0 -F "$STATS_LOG_PATH" | \ while read LINE; do # 输出行或执行其他自定义操作 echo "$LINE" done
尝试的PHP代码(未达到预期)
// Open log file $fh = fopen('/tmp/test.log', 'r'); while (true) { // Read line $line = fgets($fh); // Print line if any or sleep if ($line !== false) { echo $line . PHP_EOL; } else { // sleep for 0.1 seconds usleep(0.1 * 1000000); fseek($fh, ftell($fh)); } }
完整PHP实现代码
$logPath = '/tmp/test.log'; $fileHandle = null; $lastInode = 0; // 初始化/重新打开日志文件的工具函数 function openLogFile($path, &$inode) { global $fileHandle; // 先关闭旧的文件句柄(如果存在) if ($fileHandle !== null) { fclose($fileHandle); } // 等待文件重建(处理文件被删除后还未生成的情况) while (!file_exists($path)) { usleep(100000); // 休眠0.1秒 } // 打开文件 $fileHandle = fopen($path, 'r'); if (!$fileHandle) { error_log("无法打开日志文件: $path"); usleep(100000); return false; } // 跳到文件末尾,确保只读取后续新增内容 fseek($fileHandle, 0, SEEK_END); // 获取当前文件的inode(用于检测文件轮转) $fileStat = fstat($fileHandle); $inode = $fileStat['ino']; return true; } // 初始化打开日志文件 openLogFile($logPath, $lastInode); while (true) { // 检测文件是否被删除或轮转 if (!file_exists($logPath)) { openLogFile($logPath, $lastInode); continue; } $currentStat = stat($logPath); if ($currentStat['ino'] !== $lastInode) { openLogFile($logPath, $lastInode); continue; } // 读取新增行 $line = fgets($fileHandle); if ($line !== false) { // 这里可以替换为自定义的行处理逻辑 echo rtrim($line) . PHP_EOL; } else { // 无新内容时休眠,避免CPU空转 usleep(100000); // 重置指针位置,处理文件被截断的情况 fseek($fileHandle, ftell($fileHandle)); } }
核心逻辑解释
- 仅读新增内容:启动时通过
fseek($fileHandle, 0, SEEK_END)直接跳到文件末尾,完全跳过已有内容,和tail -n0效果一致。 - 应对文件轮转/清理:通过文件的
inode编号判断文件是否被替换——日志轮转后新文件的inode会和旧句柄的inode不一致,此时自动重新打开新文件并跳到末尾;如果文件被删除,就循环等待直到文件重建后再继续。 - 降低CPU占用:无新内容时休眠0.1秒,避免脚本持续空转消耗CPU;休眠后重置指针位置,防止文件被清空后指针停在超出文件长度的位置。
- 容错处理:文件暂时不存在时不会直接报错退出,而是等待文件重建;打开失败时记录错误并短暂重试,提升脚本稳定性。
内容的提问来源于stack exchange,提问作者user14706816
相关产品推荐
相关产品推荐

