ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux日志自动清理脚本实战:从logrotate到自定义Shell/Python方案

Linux日志自动清理脚本实战:从logrotate到自定义Shell/Python方案 1. 日志自动清理脚本从需求到落地的完整复盘做运维这些年有个活儿看着不起眼但特别磨人日志清理。服务器上的日志文件要是不管几天就能把磁盘塞满系统直接卡死甚至崩溃。我这次要分享的就是一个用于 Linux 环境下的日志自动清理脚本它解决的就是“日志无限增长导致磁盘告警”这个高频痛点。这个脚本适合谁一线运维工程师、自己折腾服务器的个人开发者、以及刚入门 Linux 想通过实战提升脚本能力的同学。它的核心价值在于自动扫描指定目录下的日志文件按保留天数或大小阈值清理过期日志并通过日志记录与邮件通知让你对清理结果心里有数。相比手工rm -rf或者单纯依赖 logrotate这个脚本更灵活能覆盖自定义目录、特殊命名规则和多样化的清理策略。我先把最终版的脚本核心逻辑放出来再逐步拆解每个设计决策背后的考量。#!/bin/bash # # 日志自动清理脚本 v2.3 # 功能按保留天数/大小阈值清理目标目录日志文件 # 适用Linux CentOS/Ubuntu 全系bash 3.2 # # ------- 配置区按需修改------- LOG_DIRS(/var/log/myapp /home/user/project/logs) RETENTION_DAYS15 MAX_SIZE_MB500 DELETE_DRY_RUNtrue # true演练模式false真实删除 LOG_DELETE_RECORD/var/log/log_cleaner/cleaner.log MAIL_NOTIFY_ENABLEDfalse MAIL_TOopsexample.com # --------------------------------- # ------- 函数定义 ------- log_msg() { local msg[$(date %Y-%m-%d %H:%M:%S)] $* echo $msg if [ -n $LOG_DELETE_RECORD ]; then echo $msg $LOG_DELETE_RECORD fi } clean_by_time() { local dir$1 local days$2 local cutoff cutoff$(date -d $days days ago %Y%m%d) local count0 local freed0 while IFS read -r file; do [ -f $file ] || continue local file_date file_date$(basename $file | grep -oE [0-9]{8} | head -1) [ -z $file_date ] continue if [ $file_date -lt $cutoff ]; then local size size$(stat -c%s $file) if [ $DELETE_DRY_RUN false ]; then rm -f $file fi freed$((freed size)) count$((count 1)) log_msg 按时间清理: $file (大小: $size bytes) fi done (find $dir -maxdepth 1 -type f -name *.log.* | sort) log_msg 目录 $dir 按时间清理完成: 删除 $count 个文件, 释放 $((freed/1024)) KB } # ------- 主流程 ------- log_msg 日志清理任务开始 for dir in ${LOG_DIRS[]}; do [ -d $dir ] || { log_msg 警告: 目录 $dir 不存在跳过 continue } clean_by_time $dir $RETENTION_DAYS done if [ $DELETE_DRY_RUN true ]; then log_msg 提示: 当前为演练模式未真实删除任何文件确认无误后请将 DELETE_DRY_RUN 改为 false fi log_msg 日志清理任务结束 运行效果示例$ ./clean_logs.sh [2025-01-18 03:20:01] 日志清理任务开始 [2025-01-18 03:20:01] 按时间清理: /var/log/myapp/app.log.20250101 (大小: 204800 bytes) [2025-01-18 03:20:01] 按时间清理: /var/log/myapp/app.log.20250102 (大小: 198656 bytes) [2025-01-18 03:20:01] 目录 /var/log/myapp 按时间清理完成: 删除 2 个文件, 释放 394 KB [2025-01-18 03:20:01] 提示: 当前为演练模式未真实删除任何文件确认无误后请将 DELETE_DRY_RUN 改为 false [2025-01-18 03:20:01] 日志清理任务结束 2. 为什么还需要自己写清理脚本logrotate 和他做不到的事先说一个很多人会问的问题Linux 自带的 logrotate 不是专门干这个的吗为什么会选择自己写一个日志自动清理的 shell 脚本而不是直接用现成的工具logrotate 确实是日志轮替的标准工具大多数发行版装完系统就默认配置了。它的工作模式是“轮替”把当前日志改名加上日期序号再新建一个空日志让程序继续写。通过rotate N参数控制保留的轮替文件数量。这种方案对系统级日志如/var/log/messages、/var/log/secure非常有效因为它能保证日志文件始终存在程序写入不受影响。但实际业务场景里logrotate 有三类情况覆盖不好。第一是它依赖/etc/logrotate.d/下的配置文件需要逐项配置默认只对系统日志生效。应用日志——尤其是放在自定义路径下的那些——通常需要你自己新增配置而这个配置语法相当诡异缩进敏感、指令又多不少同事第一次配 logrotate 都被它的语法坑过。第二是 logrotate 按“份数”轮替不看时间也不看整个目录的整体体积。如果你需要保留“最近N天的日志”这个需求用 logrotate 表达非常别扭。第三是精细化的清理策略比如按文件名中嵌入的日期批量删除、按目录总大小触发清理、按文件年龄清理logrotate 需要依赖 size、age 等参数组合远不如脚本来得直观。自己写脚本的核心优势其实就是三句话路径可控、策略直白、逻辑透明。我可以灵活控制哪些目录、哪些文件、按什么规则清理脚本逻辑读起来一目了然后续接手的同事不用去翻 logrotate 文档就能看懂出问题的时候 echo 一把就能调试。对于中小型项目脚本就是性价比最高的方案。当然如果日志量非常大或者文件数量达到几千上万个shell 脚本的findrm效率确实不如 logrotate 的 C 实现。但这个话题放到后面“性能优化”部分再展开。3. 脚本设计思路拆解先想清楚三个问题再动手3.1 问题一按“时间”判断还是按“大小”判断这是设计日志清理脚本时第一个要决策的问题也是方案分叉点。按时间清理的典型场景是应用每天晚上生成一个带日期的日志文件比如app.log.20250118、app.log.20250117。此时清理逻辑就是“把保留天数之前的文件删掉”。判断依据就是文件名中的日期段或者文件的mtime修改时间。这种方式适合日志周期性切割、命名规则统一的情况。按大小清理的典型场景则是某个日志文件持续写入但名字不变比如error.log一直在增长。此时需要设定一个大小阈值超出则触发删除或截断。判断依据是stat命令获取的文件字节数。建议把两种策略做成可组合的功能模块。我的脚本里先实现了按时间清理的clean_by_time同时把大小清理的逻辑单独留了接口。实际项目中我见过不少同学一上来只留了一种策略结果部署到线上发现另一个目录的日志命名规则完全不同又得改脚本。所以设计阶段就做好“策略与目录解耦”后面扩展会省很多事。按时间判断时优先从文件名里提取日期而不是直接看文件的mtime。为什么因为很多日志文件是上个月生成并写入的但这个月可能被程序重新打开追加写入导致mtime被更新。如果按mtime判断会出现“过期日志因为被touch而活下来”的漏删问题。文件名里的日期是写入时固定下来的不会变用它做判断更可靠。3.2 问题二文件命名规则不统一怎么办真实环境里“不统一”是常态。有的日志是app.log.2025-01-18有的是app-20250118.log还有的单纯是app.log。如果脚本一上来就用某种固定正则匹配文件名大概率会漏掉一部分。我的处理思路是把“匹配”和“判断”分离先用find找到目录下所有.log相关文件不限定具体命名格式只是缩小范围提取文件名中的连续 8 位数字作为日期比如20250118如果没有匹配到 8 位数字则跳过该文件(不处理)文件名里的日期小于截止日期就删。用grep -oE [0-9]{8}做主匹配能同时兼容app.log.20250118和app-20250118.log这种带连接符的命名。代码有两点需要注意文件名里可能没有数字比如app.log本来就不带日期这种情况会被跳过属于合理行为但如果目录下还有一种规则是app.2025.01.18.log那连续8位数字的规则匹配不到。真实项目里遇到这种命名调整一下正则即可比如[0-9]{4}[.-]?[0-9]{2}[.-]?[0-9]{2}。3.3 问题三安全删除怎么做才不手抖清理日志最怕两件事删错文件、删了改不回来。日志本身没有版本控制一旦删错就真的找不回来了。所以脚本设计里安全机制是重中之重。第一层保护演练模式。默认设置DELETE_DRY_RUNtrue脚本只打印“将要做什么”不真正执行rm。先跑一遍看输出确认无误后再把参数改成false执行真实清理。别看这个设计简单真救过不少人——有一次 App 在凌晨 3 点生成的日志文件名里带的是未来日期20990101如果没先演练直接上线这个“未来文件”反而会被跳过、旧文件却被误删。第二层保护硬限制只删除匹配规则的文件。每个待删文件都会经过“存在性检查”“日期提取”“日期对比”三重关卡任何一步不满足就跳过。这样即使目录里混入了无关文件也不会被误删。第三层保护删除记录落盘。每次删除动作都会写一行日志到$LOG_DELETE_RECORD内容包括文件名和字节数。万一后面发现误删了还能凭记录判断是什么时候删的、删了多大方便追溯。之所以把这三层保护放在一起是因为脚本一旦交给 crontab 自动化运行运行时机是深夜你在睡梦中没有任何人工复核的机会。保护机制就是你的“程序化副驾”。4. 实操过程从零到一写一个可用的清理脚本4.1 环境准备与基础命令确认不同发行版的 Linux 有些命令细节不一样脚本里用到的stat、date、find在 CentOS 和 Ubuntu 上参数略有差异尤其是stat -c%s。CentOS(RHEL 系) 的stat用-c%s输出纯字节数Ubuntu(Debian 系) 也支持-c%s但有些精简版 GNU coreutils 可能只支持--printf%s。为了跨平台稳妥脚本里stat -c%s在 Ubuntu 上是没问题的但遇到 BusyBox 之类的精简环境就建议换成wc -c $file虽然慢一点但兼容性最好。日期处理上date -d $days days ago %Y%m%d在 GNU date 下通用如果你的环境是 macOSdate -d不可用需要用date -v-15d %Y%m%d。题主场景是 Linux所以脚本直接用了 GNU 语法。国产化 Linux 环境(如统信 UOS、麒麟 OS)通常基于 Debian/Ubuntu 或 CentOS 改造GNU date 和 coreutils 都在这个脚本可以直接跑这也是相关热词里常提到“linux国产”环境的原因之一。4.2 创建脚本文件的步骤记录拿到一台服务器先创建一个专门放运维脚本的目录习惯上放在/usr/local/bin下方便 PATH 直接调用或者放/opt/scripts单独管理。实操中我更倾向于后者因为/usr/local/bin通常混入很多安装器生成的软链容易看花眼。mkdir -p /opt/scripts cd /opt/scripts vim clean_logs.sh chmod x clean_logs.sh写入脚本内容后先跑一个语法检查。bash -n是纯语法检查不执行任何逻辑但凡有书写错误它会告诉你行号。bash -n clean_logs.sh如果没输出就是语法没问题。然后再用bash -x clean_logs.sh做调试模式它会逐步打印每条命令的执行过程和变量值排查脚本逻辑问题非常好用。4.3 设置定时任务crontab 的正确姿势光有脚本不够日志清理的“自动”两个字要靠 crontab 来实现。crontab -e编辑器打开以后加一行0 3 * * * /opt/scripts/clean_logs.sh /var/log/log_cleaner/cron_stdout.log 21这里有几个细节值得展开。0 3 * * *表示每天凌晨 3 点整执行。为什么选 3 点因为大多数业务系统凌晨 2-4 点是访问低谷日志写入量小清理动作不容易影响到正在写入的文件同时这个时段离日志切割(很多程序配置的切割点在 0 点)也有一段缓冲避免了“刚切完旧文件又被删掉”的纠缠。关于 crontab 的环境变量问题我特别提醒一句crontab 执行时 PATH 很简陋通常只有/usr/bin:/bin。如果你的脚本里用到了自定义安装路径的命令比如/usr/local/bin/python3建议在脚本开头强制写好环境变量或用绝对路径。这里脚本只用到了date、find、stat、rm都在/usr/bin下所以 crontab 下可以直接跑不需要额外处理。注意crontab 的输出重定向最好单独指定日志文件不要用 /dev/null 21一吞了之。脚本正常还好一旦哪天脚本挂了没有任何输出线索排查起来会很痛苦。保留输出日志是排障的底线。4.4 python 等其他实现思路脚本不只 shell 一条路热词里能看到“python给另一个py脚本传递参数”“linux运行python脚本”这些高频搜索。如果你更熟悉 Python这个日志清理任务用 Python 实现其实更舒服尤其在日期解析、正则匹配、目录遍历上标准库的os、time、re、glob组合起来非常顺手代码可读性比 bash 高一个台阶。举一个简单的 Python 版本框架方便有编程基础的人对比import os import re import time from datetime import datetime, timedelta LOG_DIRS [/var/log/myapp, /home/user/project/logs] RETENTION_DAYS 15 DRY_RUN True cutoff datetime.now() - timedelta(daysRETENTION_DAYS) for log_dir in LOG_DIRS: if not os.path.isdir(log_dir): print(fwarning: {log_dir} not exist) continue for fname in os.listdir(log_dir): if not fname.endswith(.log) or . not in fname: continue m re.search(r(\d{8}), fname) if not m: continue file_date datetime.strptime(m.group(1), %Y%m%d) if file_date cutoff: fpath os.path.join(log_dir, fname) fsize os.path.getsize(fpath) if not DRY_RUN: os.remove(fpath) print(fclean: {fpath}, size{fsize})这个版本逻辑和 bash 版本一一对应代码更平铺直叙。写给它它的关键差异在于 Python 没有“命名管道”“进程替换”这些 bash 特有的坑在 Windows 上也能跑适配性更强。如果你工作需要跨平台或者同期有其他 Python 任务要结合建议直接用 Python 版。如果纯粹是 Linux 运维场景shell 版本的好处是零依赖、启动快、与 crontab 配合天然无缝各有取舍。5. 核心细节解析日期提取、find 用法与零碎坑5.1 日期提取真相流编辑器连续匹配脚本中basename $file | grep -oE [0-9]{8} | head -1这一行做的是“取文件名 → 提取第一个连续8位数字”。grep -oE的-o参数只输出匹配的片段-E启用扩展正则。head -1只取第一个结果防止某些文件名里有多个日期数字段导致干扰。这个提取方式存在一个隐患如果文件名里有两个日期比如app.20250101.20250102.log历史上确实见过这种命名因为你重复轮替导致日期叠加head -1会取到第一个日期可能不是真正的写入日期。遇到这种情况建议根据实际命名规则调整策略要么取最后一个tail -1要么干脆统一只取文件名最后一段。5.2 find 与 while 组合读文件列表的方法脚本里用while IFS read -r file; do...done (find ... | sort)这种写法它的优势在于能正确处理包含空格、特殊字符的文件名。管道符while加read -r是 shell 处理文件列表的经典模式读出的变量$file用双引号包裹确保文件路径中的空格不引发错误。可能有人会问直接用find ... -exec rm {} \;岂不是更简洁确实一行命令就能做到“删除目录下N天前的文件”。但那样做的问题是你没法在删除前记录文件列表、没法打印文件名、也没法区分哪些该删哪些不该删。脚本的价值不在于“删”这个动作本身而在于“可观测”“可干预”“可追溯”。这也是为什么我坚持用 while 循环而不是 find 直接执行删除。sort在这里是为了让删除顺序稳定可控先删日期靠前的再删日期靠后的。逻辑上对结果没影响但日志输出的可读性会好很多。5.3 stat 获取文件大小的跨发行版差异stat -c%s $file返回文件字节数这在 CentOS/RHEL 和 Ubuntu/Debian 上都可用。但请特别注意两点CentOS 上stat -c%s输出纯数字Ubuntu 上同样输出纯数字没问题。但如果你用stat --printf%s\n两种发行版也兼容。不建议用stat -c之外的 BSD 写法BSD 的stat -f%z在 Linux 上会报错。如果文件超过 2GBstat -c%s在 32 位系统上可能返回错误值。今天的主流服务器基本是 64 位系统这个问题极少遇到但以防万一遇到超大日志时可以把字节换算成 MB 再做判断。脚本里给出的释放空间统计单位是 KB用freed$((freed size))累加原始字节最后统一除以 1024。这里有个隐含注意点size来自stat -c%s是纯数字freed初始为 0。bash 的算术运算不支持浮点数除完直接取整作为参考值够用了不必较真。5.4 文件时间还是文件名时间场景判断上一节说“优先从文件名提取日期”但这是在“日志文件名带日期”的前提下。真实世界里还有一种日志是程序持续写入同一个文件没有任何轮替步骤比如app.log从安装运行到现在就一直增长。名字里没有日期可以提取此时文件名日期逻辑失效只能退一步看文件的mtime修改时间。所以脚本在提取不到 8 位数字时直接continue跳过。如果目录里既有带日期的轮替日志又有一个永不轮替的app.log那么这个app.log永远不会被时间策略清理需要单独用大小策略处理。不要试图用一个函数解决所有文件不同文件用不同策略职责分离会更清晰。6. 功能扩展大小阈值清理、邮件通知与异常处理6.1 按目录总大小触发清理按文件大小清理的另一个维度是按“整个目录的总大小”触发。比如日志目录总大小超过 2GB 就清理最旧的文件直到降到 1GB 以下。这种策略维保在业务日志小文件特别多的时候尤其有效——文件数量多、单个文件小、日期分散按天数判断可能删不干净按总容量判断更直观。实现思路clean_by_size() { local dir$1 local max_size_mb$2 local dir_size_mb dir_size_mb$(du -sm $dir 2/dev/null | cut -f1) if [ $dir_size_mb -le $max_size_mb ]; then log_msg 目录 $dir 当前大小 ${dir_size_mb}MB未超过 ${max_size_mb}MB 阈值跳过 return 0 fi log_msg 目录 $dir 当前大小 ${dir_size_mb}MB已超阈值开始按文件修改时间倒序清理... find $dir -maxdepth 1 -type f -name *.log* -printf %T %p\n | sort -n | while read -r ts file; do if [ $(du -sm $dir | cut -f1) -le $max_size_mb ]; then break fi rm -f $file log_msg 按大小清理: $file done }这段代码有一个小心机find输出用-printf %T %p\n把文件修改时间转成 Unix 时间戳加路径sort -n按时间从旧到新排列。判断逻辑很直接目录还超过阈值就删一个最旧的文件删完再检查一次直到目录低于阈值或无可删文件。要注意那个du -sm在每次循环里都会执行一次文件多时会有性能开销但对日常日志目录规模完全够用。提示find -printf在 GNU findutils 上可用国产化 Linux(麒麟、UOS 等)内置的 find 通常也是 GNU 版本。如果在 BSD 系统或 macOS 上跑-printf参数会报错需要改用-exec stat或ls -t等方式替代。6.2 邮件通知功能的落地日志清理脚本往往在业务低谷静默运行结果要不要人知道一般建议“有异常才通知每次清理多少量级可以汇总”。脚本里的MAIL_NOTIFY_ENABLED参数控制是否调用mailx发邮件。send_mail() { local subject$1 local body$2 if [ $MAIL_NOTIFY_ENABLED true ]; then echo $body | mailx -s $subject $MAIL_TO fi }邮件接基于本机 MTA 是否可用。现在很多云服务器默认没装 sendmail/postfixmailx命令发不出去。如果不想引入邮件基础设施更实用的方案是接入企业微信、钉钉、飞书的 Webhook那本质上是 HTTP POST 一个 JSONcurl 就能解决。如果对这块感兴趣可以把脚本里的清理汇总信息拼个 JSON 发到群机器人这个比邮件更直观是现在运维机器人告警的主流姿势。6.3 文件在写入时被删除的后果日志文件被清理的时候程序可能还在往这个文件写入数据。Linux 上rm删除一个被占用的文件并不会立刻报错文件句柄仍然存在程序继续往这个“已删除”的 inode 写入直到文件描述符关闭为止。从用户角度看磁盘空间不会立刻释放现象是“删了文件 df 却不变”。这种情况怎么办关键点在于不要在高峰期清理正在写入的日志。定时任务放在凌晨 3 点就是为了避开写入高峰。如果还有特殊情况比如某个服务凌晨 4 点会有活跃任务就需要进一步微调执行时间。如果真的遇到“文件被删除但空间没释放”的情况用lsof | grep deleted查是哪个进程占着句柄重启该进程即可释放空间。这里重点提醒不要为了释放空间直接 kill 业务进程尽量选低峰期重启否则可能引发连接中断、缓存丢失等连锁反应。6.4 日志记录文件本身会不会无限膨胀清理脚本往$LOG_DELETE_RECORD里写日志这个“日志清理脚本的日志”也要防止无限增长。建议LOG_DELETE_RECORD使用 logrotate 管理或者在同个 crontab 里每周来一条清理命令。有经验的运维不会让自己陷入“清理脚本把磁盘写满”的讽刺局面。脚本体积膨胀很快的场景是某个目录有海量小文件要清理一条文件一行记录几万行日志很快就几 MB。可以定期清理超过 30 天的清理记录文件或者按行数截断。7. 常见问题与排查技巧实录让脚本真正可靠7.1 脚本报错date: invalid date这类问题多半是date -d语法在目标 Linux 上不可用比如有些精简版系统只装了 BusyBox 的 date。前文提过 macOS 上也没有 GNU 的-d参数。排查方式很简单在目标机器上直接敲date -d 15 days ago %Y%m%d看报不报错。如果不支持可以退回用date -d -15 days %Y%m%d还不行就用date --date15 days ago %Y%m%d抑或直接改用python或perl生成日期字符串。总的来说先把环境摸清再写脚本能省下大量调试时间。另一个相关的坑date %Y%m%d在“代替”模式下取的日期用的是系统当前时间。如果服务器时区配置异常凌晨 0 点跑清理脚本时date拿到的可能还是前一天导致截止日期计算偏差。建议服务器统一配置 TZAsia/Shanghai 或你所在时区。排查时可以用timedatectl看一下当前时间与本地时间是否一致。7.2 日志文件名里带了时间但是格式不一热词里有人在找“linux常用命令大全”“shell脚本入门”会卡在这一类细节问题上。文件名带日期的格式五花八门app.log.2025-01-18、app-20250118.log、app.20250118.0930.log。grep -oE [0-9]{8}只匹配不带分隔符的连续8位数字如果文件名是2025-01-18这种带横杠的形式匹配失败文件就被跳过。真实场景里我见过有一半的日志命名都带短横线或点分隔符。针对带分隔符的命名改用grep -oE [0-9]{4}[.-]?[0-9]{2}[.-]?[0-9]{2}匹配后再去符号拼接成YYYYMMDD比较。还有一种做法是直接用sed删掉分隔符file_date$(basename $file | grep -oE [0-9]{4}[.-]?[0-9]{2}[.-]?[0-9]{2} | head -1 | tr -d .-)tr -d .-把匹配结果里的点号和横杠全部去掉得到纯数字日期。这个方法非常实用值得直接抄走。7.3 脚本跑完了但磁盘空间没释放前面说过“deleted 文件仍然占用空间”的问题除了那个原因之外还有几种可能。第一rm -f删除后df的数据刷新有延迟等几秒再看第二删除的是稀疏文件或者软链对应的实际文件位置不对删的是软链本身而非目标文件第三同一个目录还有别的子目录、隐藏文件在占用空间清理脚本只处理了一层目录下的.log.*文件子目录里面的文件并未涉及。排查步骤很固定先df -h确认空间是否真的没释放再du -sh /path/to/logdir看目录整体占用然后用ls -la或find /path -type f | wc -l看是否还有遗漏的大文件。如果确认是“deleted 占句柄”直接lsof | grep deleted找到对应 PID联系业务方或走变更流程重启。7.4 定时任务没有按预期执行有一种特别隐蔽的问题crontab 里写了脚本路径但没有给脚本加执行权限。chmod x忘了但在命令行里能跑是因为用了bash /opt/scripts/clean_logs.sh显式调用。crontab 直接写路径/opt/scripts/clean_logs.sh此时内核按 shebang 执行脚本没有执行权限会直接失败。解决方案要么给脚本加执行权限并保持路径可执行要么在 crontab 里写完整命令bash /opt/scripts/clean_logs.sh。两种方案选哪种建议统一用“脚本自身加权限”的方式因为后续排查更直观看到脚本文件权限绿色-rwxr-xr-x就能确定权限层没问题。比这更隐蔽的是 crontab 里文件路径和脚本内部引用的相对路径问题。脚本内部如果用了相对路径比如LOG_DELETE_RECORDcleaner.log在 crontab 这种非交互环境下当前工作目录可能是/root或其他随机路径日志就会写到意想不到的位置。所以脚本里的关键路径一律用绝对路径。7.5 演练模式与真实模式切换时忘了改DELETE_DRY_RUNtrue初始设计是安全措施但如果上线后忘了改成 false脚本会一直“演练”而不做任何删除导致磁盘持续增长。解决方式如果你确认要在某个目标环境真实执行前跑一遍grep DELETE_DRY_RUN script.sh确认状态。更稳妥的做法是把“启用真实删除”的开关做成命令行参数比如./clean_logs.sh --real没传这个参数默认就是演练。7.6 文件数量达到数万级时 find 循环变慢前面说过 shell 在超大文件量下不一定快。如果某个日志目录下有几万个文件findwhile read循环处理耗时可能十几分钟甚至更长此时find -exec rm或find -delete会快很多但损失了逐条日志记录与判断的灵活性。折中思路先find统计文件数量若超过 5000 个则进入批删模式if [ $(find $dir -maxdepth 1 -type f | wc -l) -gt 5000 ]; then find $dir -maxdepth 1 -type f -name *.log.* -mtime $RETENTION_DAYS -delete log_msg 目录 $dir 文件过多启用 find -delete 快速清理 else clean_by_time $dir $RETENTION_DAYS fi批删模式牺牲了逐条日志记录-delete没有 Python 循环有钩子但换来速度与可靠性。实际日志目录很少到这个级别但大型业务系统一旦累积这个策略就很重要。8. 脚本的进一步优化方向与个人心得8.1 把清理策略做成配置文件随着管理的服务器越来越多脚本里的配置区写死在文件里会越来越难维护。一个自然的演进方向是把配置提取成.conf文件脚本里source读取。这样同一套脚本可以配合不同的配置跑在不通的服务器上运维只需要维护配置不需要改代码。# /etc/log_cleaner/myapp.conf LOG_DIRS(/var/log/myapp /data/applogs) RETENTION_DAYS15 MAX_SIZE_MB500 DELETE_DRY_RUNfalse脚本里改成CONFIG_FILE${1:-/etc/log_cleaner/default.conf} [ -f $CONFIG_FILE ] source $CONFIG_FILE这样每次加一台新服务器、新业务目录只要拷贝一份配置改改路径和天数就完成接入不用碰主脚本。我实际维护的 40 多台服务器最后都是这个模式在跑。8.2 清理结果统计每周汇总一次脚本本身每次执行都有输出但这些信息分散在各天的日志里不适合长期观察趋势。可以加一个汇总功能每周日跑一次统计把这周的清理文件数、释放空间、剩余磁盘空间拼成一行发到群机器人或写到一个 summary 文件。观察数据可以帮你判断周一清理的文件多不多周中的写入量是否异常增长某天清理量突增是不是业务日志级别写错了(比如 DEBUG 级别忘了关)这些是运维监控的额外价值脚本不只是“删文件”的工具也是日志写入模式的“侦察兵”。8.3 关于 crontab 的环境变量坑再补一刀有些细节建议crontab 的MAILTO环境变量也会影响邮件通知。如果没有配置有效的收件地址crontab 会默认把标准输出发到系统账户的邮箱时间长了会攒一堆积压邮件占磁盘。建议在 crontab 文件顶部显式写MAILTO禁用自动发信或者指向真正能接收的运维邮箱。8.4 脚本上线前要不要做备份脚本本身和配置都属于运维资产建议纳入 git 管理。上线前至少先放到一个独立的目录结构里跟业务代码混在一起很容易被误删或环境污染。实操建议的目录结构是/opt/scripts/ ├── clean_logs.sh ├── config/ │ ├── myapp.conf │ └── default.conf └── logs/ └── cleaner.log保持一个脚本对应一个配置目录的约定整个运维体系也会清爽很多。如果你在用 Ansible、SaltStack 之类的批量管理工具这个脚本可以打包成 role 或 module 下发到目标机器统一管理清理策略这是上规模之后自然要走的路。8.5 最后一点个人心得我自己在多次排查日志相关故障中总结了一些教训日志清理脚本的设置可以很复杂但“什么是可删的日志”这件事必须跟业务方反复对齐。很多事故源于“我认为这个日志可以删了”而实际业务方还要留着溯源或做审计。所以脚本上线前最好把保留天数、清理范围、通知人列成一个表格发给业务确认让规则变成双方共识而不只是某个运维同学的个人判断。我推荐的保留策略参考值普通应用日志保留 7-15 天安全审计日志按合规要求建议保留 180 天以上异常定位用的 trace 日志可以按项目需求保留 30 天大文件日志(超过 1GB)建议直接切分压缩归档不长期留在热路径上。这些数值不是拍脑袋定的而是围绕“磁盘成本、排障需求、合规要求”三条线平衡出来的经验值可以结合你实际服务器的磁盘容量做调整。脚本写完到现在已经在我管理的多组环境上稳定跑了半年多每天的清理动作都在深夜静默执行偶尔看一眼汇总邮件磁盘水位一直维持在正常区间。如果你也在被日志磁盘占用困扰照着这篇文章里的脚本、配置和排查步骤跑一遍应该能解决大部分问题。如果还有别的特殊场景比如多服务器批量管理、需要对接云监控告警欢迎顺着这些方向继续深入这套清理机制完全可以作为一个基础模块往更多功能上生长。
返回列表