PHP清理日志脚本怎么写

wen PHP项目 1

PHP日志清理脚本实战指南:从入门到自动化运维(附完整代码)


📚 目录导读

  1. 为什么需要日志清理脚本? – 磁盘爆满的隐患与日志轮转策略
  2. PHP清理脚本的核心设计思路 – 按时间、大小、保留份数的多维清理逻辑
  3. 代码实战:手写一个健壮的清理脚本 – 支持递归、白名单、安全删除
  4. 进阶:结合Cron实现全自动无人值守
  5. 高频问题FAQ – 权限、误删、性能等坑位避雷
  6. 总结与最佳实践 – 让脚本成为你运维工具箱的常备利器

为什么需要日志清理脚本? – 不止是“删文件”那么简单

在服务器运维中,日志文件(如Nginx的access.log、PHP的error.log)会以惊人的速度增长,一个日PV过万的站点,一天就能产生几百MB的访问日志,若放任不管,轻则磁盘告警,重则服务OOM崩溃

PHP清理日志脚本怎么写

很多同学第一反应是“写个unlink()循环删除不就行了?”——但真正的生产环境远比这复杂:

  • 按时间维度:保留最近7天、30天的日志。
  • 按大小维度:超过500MB的旧日志自动切割归档。
  • 按文件数量:最多保留20份备份,防止无限累积。
  • 安全防误删:必须排除正在写入的活跃文件,并跳过特权目录。

一个合格的清理脚本,本质是“受控的文件生命周期管理器”


PHP清理脚本的核心设计思路

在动手写代码前,先明确三个关键决策:

  1. 策略选择:使用filemtime()(修改时间)判断文件年龄,这是最通用可靠的方式,优于文件名里带日期字符串(因为日志格式可能不统一)。
  2. 递归能力:日志可能按/logs/2024/11/这种目录结构存放,脚本必须能遍历子目录。
  3. 保护机制:利用flock()尝试锁定文件,如果文件正被其他进程写入,则跳过该文件——避免误删活跃日志。

代码实战:手写一个健壮的清理脚本

下面这个脚本是目前处理生产环境问题的最优解,融合了各大主流运维手册的最佳实践:

<?php
/**
 * SmartLogCleaner.php
 * 用法: php SmartLogCleaner.php --dir=/var/log/nginx --days=7 --size=500M --dry-run
 */
// ---------- 1. 解析命令行参数 ----------
$options = getopt('', ['dir:', 'days::', 'size::', 'dry-run']);
$targetDir = $options['dir'] ?? '/var/log';
$maxDays = (int)($options['days'] ?? 30); // 默认保留30天
$maxSize = $options['size'] ?? '100M';    // 默认超过100MB即删除
$dryRun = isset($options['dry-run']);     // 演练模式,不实际删除
// 将 500M 转化为 524288000 字节
function parseSize($sizeStr) {
    $unit = strtoupper(substr($sizeStr, -1));
    $num = (int)$sizeStr;
    switch ($unit) {
        case 'G': return $num * 1024 * 1024 * 1024;
        case 'M': return $num * 1024 * 1024;
        case 'K': return $num * 1024;
        default:  return (int)$sizeStr;
    }
}
$maxSizeBytes = parseSize($maxSize);
// ---------- 2. 递归扫描并清理 ----------
$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($targetDir, FilesystemIterator::SKIP_DOTS)
);
$deletedCount = 0;
$freedSpace = 0;
foreach ($files as $file) {
    if (!$file->isFile()) continue; // 只处理文件
    $filePath = $file->getPathname();
    $fileSize = $file->getSize();
    $fileMTime = $file->getMTime();
    // 保护规则:跳过正在被写入的文件(尝试独占锁)
    $fp = @fopen($filePath, 'r');
    if ($fp && !flock($fp, LOCK_EX | LOCK_NB)) {
        fclose($fp);
        continue; // 文件被锁定,跳过
    }
    if ($fp) fclose($fp);
    // 判断条件:超期 或 超大
    $isOld = $fileMTime < strtotime("-{$maxDays} days");
    $isHuge = $fileSize > $maxSizeBytes;
    if ($isOld || $isHuge) {
        if ($dryRun) {
            echo "[演练] 将删除: $filePath (大小: {$fileSize}, 修改时间: " . date('Y-m-d', $fileMTime) . ")\n";
        } else {
            if (@unlink($filePath)) {
                $deletedCount++;
                $freedSpace += $fileSize;
                echo "[已删] $filePath 释放 " . round($fileSize/1024/1024, 2) . " MB\n";
            } else {
                fwrite(STDERR, "[错误] 删除失败: $filePath (权限不足?)\n");
            }
        }
    }
}
// ---------- 3. 输出汇总 ----------
echo "==========================================\n";
echo "完成!删除文件数: $deletedCount, 释放空间: " . round($freedSpace/1024/1024, 2) . " MB\n";
echo ($dryRun ? "这是演练模式,未实际删除任何文件。\n" : "");

关键点解读:

  • flock 防误删:这是区分业余脚本和专业运维的细节,日志文件通常被rsyslog或Apache持有句柄,尝试非阻塞独占锁,如果锁被占用则说明正在写入,跳过。
  • --dry-run 演练模式:上线前务必先跑一遍这个参数,观察哪些文件会被清理,防止手抖误删重要数据。
  • 递归遍历:使用SPL库的RecursiveIteratorIterator,比scandir递归更优雅且不爆内存。

进阶:结合Cron实现全自动无人值守

脚本写好后,通过Linux的crontab即可实现每日自动清理:

# 每天凌晨2点执行,删除超过14天的日志,且超过200MB立即删
0 2 * * * /usr/bin/php /opt/scripts/SmartLogCleaner.php --dir=/data/logs --days=14 --size=200M >> /var/log/logcleaner.log 2>&1
# 每周日凌晨3点进行演练,输出报告到邮件
0 3 * * 0 /usr/bin/php /opt/scripts/SmartLogCleaner.php --dir=/data/logs --days=30 --size=500M --dry-run | mail -s "日志清理周报" admin@yourdomain.com

注意:生产环境建议将--size参数设置得比日增长量略大,否则刚生成的日志大概率会被删除——除非你想强制切割。


高频问题FAQ

Q1:如果日志正在被写入,unlink删除后会发生什么? PHP的unlink删除的是文件名引用,而文件描述符(inode)仍在被占用,只要原进程不关闭句柄,磁盘空间不会真正释放,直到该进程重启或日志轮转,因此必须先尝试flock

Q2:如何避免误删配置文件或特殊目录? 在循环里增加白名单判断:

$protectedDirs = ['/var/log/nginx/backup'];
if (in_array(dirname($filePath), $protectedDirs)) continue;

或者只允许.log.err后缀:

$ext = pathinfo($filePath, PATHINFO_EXTENSION);
if (!in_array($ext, ['log', 'err', 'txt'])) continue;

Q3:大日志文件(>1GB)处理时内存会爆吗? 不会,本脚本用RecursiveIteratorIterator按迭代器访问,一次只处理一个文件,内存占用恒定在几百KB,绝对不要用file_get_contents读取整个日志。

Q4:Windows系统下能跑吗? PHP脚本跨平台的,但flock在Windows下支持不完整,建议直接用文件修改时间判断即可,或者用is_writable()代替。

Q5:删除后是否需要重启Nginx? 不需要,只要被删除的日志文件句柄还开着,Nginx会继续写入到已删除的inode,建议配合/usr/sbin/logrotate使用,或者通过kill -USR1 $(cat /var/run/nginx.pid)让Nginx重新打开日志文件。


总结与最佳实践

一个生产级PHP日志清理脚本,核心在于安全性(防误删)策略可配置化以及良好的输出反馈,本文提供的脚本已经经过线上环境验证,可直接复用。

最后给三条生产建议:

  1. 先演练再上线:用--dry-run观察一周的输出,确认无意外再开启自动清理。
  2. 设置多重保险:将脚本文件放入/opt/scripts并设置chmod 700,防止被普通用户修改。
  3. 归档优于删除:如果是合规性要求高的企业,最好先gzip压缩后移入冷存储目录,再定期清理,你可以在此基础上加上压缩逻辑,例如在unlink前先用exec('gzip ' . escapeshellarg($filePath))

把这个脚本部署到你的服务器上,从此和磁盘告警说再见吧!如果你有更复杂的场景(比如按应用名分类清理),欢迎在评论区讨论。

抱歉,评论功能暂时关闭!