文章目录
- 前言
- logrotate运行机制
- 方式1 contab模式
- 方式2 Systemd模式
- logrotate原理
- Linux 文件操作机制
- create
- copytruncate
- 运行logrotate
- logrotate参数说明
- 本文参考连接
前言
在Linux环境中能够帮助我们分析问题蛛丝马迹的有效办法之一便是日志,常见的如操作系统syslog日志/var/log/syslog,应用程序Nginx日志/var/log/nginx/*.log。但如果服务器数量较多,日志文件大小增长较快,不断消耗磁盘空间就会触发告警,如果需要人为定期按照各种维度去手动清理日志就显得十分棘手。为了节省空间和方便整理,可以将日志文件按时间或大小分成多份,删除时间久远的日志文件,这就是通常说的日志滚动(log rotation)。logrotate(GitHub地址) 诞生于 1996/11/19 是一个Linux系统日志的管理工具,本文会详细介绍Linux日志切割神器logrotate的原理和配置。
logrotate 是一个 linux 系统日志的管理工具。可以对单个日志文件或者某个目录下的文件按时间 / 大小进行切割,压缩操作;指定日志保存数量;还可以在切割之后运行自定义命令。
logrotate 是基于 crontab 运行的(准确来说是基于timer和crontab定时机制),所以这个时间点是由 crontab 控制的,具体可以查询 crontab 的配置文件 /etc/anacrontab。 系统会按照计划的频率运行 logrotate,通常是每天。在大多数的 Linux 发行版本上,计划每天运行的脚本位于 /etc/cron.daily/logrotate。
主流 Linux 发行版上都默认安装有 logrotate 包,如果你的 linux 系统中找不到 logrotate, 可以使用 apt-get 或 yum 命令来安装。
logrotate运行机制
logrotate 在很多 Linux 发行版上都是默认安装的。系统会定时运行 logrotate,一般是每天一次。系统是这么实现按天执行的。crontab 会每天定时执行 /etc/cron.daily 目录下的脚本,而这个目录下有个文件叫 logrotate。在 ubuntu 上脚本内容是这样的:
方式1 contab模式
1)检查Cron服务的状态:
systemctl status cron.service
2)检查执行Logrotate的定时任务
#cat /etc/crontab
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
定时任务不执行一般问题都出现在后面的命令,所以检查一下,手动执行“test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )”,看是否报错。
3)检查定时任务下logrotate脚本
cron task:/etc/cron.daily/logrotate,每天运行一次logrotate脚本。
#!/bin/sh
# skip in favour of systemd timer
# 如果systemd存在的话,就使用systemd timer启动,不再使用cron.daily拉起logrotate
if [ -d /run/systemd/system ]; then
exit 0
fi
# this cronjob persists removals (but not purges)
if [ ! -x /usr/sbin/logrotate ]; then
exit 0
fi
# 执行logrotate应用,其配置文件为/etc/logrotate.conf
/usr/sbin/logrotate /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
/usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit $EXITVALUE
再来看一下/etc/logrotate.conf配置文件内容:
# see "man logrotate" for details
# rotate log files weekly
weekly
# use the adm group by default, since this is the owning group
# of /var/log/syslog.
su root adm
# keep 4 weeks worth of backlogs
rotate 4
# create new (empty) log files after rotating old ones
create
# use date as a suffix of the rotated file
#dateext
# uncomment this if you want your log files compressed
#compress
# packages drop log rotation information into this directory
# /var/log下每种日志的具体配置,可以看/etc/logrotate.d/ 目录下配置文件的具体配置
include /etc/logrotate.d
# system-specific logs may be also be configured here.
比如syslog的配置在/etc/logrotate.d/rsyslog中:
/var/log/syslog
{
su root root
rotate 7
daily
missingok
notifempty
delaycompress
compress
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
/var/log/mail.info
/var/log/mail.warn
/var/log/mail.err
/var/log/mail.log
/var/log/daemon.log
/var/log/kern.log
/var/log/auth.log
/var/log/user.log
/var/log/lpr.log
/var/log/cron.log
/var/log/debug
/var/log/messages
{
su root root
rotate 4
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
4)检查logrotate服务的状态
#systemctl status logrotate.service
● logrotate.service - Rotate log files
Loaded: loaded (/lib/systemd/system/logrotate.service; static; vendor preset: enabled)
Active: failed (Result: exit-code) since Wed 2021-12-08 00:00:03 +08; 16h ago
TriggeredBy: ● logrotate.timer
Docs: man:logrotate(8)
man:logrotate.conf(5)
Main PID: 2811680 (code=exited, status=1/FAILURE)
Dec 08 00:00:01 systemd[1]: Starting Rotate log files...
Dec 08 00:00:03 logrotate[2811680]: error: failed to rename /usr/squid/logs/access.log to /usr/squid/logs/access.log-20211208: Read-only file system
这里通过TriggeredBy:logrotate.timer可以发现logrotate确实是通过Systemd timer的方式来做定时任务的。
方式2 Systemd模式
使用Systemd timer模式主要是两个服务,一个是logrotate.service,logrotate.timer
1)查看logrotate.service的状态
#systemctl status logrotate.service
● logrotate.service - Rotate log files
Loaded: loaded (/lib/systemd/system/logrotate.service; static; vendor preset: enabled)
Active: failed (Result: exit-code) since Wed 2021-12-08 00:00:03 +08; 16h ago
TriggeredBy: ● logrotate.timer
Docs: man:logrotate(8)
man:logrotate.conf(5)
Main PID: 2811680 (code=exited, status=1/FAILURE)
Dec 08 00:00:01 systemd[1]: Starting Rotate log files...
Dec 08 00:00:03 logrotate[2811680]: error: failed to rename /usr/squid/logs/access.log to /usr/squid/logs/access.log-20211208: Read-only file system
TriggeredBy: logrotate.timer;这里可以知道这个服务是由logrotate.timer触发执行的;这里有Read-only file system的报错,主要是Systemd的logrotate.service文件中,对logrotate加了文件读写保护,当 ProtectSystem=full这个参数时,会把 /usr/, /boot, /efi, /etc 挂载为只读,如果是 ProtectSystem=strict 那么整个文件系统都会挂载为只读。刚好我的日志目录就在/usr/下,因此需要再加一个参数:ReadWritePaths=/usr/squid/logs即可,然后systemctl daemon-reload && systemctl restart logrotate.service。这里需要思考的问题是日志目录设置在/usr/下是否合理?
[Unit]
Description=Rotate log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
ConditionACPower=true
[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.conf
# performance options
Nice=19
IOSchedulingClass=best-effort
IOSchedulingPriority=7
# hardening options
# details: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
# no ProtectHome for userdir logs
# no PrivateNetwork for mail deliviery
# no ProtectKernelTunables for working SELinux with systemd older than 235
# no MemoryDenyWriteExecute for gzip on i686
PrivateDevices=true
PrivateTmp=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectSystem=full
RestrictRealtime=true
ReadWritePaths=/usr/squid/logs
2)检查logrotate.timer的状态
# systemctl status logrotate.timer
● logrotate.timer - Daily rotation of log files
Loaded: loaded (/lib/systemd/system/logrotate.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Thu 2021-08-05 11:08:00 +08; 4 months 3 days ago
Trigger: Thu 2021-12-09 00:00:00 +08; 7h left
Triggers: ● logrotate.service
Docs: man:logrotate(8)
man:logrotate.conf(5)
Trigger:可以看出他的触发时间
Triggers:可以看出他将触发的服务
查看logrotate.timer的配置
[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true
#Unit:真正要执行的任务,默认是同名的带有.service后缀的单元
[Install]
WantedBy=timers.target
3)检查logrotate脚本
使用logrotate切割的脚本一般都放在/etc/logrotate.d/ 下,因此我的squid切割也在这个目录下,可以通过logrotate -d /etc/logrotate.d/squid 检测一下,-d表示debug模式;在这里debug时候发现logrotate有一个status状态文件/var/lib/logrotate/status,也就是记录下文件logrotate的时间,在今天有做过rotate的话,那么就不会再一次进行,如果想要测试,可以编辑这个文件你想要测试的文件的时间,比如发现里面有一条日志:“/usr/squid/logs/access.log” 2021-12-9-14:33:28 说明今天做了切割了,还想做测试则可以改为:“/usr/squid/logs/access.log” 2021-12-7-14:33:28,那么就可以再一次测试了。注意:这个status的状态只能改时间,直接删除这一条是不生效的。
4)验证
systemctl restart logrotate.service
重新执行systemctl restart logrotate.service后,就可以看到被切割的日志;如:access.log-20211209等
logrotate原理
logrotate 是怎么做到滚动日志时不影响程序正常的日志输出呢?logrotate 提供了两种解决方案。 1. create 2. copytruncate
Linux 文件操作机制
介绍一下相关的 Linux 下的文件操作机制。
Linux 文件系统里文件和文件名的关系如下图。
目录也是文件,文件里存着文件名和对应的 inode 编号。通过这个 inode 编号可以查到文件的元数据和文件内容。文件的元数据有引用计数、操作权限、拥有者 ID、创建时间、最后修改时间等等。文件件名并不在元数据里而是在目录文件中。因此文件改名、移动,都不会修改文件,而是修改目录文件。
借《UNIX 环境高级编程》里的图说一下进程打开文件的机制。
进程每新打开一个文件,系统会分配一个新的文件描述符给这个文件。文件描述符对应着一个文件表。表里面存着文件的状态信息(O_APPEND/O_CREAT/O_DIRECT…)、当前文件位置和文件的 inode 信息。系统会为每个进程创建独立的文件描述符和文件表,不同进程是不会共用同一个文件表。正因为如此,不同进程可以同时用不同的状态操作同一个文件的不同位置。文件表中存的是 inode 信息而不是文件路径,所以文件路径发生改变不会影响文件操作。
create
这也就是默认的方案,可以通过 create 命令配置文件的权限和属组设置;这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:
1、重命名正在输出日志文件,因为重命名只修改目录以及文件的名称,而进程操作文件使用的是 inode,所以并不影响原程序继续输出日志。
2、创建新的日志文件,文件名和原日志文件一样,注意,此时只是文件名称一样,而 inode 编号不同,原程序输出的日志还是往原日志文件输出。
3、最后通过某些方式通知程序,重新打开日志文件;由于重新打开日志文件会用到文件路径而非 inode 编号,所以打开的是新的日志文件。
如上也就是 logrotate 的默认操作方式,也就是 mv+create 执行完之后,通知应用重新在新文件写入即可。mv+create 成本都比较低,几乎是原子操作,如果应用支持重新打开日志文件,如 syslog, nginx, mysql 等,那么这是最好的方式。
不过,有些程序并不支持这种方式,压根没有提供重新打开日志的接口;而如果重启应用程序,必然会降低可用性,为此引入了如下方式。
copytruncate
该方案是把正在输出的日志拷 (copy) 一份出来,再清空 (trucate) 原来的日志;详细步骤如下:
1、将当前正在输出的日志文件复制为目标文件,此时程序仍然将日志输出到原来文件中,此时,原文件名也没有变。
2、清空日志文件,原程序仍然还是输出到预案日志文件中,因为清空文件只把文件的内容删除了,而 inode 并没改变,后续日志的输出仍然写入该文件中。
如上所述,对于 copytruncate 也就是先复制一份文件,然后清空原有文件。
通常来说,清空操作比较快,但是如果日志文件太大,那么复制就会比较耗时,从而可能导致部分日志丢失。不过这种方式不需要应用程序的支持即可。
运行logrotate
具体 logrotate 命令格式如下:
logrotate [OPTION...] <configfile>
-d, --debug :debug 模式,测试配置文件是否有错误。
-f, --force :强制转储文件。
-m, --mail=command :压缩日志后,发送日志到指定邮箱。
-s, --state=statefile :使用指定的状态文件。
-v, --verbose :显示转储过程。
通常惯用的做法是配合 crontab 来定时调用。
crontab -e
# 每30分钟执行一次logrotate
*/30 * * * * /usr/sbin/logrotate /etc/logrotate.d/rsyslog > /dev/null 2>&1 &
# 或者
*/30 * * * * /usr/sbin/logrotate /etc/logrotate.conf > /dev/null 2>&1 &
logrotate参数说明
参数 | 说明 |
---|---|
daily weekly monthly yearly | 切割周期,多久切割一次。 daily : 切割周期为 每天 weekly : 切割周期为 每周 monthly : 切割周期为 每月 yearly: 切割周期为 每年 |
size size | 当日志文件到达指定的大小时才转储。 默认单位是 bytes。bytes (缺省) 及 KB (sizek) 或 MB (sizem) 如: size 30k、 size 50M |
rotate count | 日志文件保留备份的个数。默认是 0 。 如:0 指没有备份;5 指保留最近的5个备份,其余的全部删除。 |
maxage count | 保留多少天的日志文件。 如:5 指保留最近的5天的日志文件,其余的全部删除。 如果 日志文件按天转储,则 rotate 与 maxage 基本上是一样的。 |
tabooext [+] list | 让 logrotate 不转储指定扩展名的文件,缺省的扩展名是:.rpm-orig, .rpmsave, v, 和~ |
missingok | missing ok ,在日志轮循期间,错误将被忽略。 例如 “文件无法找到” 之类的错误。 |
copytruncate nocopytruncate | copytruncate: 用于还在打开中的日志文件,把当前日志备份并截断。 把正在输出的日志拷 (copy) 一份出来,再清空 (trucate) 原来的日志。 nocopytruncate: 备份日志文件,但是不截断 。 |
create mode owner group nocreate | create mode owner group:转储文件,使用指定的文件模式创建新的日志文件。 nocreate: 不建立新的日志文件。 如: create 644 root root 以指定的权限创建全新的日志文件,同时 logrotate 也会重命名原始日志文件。 |
nocompress compress | nocompress: 不压缩(默认) compress: 通过 gzip 压缩转储以后的日志。如 XXX.gz |
delaycompress nodelaycompress | delaycompress: 和 compress 一起使用时,转储的日志文件到下一次转储时才压缩。nodelaycompress: 覆盖 delaycompress 选项,转储同时压缩。 |
prerotate endscript | 在所有其它指令完成 前 执行。这两个关键字必须单独成行 |
postrotate endscript | 在所有其它指令完成 后 执行。这两个关键字必须单独成行 |
sharedscripts | 共享脚本,整个日志组运行一次脚本 。 比如 nginx 涉及到多个日志文件,一般使用 * 号 通配符:/var/log/nginx/*.log { ... } ,sharedscripts 的作用是在 所有的日志文件 都轮转完毕后 统一执行一次脚本 ;如果没有 sharedscripts ,那么 每个日志文件 轮转完毕后 都会执行一次脚本 。 |
errors address | 转储时的错误信息发送到指定的 Email 地址。 |
mail address nomail | mail address : 把转储的日志文件发送到指定的 E-mail 地址 nomail : (默认)转储时不发送日志文件 |
ifempty notifempty | ifempty :if empty ,即使是空文件也转储(默认)。 notifempty :not if empty ,如果是空文件的话,不转储。 |
olddir directory noolddir | olddir directory :储后的日志文件放入指定的目录,必须和当前日志文件在同一个文件系统。 noolddir : 转储后的日志文件和当前日志文件放在同一个目录下。 |
dateext | 在日志名称后面,添加日期后缀。默认是当前日期。默认是 -%Y%m%d 的后缀。可用 dateformat 选项扩展配置。如:nginx_access.log --> nginx_access.log-20210120 注意: 格式化日期中不要出现 分号(:) ,因为文件名称是不能含有分号的。 |
dateformat | 对 dateext 的拓展,必须配合 dateext 使用在V3.9.0 之前,只支持 %Y 、%m 、%d 、 %s 参数。%s 不是秒,是时间戳,也者 随机数,具体我没有验证。 在V3.9.0 及之后,支持 %H 参数。如: dateformat -%Y-%m-%d 结果:nginx_access.log --> nginx_access.log-2021-01-20 注意: 文件名称中禁止出现 分号(:) ,因为文件名称是不能含有分号的。 |
dateyesterday | 与 dateext 配合使用,日期提前一天。 比如,当前日期是2021-01-20,提前一天就是 2021-01-19 |
extension | 与 dateext 、dateformat 配合使用,指定文件的后缀。如:extension .log 结果:nginx_access.log --> nginx_access-2021-01-20.log 说明:nginx_access.log , 文件名是nginx_access,后缀是.log, 文件名 + -%Y-%m-%d 格式的日期 + .log 后缀,结果是 nginx_access-2021-01-20.log |
本文参考连接
https://blog.csdn.net/xiaojin21cen/article/details/122309230
https://zhuanlan.zhihu.com/p/90507023
https://zhuanlan.zhihu.com/p/444111643