使用`nohup command &`启动一个后台服务时,所有的输出默认都会重定向到当前目录下的`nohup.out`文件。如果这个服务运行数周或数月,这个日志文件可能会增长到数GB甚至更大,不仅占用宝贵的磁盘空间,还让查找特定日志变得困难。更糟糕的是,如果磁盘因此被占满,可能导致服务崩溃。手动处理这些日志既低效又不可靠,因此我们需要一套自动化的方案来实现日志的定时拆分和清理。
首先需要明白,直接删除或移动一个正在被进程写入的日志文件会带来问题。如果你简单地执行`rm nohup.out`或`mv nohup.out nohup.out.old`,进程仍然持有原文件的文件描述符,会继续向已移动的文件(`nohup.out.old`)写入,而新的`nohup.out`文件会被创建但进程不会向其中写入。正确的方法需要确保日志写入能无缝切换到新文件。
方法一是使用Shell脚本实现拆分与清理。我们可以编写一个Shell脚本,定期执行以下操作:复制当前日志内容到带时间戳的备份文件,然后清空原日志文件。以下是一个健壮的实现:
#!/bin/bash
# 文件名:rotate_nohup_log.sh
# 功能:拆分nohup.out日志并清理旧文件
# 设置日志目录和文件名(根据你的实际情况修改)
LOG_DIR="/home/your_app"
LOG_FILE="$LOG_DIR/nohup.out"
BACKUP_DIR="$LOG_DIR/log_backups"
# 如果日志文件不存在或为空,则退出
if [ ! -f "$LOG_FILE" ] || [ ! -s "$LOG_FILE" ]; then
exit 0
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 生成带日期时间戳的备份文件名
BACKUP_NAME="nohup_$(date +%Y%m%d_%H%M%S).log"
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
# 核心步骤:复制当前日志内容到备份文件
cp "$LOG_FILE" "$BACKUP_PATH"
# 清空原始日志文件(注意:使用truncate而不是删除)
truncate -s 0 "$LOG_FILE"
# 可选:压缩备份文件以节省空间
gzip -f "$BACKUP_PATH"
echo "$(date '+%Y-%m-%d %H:%M:%S') - 已拆分日志至 ${BACKUP_PATH}.gz" >> "$LOG_DIR/rotate.log"
# 清理超过30天的旧日志备份
find "$BACKUP_DIR" -name "nohup_*.log.gz" -mtime +30 -delete
这个脚本的关键点是使用`cp`命令复制日志内容而不是移动文件;使用`truncate -s 0`命令将原文件大小截断为0字节,而不是删除文件;进程会继续向同一个文件描述符写入,但文件现在已经是空的了;自动压缩备份文件并清理30天前的旧备份。
方法二是使用Linux内置的logrotate工具,对于更专业和标准化的管理,Linux自带的`logrotate`工具是更好的选择。它可以处理日志轮转、压缩、邮件通知等复杂需求。
首先为nohup日志创建一个logrotate配置文件,比如`/etc/logrotate.d/myapp-nohup`:
# /etc/logrotate.d/myapp-nohup
/home/your_app/nohup.out {
daily # 每天轮转一次
missingok # 如果文件不存在也不报错
notifempty # 如果文件为空则不轮转
compress # 轮转后压缩旧日志
delaycompress # 延迟一次再压缩,方便查看最新日志
copytruncate # 关键参数:先复制后截断,无需重启进程
create 0640 user group # 轮转后创建的新文件权限和属主
dateext # 使用日期作为轮转文件的后缀
dateformat -%Y%m%d # 日期格式
maxage 30 # 删除超过30天的备份
rotate 30 # 保留30个备份文件
# 如果轮转后需要执行特定命令(如重启服务),可以在这里添加
# postrotate
# /bin/kill -HUP $(cat /var/run/your_app.pid 2>/dev/null) 2>/dev/null || true
# endscript
}
`copytruncate`参数是这里的关键,它实现了与我们手动脚本相同的逻辑:先复制文件内容,然后清空原文件。这种方法不需要进程支持重打开日志文件。
测试配置是否正确:
# 调试模式运行,查看效果但不实际执行
sudo logrotate -d /etc/logrotate.d/myapp-nohup
# 强制执行一次轮转
sudo logrotate -vf /etc/logrotate.d/myapp-nohup
无论使用哪种方法,都需要设置定时任务来自动执行。
对于自定义脚本方法,使用crontab设置每天凌晨执行:
# 编辑当前用户的crontab
crontab -e
# 添加以下行,表示每天凌晨2点执行日志拆分脚本
0 2 * * * /bin/bash /path/to/rotate_nohup_log.sh
# 保存退出后,cron会自动加载新配置
对于logrotate方法,它通常已经配置为通过`/etc/cron.daily/logrotate`每天自动运行。你可以检查这个配置,如果需要不同的执行频率,可以调整cron设置。
如果你的服务器上运行着多个使用nohup的应用,可以为每个应用单独配置:
# 在脚本中处理多个日志文件
declare -a LOG_FILES=("/app1/nohup.out" "/app2/nohup.out")
for LOG_FILE in "${LOG_FILES[@]}"; do
if [ -f "$LOG_FILE" ] && [ -s "$LOG_FILE" ]; then
# 处理每个日志文件...
fi
done
在脚本中添加磁盘空间检查,当空间不足时提前清理:
# 检查磁盘使用率,超过85%时删除更早的备份
DISK_USAGE=$(df -h "$BACKUP_DIR" | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 85 ]; then
# 删除超过15天的备份
find "$BACKUP_DIR" -name "nohup_*.log.gz" -mtime +15 -delete
echo "$(date) - 磁盘使用率${DISK_USAGE}%,已清理15天前备份" >> /var/log/cleanup.log
fi
如果你的服务会定期重启,每次重启都会创建新的nohup.out文件。这时你可能需要按服务实例或启动时间来区分日志:
# 在启动服务时,使用带时间戳的日志文件名
START_TIME=$(date +%Y%m%d_%H%M%S)
nohup your_command > nohup_${START_TIME}.out 2>&1 &
# 然后调整脚本,处理所有nohup_*.out文件
确保脚本和日志目录有正确的权限。如果服务以特定用户运行,日志文件的所有权和权限需要相应设置:
# 设置正确的目录权限
chmod 755 /home/your_app
chmod 644 /home/your_app/nohup.out
# 如果服务以特定用户运行
chown app_user:app_group /home/your_app/nohup.out
通过实施这些自动化策略,你可以确保nohup日志得到有效管理,既释放磁盘空间,又保留必要的故障排查依据,同时保证服务的连续稳定运行。
CN
EN