自动归档日志的脚本怎么写

wen 实用脚本 1

自动归档日志的脚本怎么写?从零到生产级的完整指南

目录导读

  1. 为什么需要自动归档日志? —— 磁盘爆满的痛与合规需求
  2. 核心原理 —— 归档脚本必须解决的三个问题
  3. 实战脚本 —— 三套直接可用的Shell/Python方案
  4. 生产级优化 —— 压缩、轮转、异常通知与安全
  5. 常见问题与FAQ —— 你踩过的坑都在这里
  6. 总结与下一步 —— 从脚本到日志管理平台

为什么需要自动归档日志?

当你的应用日志以每天500MB的速度增长,而磁盘分区只有80GB时,不归档的后果只有一个:30天后服务崩溃,更严重的是,在金融或医疗行业,法律要求日志保留至少180天——归档不是选择,而是强制项

自动归档日志的脚本怎么写

手动用crontab执行tar看似简单,但真正的痛点在于:

  • 日志文件被进程占用:直接压缩会丢失最新写入的数据
  • 归档与清理脱节:只打包不删除,磁盘一样会满
  • 无失败重试机制:半夜脚本挂了没人发现

核心原理:归档脚本的三个关键决策

一个成熟的自动归档脚本,需要依次回答这三个问题:

  1. 何时归档? —— 基于文件大小(如>100MB)还是基于时间(如每天凌晨3点)?
  2. 如何安全归档? —— 使用copytruncate还是先rename再重建?
  3. 归档后如何处理原文件? —— 立即删除、保留N天、还是转存冷存储?

经验值:90%的场景采用“按大小触发 + rename + 延迟删除”组合,既保证数据完整性,又避免频繁IO。


实战脚本:三套直接可用的方案

方案A:纯Shell + logrotate(最推荐,零依赖)

其实logrotate已内置归档逻辑,你只需写一个配置:

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily                    # 按天归档
    rotate 7                 # 保留7份历史
    compress                 # 压缩为.gz
    delaycompress            # 延迟一天压缩,避免文件被占用
    copytruncate             # 先复制再清空原文件(适合不重启的进程)
    missingok                # 文件不存在不报错
    notifempty               # 空文件不归档
    create 0640 www-data www-data   # 新文件权限
}

配套cron任务(每天凌晨执行):

0 2 * * * /usr/sbin/logrotate -v /etc/logrotate.d/myapp >> /var/log/logrotate.log 2>&1

方案B:纯Shell脚本(可完全自定义)

#!/bin/bash
# 自动归档30天前的日志,并压缩删除
LOG_DIR=/var/log/myapp
ARCHIVE_DIR=/data/log_archive
RETENTION_DAYS=30
# 1. 创建归档目录
mkdir -p $ARCHIVE_DIR
# 2. 找到30天前的.log文件,打包后删除原文件
find $LOG_DIR -name "*.log" -mtime +$RETENTION_DAYS | while read file
do
    base=$(basename "$file")
    date_str=$(date -r "$file" +%Y%m%d)
    tar -czf "$ARCHIVE_DIR/${base}_${date_str}.tar.gz" -C "$LOG_DIR" "$base"
    if [ $? -eq 0 ]; then
        rm -f "$file" && echo "[OK] Archived and removed: $file"
    else
        echo "[FAIL] tar failed: $file" >> /var/log/archive_error.log
    fi
done

方案C:Python脚本(更灵活,适合复杂规则)

#!/usr/bin/env python3
import os, gzip, shutil, datetime, glob
LOG_DIR = "/var/log/myapp/"
ARCHIVE_DIR = "/data/archive/"
RETENTION_DAYS = 15
def rotate_file(path):
    """将日志文件压缩到归档目录,并清空原文件"""
    base = os.path.basename(path)
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    target = os.path.join(ARCHIVE_DIR, f"{base}.{timestamp}.gz")
    with open(path, 'rb') as f_in, gzip.open(target, 'wb') as f_out:
        shutil.copyfileobj(f_in, f_out, length=1024*1024)
    # 安全清空原文件(保留inode,进程不断写)
    open(path, 'w').close()
    print(f"Archived: {path} -> {target}")
if __name__ == "__main__":
    for log_file in glob.glob(LOG_DIR + "*.log"):
        # 检查大小超过100MB就归档
        if os.path.getsize(log_file) > 100 * 1024 * 1024:
            rotate_file(log_file)

生产级优化:必须补上的四个细节

压缩选型:gzip vs zstd

  • gzip:兼容性最好,但压缩速度慢(100MB/s)
  • zstd:比gzip快10倍,压缩率高30%,推荐用于大日志
    # 使用zstd替换gzip
    tar --zstd -cf "$file.tar.zst" -C "$LOG_DIR" "$base"

异常告警(一个都不能少)

在脚本关键步骤后增加退出码判断,并接入钉钉/邮件通知:

if [ $? -ne 0 ]; then
    curl -X POST -H "Content-Type: application/json" \
         -d '{"msgtype":"text","text":{"content":"日志归档失败: '$file'"}}' \
         https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN
fi

处理文件被占用的终极方案

rename + 信号触发(适合Java/Tomcat等会持有文件句柄的进程):

# 将当前日志改名,然后向进程发送USR1信号让应用重新开日志文件
mv app.log app.log.$(date +%Y%m%d)
kill -USR1 $(pgrep -f myapp.jar)   # 应用需实现USR1处理
sleep 2
gzip app.log.$(date +%Y%m%d) &

清理策略:双保险

  • 本地保留天数:例如只留本地7天压缩档
  • 远端存储同步:用aws s3 syncrsync推送到对象存储

常见问题与FAQ

Q1:为什么我的copytruncate会丢失少量日志?

copytruncate是“先复制再清空”,这两步之间写入的数据会丢失,如果应用允许,优先使用rename方案,若必须用,请配合delaycompress降低风险。

Q2:crontab里执行脚本,为什么总是找不到targzip

:crontab的PATH环境变量极小,每个命令需写全路径:

# crontab里这样写
0 2 * * * /usr/bin/find /var/log/myapp -name "*.log" -exec /usr/bin/gzip {} \;

或脚本开头加:export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Q3:归档后,应用日志中出现了空白或乱码?

:大概率是日志文件被移动位置后,应用通过文件名重新创建了新文件,但旧文件的文件描述符还在写,解决:检查应用是否支持日志文件“rename后自动重建”,否则只能用copytruncate

Q4:如何做“归档前校验数据完整性”?

:在生产脚本中,压缩前先用md5sum生成校验文件,压缩后再次计算原文件的哈希,对比一致才删除原文件。

Q5:多个应用共用磁盘,如何全局容量预警?

:在脚本最前面加一段磁盘配额检测:

DISK_USE=$(df /data | awk 'NR==2 {print $5}' | tr -d '%')
if [ $DISK_USE -gt 85 ]; then echo "磁盘超85%,强行清理5天前日志"; fi

总结与下一步

自动归档日志的脚本核心就三句话:什么时候归档、怎么归档、归档后原文件怎么处理,从简单的logrotate到自研Python脚本,没有银弹,关键是匹配你的应用类型和日志频率。

如果你还在手工敲tar命令,我强烈建议今天就把logrotate部署上——它已经足够应对80%的需求,更复杂的场景(如实时海量日志、多级归档策略),推荐转向ELK/Loki等日志平台,脚本只作为最后的兜底。

下次遇到日志爆盘,你该先检查这三件事

  1. 是否已经配置了归档策略?
  2. 归档命令的退出码有被检查吗?
  3. 归档后的压缩包是否同步到了远端?

打开你的终端,写第一版脚本吧——别忘了先测试,再上生产

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