rsyslog + Logrotate进行日志记录并切割压缩


目标描述

Golang程序将日志写入到rsyslog的LOCAL0中。

所以这里要使用rsyslog记录来自LOCAL0的日志,几点需求:

  1. 日志存储在/xxx/程序名/程序名.log下。
  2. 要对日志进行切割,压缩。

这里主要需要研究的功能就是:

  1. rsyslog的日志动态路径。
  2. Logrotate的定期日志切割。
  3. Logrotate的日志压缩。

另外:

这里系统为Centos7,不涉及远程日志记录。

关于rsyslog和Logrotate的介绍网上有很多:

Linux rsyslog服务

Rsyslog日志系统

日志切割之Logrotate

高效的log工具:Logrotate


rsyslog的配置


基本配置

rsyslog的配置文件位于/etc/rsyslog.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
# Log all the mail messages in one place.
mail.* -/var/log/maillog
# Log cron stuff
cron.* /var/log/cron
# Everybody gets emergency messages
*.emerg :omusrmsg:*
# Save news errors of level crit and higher in a special file.
uucp,news.crit /var/log/spooler
# Save boot messages also to boot.log
local7.* /var/log/boot.log
...

上面的配置中,所使用的*就表示通配,例如mail.*就表示来自mail的所有级别日志都记录到 -/var/log/maillog中。*.emerg则表示所有emerg级别以上的日志(其实emerg已经是最高级别)都发送给所有正在登陆的用户。

rsyslog有三种配置格式basicadvancedobsolete legacy,它们可以在同一个配置文件中混用,但是官方建议尽量避免使用obsolete legacy配置格式, 因为这个不健康,官方原话:Do not use obsolete legacy format. It will make your life miserable.

1
2
3
4
5
6
7
8
9
# basic
mail.info /var/log/mail.log
# advanced
mail.info action(type="omfile" File="/var/log/mail.log")
# legacy(下面例子的含义和上面不一样,它定义一个Template)
# 总之一般带着 $ 的语句格式都是 legacy
$template DynFile,"/var/log/%HOSTNAME%/%programname%.log"

这里如果要让自己的程序使用rsyslog来记录日志,那么就可以使用LOCAL0~6这些facility来进行我们的自定义日志记录,


配置日志动态路径

例如一条在rsyslog接收到一条日志之后,希望它能将根据日志附带的tag信息, 将日志打印到 /logdir/tag/tag.log 文件,这就是动态日志路径。

要达到这个目的,使用rsyslog配置中的Templates语法结构即可。

Templates

这里可以使用Templates来定义一段字符串,字符串中可以带有变量,这样就可以达到日志记录位置随日志的tag信息而变化的目的。

修改 /etc/rsyslog.conf :

1
2
3
template(name="MyDynFile" type="string" string="/var/log/%programname%/%programname%.log")
local0.* action(type="omfile" dynaFile="MyDynFile")

上面的配置中,首先定义了一个string类型的Template,然后将local0的所有日志输入到这个路径下的日志文件中,

这里的%programname%即表示:

the “static” part of the tag, as defined by BSD syslogd. For example, when TAG is “named[12345]”, programname is “named”.

也就是说这里将日志保存到/var/log/%programname%/%programname%.log路径之下,更多的字段可以参考rsyslog Properties

测试配置:

首先使用命令看配置是否有语法错误:

1
$ /usr/sbin/rsyslogd -f /etc/rsyslog.conf -N1

重启rsyslog服务:

1
$ sudo systemctl restart rsyslog

测试配置是否达到预期:

1
2
3
4
$ logger -t test -p local0.info "hello world"
$ cat /var/log/test/test.log
Sep 23 11:38:32 node1 test: hello world

可以看到这里动态日志路径配置成功。


Logrotate 配置

上面完成了使用rsyslog来进行日志记录,但是如果不对日志进行切割压缩,日志的大小就会无限增长,不仅将来不好查询,而且占存储空间, 这里可以使用Logrotate来进行日志的切割与压缩。

Logrotate并不是一个一直运行的linux程序,它的自动运行是linux的计划任务cron来实现的,位于/etc/cron.daily/logrotate, 它每天执行一次。

而Logrotate的配置文件则位于/etc/logrotate.conf,这个配置文件里面又包含了目录/etc/logrotate.d/下的所有配置文件, 所有通常在/etc/logrotate.d/目录下来添加自定义的配置文件。

切割日志

  • 方法一:将原日志文件重命名,重新创建新的日志文件,通知使用此日志的进程使用新的日志文件。对应Logrotate中的create
  • 方法二:先将原日志文件复制,然后截断原文件,这样不需要通知使用此日志的进程,但两个操作之间有短暂的时间间隙,可能会丢失日志。对应Logrotate中的copytruncate
  • 方法三:只复制原日志文件。对应Logrotate中的copy

这里之所以有三种切割日志的方法,原因在于当一个程序获取到一个文件句柄并向里面写入数据时,即使此时文件名发生了变化, 也不会影响之前的文件句柄的使用,程序仍然可以通过这个文件句柄写入数据到此文件中,如果不对原程序发出通知,让其重新获取文件句柄, 那么这个日志文件的大小就会继续增长。

压缩日志:经过日志切割,原日志文件已经重命名,已经没有进程再继续使用它,这时便可以进行任意的操作,想要压缩就可以直接进行压缩。

rotate:保留日志文件的数量(轮转数量)。例如rotate为3,切割出来日志文件为log.1log.2log.3,则下一次再进行切割时, 会将log.3删除,log.2重命名为log.3log.1重命名为log.2,新切割出来的日志文件命名为log.1


Logrotate的具体配置。

这里添加一个新的配置文件到/etc/logrotate.d/目录下,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/var/log/*/*.log {
create 0644 root root // 新创建日志文件的权限
daily // 每天执行一次
rotate 65535 // 轮转数量 65535,基本等于存储所有日志
size 1M // 超过1M才进行切割
dateext // 使用日期作为后缀
dateformat -%Y%m%d.%s // 定义日期后缀的格式
missingok // 没有找到日志文件也OK
notifempty // not ifempty 如过日志文件为空,则不切割
compress // 切割后进行压缩
sharedscripts // 脚本只执行一次
postrotate // 脚本,用于通知rsyslog使用新的日志文件
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript
}

这里首先配置的路径为/var/log/*/*.log,因为上面rsyslog将LOCAL0的日志记录到了/var/log/%programname%/%programname%.log, 这样便能够通配到所有LOCAL0的日志文件。

注意到这里的最后几句配置:

1
2
3
postrotate // 脚本,用于通知rsyslog使用新的日志文件
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript

其中夹在postrotateendscript中间的就是脚本,postrotate表示脚本在rotate之后运行,也就是此时已经将原日志文件重命名, 并且创建了新日志文件,但是还没有对原日志文件进行压缩。脚本中使用kill -HUP来通知rsyslog使用新日志文件,脚本执行完毕后, Logrotate将对原日志文件使用gzip进行压缩。

这里切割出来的日志文件将保存在与原日志文件同一目录下,如果想要将切割出来的日志文件保存到别的目录,可以添加下面的配置:

1
olddir /var/log/old

这样切割下来的日志文件就会存储在/var/log/old目录之下(old目录需要手动创建)。


Logrotate 日志压缩

默认情况下,Logrotate使用gzip进行压缩,当然也可以配置其它的压缩工具。

如果要使用bzip2来进行压缩,则可以使用下面的配置:

1
2
3
4
compress
compresscmd /usr/bin/bzip2
compressext .bz2
compressoptions -9

那么在压缩日志时,就会使用/usr/bin/bzip2 -9来进行压缩。

使用bzip2可以达到更大的压缩比,但是在压缩过程中也会消耗更多的CPU。

同理也可以配置成其它的压缩工具。


Logrotate 定期执行

Logrotate通过cron来定期运行,默认配置在/etc/cron.daily/logrotate,也就是默认每天执行一次。

使用下面命令可以让Logrotate立即执行某一配置,而不用等待计划任务:

1
2
3
4
5
6
7
8
// 立即执行syslog配置
$ logrotate /etc/logrotate.d/syslog
// 使用debug模式执行,并不改变和生成任何日志文件
$ logrotate -d /etc/logrotate.d/syslog
// 强制执行,忽略size参数
$ logrotate -f /etc/logrotate.d/syslog

如果想要更灵活的执行时间配置,就可以在cron中来添加,例如想要Logrotate每分钟执行一次,则可以在/etc/crontab中进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo vim /etc/crontab
$ cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
*/1 * * * * root logrotate /etc/logrotate.d/syslog

这样就可以使得Logrotate每分钟执行一次。


配置总结

rsyslog:

1
2
3
$umask 0000
template(name="MyDynFile" type="string" string="/data1/log/%programname%/%programname%.log")
local0.* action(type="omfile" dynaFile="MyDynFile" dirCreateMode="0755" fileCreateMode="0644" ioBufferSize="64K")

这里的umask配合dirCreateModefileCreateMode来使用,这样便可指定创建出来的文件夹与文件的默认权限。

Logrotate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/data1/log/*/*.log {
create 0644 root root
daily
rotate 65535
size 1M
dateext
dateformat -%Y%m%d.%s
missingok
notifempty
compress
sharedscripts
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript
}

补充:docker内使用rsyslog + Logrotate

今天要在docker中配置rsyslog + Logrotate,发现centos7的docker中没有自带rsyslog和cron,并且没有systemd,所以不能通过systemctl来操作服务。

首先需要安装rsyslog和cron:

1
# yum install -y rsyslog cronie

非常重要的一点在于:rsyslog 默认通过 journal 读取日志信息,但CentOS镜像默认并未安装systemd和journald。

首先kill掉正在运行的rsyslog进程,然后修改配置:

  • 注释$ModLoad imjournal
  • 注释$IMJournalStateFile imjournal.state
  • $OmitLocalLogging on改为$OmitLocalLogging off
  • 将journal的配置删除:rm -rf /etc/rsyslog.d/listen.conf

启动rsyslog:

1
# rsyslogd

启动cron:

1
# crond

补充:修改rsyslog的日志记录格式

首先可以看到默认的rsyslog的格式:

1
Sep 30 17:21:49 localhost test: Hello World!!!

它所对应的配置格式是:

1
2
3
template(name="FileFormat" type="string"
string= "%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"
)

可以看到其实有点儿丑,为了把它变得好看一点,需要新建一个template来定义它的格式。

官方文档中这部分定义的字段都可以使用:

这里定义的template如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template(name="MyOutFmt" type="list") {
constant(value="[")
property(name="syslogseverity-text")
constant(value="]")
constant(value="[")
property(name="timereported" dateformat="year")
constant(value="-")
property(name="timereported" dateformat="month")
constant(value="-")
property(name="timereported" dateformat="day")
constant(value=" ")
property(name="timereported" dateformat="hour")
constant(value=":")
property(name="timereported" dateformat="minute")
constant(value=":")
property(name="timereported" dateformat="second")
constant(value=" ")
property(name="timereported" dateformat="tzoffsdirection")
property(name="timereported" dateformat="tzoffshour")
property(name="timereported" dateformat="tzoffsmin")
constant(value="]")
constant(value="[")
property(name="programname" position.from="1" position.to="32")
constant(value="]")
constant(value=": ")
property(name="msg")
constant(value="\n")
}

配置输出文件的templateMyOutFmt

1
local0.* action(type="omfile" dynaFile="MyDynFile" template="MyOutFmt" dirCreateMode="0755" fileCreateMode="0644" ioBufferSize="64K")

测试效果:

1
2
3
4
5
6
7
8
9
$ logger -t test -p local0.info 'Hello World!!!'
$ logger -t test -p local0.warning 'Hello World!!!'
$ logger -t test -p local0.err 'Hello World!!!'
$ tail -n3 /data1/log/test/test.log
[warning][2019-09-30 18:13:25 +0800][test]: Hello World!!!
[err][2019-09-30 18:13:31 +0800][test]: Hello World!!!
[err][2019-09-30 18:14:19 +0800][test]: Hello World!!!

ok,顺眼多了。


参考

The rocket-fast Syslog Server

logrotate(8) - Linux man page

docker容器中使用rsyslogd

---------------------------------END---------------------------------