本文目录导读:

Linux Shell 脚本方案
使用 logrotate(推荐)
#!/bin/bash
# 日志切割脚本 - logrotate方式
# 配置 logrotate
cat > /etc/logrotate.d/myapp << 'EOF'
/var/log/myapp/*.log {
daily # 按天切割
rotate 30 # 保留30天
compress # 压缩旧日志
delaycompress # 延迟一天压缩
missingok # 日志缺失不报错
notifempty # 空文件不切割
create 0644 root root # 创建新文件的权限
sharedscripts # 所有日志共享一个脚本
postrotate
# 通知应用程序重新打开日志文件
kill -USR1 `cat /var/run/myapp.pid` 2>/dev/null || true
endscript
}
EOF
# 手动运行测试
logrotate -vf /etc/logrotate.d/myapp
纯 Shell 脚本
#!/bin/bash
# 日志切割脚本 - 手动方式
LOG_DIR="/var/log/myapp"
LOG_FILE="app.log"
DATE_FORMAT=$(date +%Y%m%d)
RETENTION_DAYS=30
# 切割日志函数
rotate_log() {
local log_path="$LOG_DIR/$LOG_FILE"
local backup_path="$LOG_DIR/${LOG_FILE}_${DATE_FORMAT}"
# 检查日志文件是否存在
if [ ! -f "$log_path" ]; then
echo "日志文件不存在: $log_path"
exit 1
fi
# 复制当前日志到备份文件
cp "$log_path" "$backup_path"
# 清空当前日志文件
> "$log_path"
# 压缩旧日志(可选)
gzip "$backup_path" 2>/dev/null || true
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 日志已切割: $backup_path"
}
# 清理过期日志
cleanup_old_logs() {
find "$LOG_DIR" -name "${LOG_FILE}_*" -type f -mtime +$RETENTION_DAYS -delete
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 已清理 $RETENTION_DAYS 天前的旧日志"
}
# 主函数
main() {
# 创建日志目录
mkdir -p "$LOG_DIR"
# 执行切割
rotate_log
# 清理旧日志
cleanup_old_logs
}
main
Python 脚本方案
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import gzip
import shutil
import logging
from datetime import datetime, timedelta
from logging.handlers import TimedRotatingFileHandler
class DailyLogRotator:
"""按天切割日志"""
def __init__(self, log_path, retention_days=30):
self.log_path = log_path
self.retention_days = retention_days
self.logger = None
def setup_logger(self, logger_name='myapp'):
"""设置按天切割的日志记录器"""
handler = TimedRotatingFileHandler(
filename=self.log_path,
when='midnight', # 每天午夜切割
interval=1, # 间隔1天
backupCount=self.retention_days, # 保留天数
encoding='utf-8',
delay=False
)
# 设置日志格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
# 创建logger
self.logger = logging.getLogger(logger_name)
self.logger.setLevel(logging.INFO)
self.logger.addHandler(handler)
return self.logger
def manual_rotate(self):
"""手动切割日志"""
if not os.path.exists(self.log_path):
print(f"日志文件不存在: {self.log_path}")
return False
# 生成备份文件名
backup_name = f"{self.log_path}.{datetime.now().strftime('%Y%m%d')}"
try:
# 复制并清空原日志
shutil.copy2(self.log_path, backup_name)
with open(self.log_path, 'w') as f:
f.truncate()
# 压缩旧日志
with open(backup_name, 'rb') as f_in:
with gzip.open(f"{backup_name}.gz", 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
# 删除未压缩的备份
os.remove(backup_name)
print(f"日志已切割并压缩: {backup_name}.gz")
return True
except Exception as e:
print(f"日志切割失败: {e}")
return False
def cleanup_old_logs(self):
"""清理过期日志"""
now = datetime.now()
cutoff_date = now - timedelta(days=self.retention_days)
for filename in os.listdir(os.path.dirname(self.log_path)):
if filename.startswith(os.path.basename(self.log_path)):
filepath = os.path.join(os.path.dirname(self.log_path), filename)
# 解析日志中的日期
try:
file_date = datetime.strptime(
filename.split('.')[-2], # 假设格式为 app.log.20240101.gz
'%Y%m%d'
)
if file_date < cutoff_date:
os.remove(filepath)
print(f"已删除过期日志: {filename}")
except (ValueError, IndexError):
continue
def main():
"""主函数"""
# 配置
config = {
'log_path': '/var/log/myapp/app.log',
'retention_days': 30
}
# 创建日志目录
os.makedirs(os.path.dirname(config['log_path']), exist_ok=True)
# 初始化日志切割器
rotator = DailyLogRotator(
log_path=config['log_path'],
retention_days=config['retention_days']
)
# 手动切割
rotator.manual_rotate()
# 清理旧日志
rotator.cleanup_old_logs()
# 设置自动切割的logger(用于应用程序中)
logger = rotator.setup_logger()
# 测试日志
logger.info("系统启动")
logger.info("日志切割完成")
if __name__ == "__main__":
main()
Java 方案(Log4j2)
<!-- log4j2.xml 配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<!-- 按天切割的 RollingFile -->
<RollingFile name="RollingFile"
fileName="/var/log/myapp/app.log"
filePattern="/var/log/myapp/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<!-- 按天切割 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<!-- 保留30天 -->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
使用定时任务
crontab 配置
# 每天0点执行日志切割 0 0 * * * /usr/local/bin/rotate_log.sh # 如果需要更精确的时间(比如每天23:59) 59 23 * * * /usr/local/bin/rotate_log.sh # 也可以每半小时执行一次检查 */30 * * * * /usr/local/bin/rotate_log.sh
systemd timer (推荐)
# /etc/systemd/system/log-rotate.service [Unit] Description=Daily Log Rotation Service [Service] Type=oneshot ExecStart=/usr/local/bin/rotate_log.sh User=root [Install] WantedBy=multi-user.target
# /etc/systemd/system/log-rotate.timer [Unit] Description=Run log rotation daily [Timer] OnCalendar=daily Persistent=true [Install] WantedBy=timers.target
Docker 容器方案
# Dockerfile
FROM alpine:latest
# 安装 logrotate
RUN apk add --no-cache logrotate bash
# 添加 logrotate 配置
COPY logrotate.conf /etc/logrotate.conf
COPY rotate_log.sh /usr/local/bin/rotate_log.sh
# 创建日志目录
RUN mkdir -p /var/log/myapp && \
chmod +x /usr/local/bin/rotate_log.sh
# 添加定时任务
RUN echo "0 0 * * * /usr/local/bin/rotate_log.sh" >> /var/spool/cron/crontabs/root
CMD ["crond", "-f", "-l", "2"]
使用建议
- 推荐使用 logrotate:系统自带,配置简单,功能强大
- 考虑SIGHUP信号:许多应用(如Nginx)支持重新加载日志
- 压缩旧日志:节省存储空间
- 设置保留天数:避免磁盘占满
- 错误处理:确保日志切割失败时有告警
- 测试环境验证:先在小范围测试
选择哪种方案取决于你的具体需求和技术栈,对于大多数Linux系统,logrotate是最简单可靠的选择。