[TOC]
Logrotate 日志管理工具
logrotate 是一个Linux系统默认安装了的日志文件管理工具,用来把旧文件轮转、压缩、删除,并且创建新的日志文件。我们可以根据日志文件的大小、天数等来转储,便于对日志文件管理。
默认 logrotate 是在每天凌晨 3 点多被 anacron 调用执行.
具体调用流程分析可以见 4. Shell篇.md#anacron 周期命令调度程序
日志管理工具
logrotate [选项] <配置文件>
选项
# 常用
-d, --debug # 调试模式,仅输出操作步骤,并不实际执行。隐式包含 -v 参数
-f, --force # 强制执行转储(logrotate 会根据状态文件, 自动判定是否有必要执行), 若配置文件刚修改完要马上执行建议使用该参数.
-s, --state=statefile # 使用指定的状态文件(而非默认的),对于运行在不同用户情况下有用
-v, --verbose # 显示转储过程的详细信息
# 不常用
-m, --mail=command # 发送邮件命令而不是用‘/bin/mail'发
文件
/etc/logrotate.conf # 默认配置文件
/var/lib/logrotate.status # 默认状态文件
默认配置文件
/etc/logrotate.conf
中一般会有一句include /etc/logrotate.d
用于加载该目录下的所有配置文件.可在
/etc/logrotate.d/
下放置自定义的配置文件来自动调用.- 默认的状态文件(记录每个处理的日志的最后时间?)
/var/lib/logrotate.status
参考:
配置选项
默认读取的配置 /etc/logrotate.conf
# 触发方式 - 时间间隔
hourly
daily
weekly
monthly
# 触发方式 - 文件大小
size # 当日志文件到达指定的大小时才转储(例如 10(字节), 100k,4M, 1G
maxsize # 与时间间隔一同配置, 当日志文件超过该 maxsize 时, 即使未到指定时间间隔也会轮转
minsize # 与时间间隔一同配置, 若日志文件未超过该 minsize 时, 即使到达指定时间间隔也不会轮转.
# 对旧日志操作方式(默认策略应该是重命名原日志文件)
默认方式 # 修改日志文件名(实际是修改所属目录inode中的信息), 若该文件已被进程打开, 通常需要配置 postrotate 发送信号给该进程以重新打开日志文件.
copytruncate # 用于还在打开中的日志文件,把当前日志备份并截断,是先拷贝再清空的方式,拷贝和清空之间有一个时间差,可能会丢失部分日志数据
nocopytruncate # 备份日志文件但是不截断
# 压缩
compress # 压缩(gzip)日志文件的所有非当前版本
nocompress # (默认)不压缩
delaycompress # 本次转储的日志在下一次转储时才压缩.
nodelaycompress # (默认)不延迟压缩
# 邮件
mail # 转储的日志发送到指定邮箱
nomail # (默认)不发送转储的日志文件到邮箱
# 错误处理
errors # 转储时的错误信息发送到指定邮箱
missingok # 如果日志文件丢失,不要显示错误
# 空文件处理
notifempty # 如果日志文件为空,则不轮换日志文件(即忽略空文件).
ifempty # (默认)即使空文件也转储
# 存放目录
olddir # 转储后的日志放在指定目录下
noolddir # (默认)转储后的日志放在和当前日志文件同一个目录下
# 转储日志命名
dateext # 指定转储后的日志文件以当前日期为格式结尾,如 access.log-20200426.gz.
# 默认是以自增序号, 比如 messages -> messages.1 -> messages.2
dateformat # 配合dateext使用,紧跟在下一行出现,定义日期格式,只支持 %Y %m %d %H %s 这5个参数(%s 是时间戳)
# hourly 默认使用时间格式: -%Y%m%d%H
# daily,weekly,monthly 默认使用时间格式: -%Y%m%d
# 示例: dateformat -%Y%m%d%s
# 保留转储文件数量
rotate # 保留转储后的日志文件数量, 0指没有备份(轮转后马上删除旧日志), n指保留n个备份
# 创建新日志
create # 轮换原始文件并创建具有指定权限、用户和组的新文件, 示例: create 700 root root
nocreate # (默认)不主动创建新的日志文件
# 脚本
sharedscripts # 对于整个日志组只运行一次脚本
prerotate # 引入一个在日志被轮换前执行的脚本, 需要和 endscript 严格配置. 关键字必须单独一行.
postrotate # 引入一个在日志被轮换后执行的脚本, 需要和 endscript 严格配置. 关键字必须单独一行.
endscript # 标记 prerotate 或 postrotate 脚本的结束, 关键字必须单独一行.
示例
nginx 示例
/data/nginx/logs/*log {
daily
rotate 32
missingok
notifempty
compress
delaycomporess
dateext
sharedscripts
postrotate
/bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || :
endscript
}
轮转完后日志会从/data/nginx/logs/access.log
变为/data/nginx/logs/access.log-20200426.gz
, 之类的日期是个示例.
可通过手动执行(指定配置文件)来调用:
logrotate /path/to/nginx_logrotate
日志轮转的机制
此处不讨论压缩等的后续操作.该部分内容来自: https://www.lightxue.com/how-...
若inode部分不理解, 可查看原文链接或查看笔记 3. 系统管理篇.md#文件、目录与 inode(i节点))
方案1:create
默认方案没有名字,姑且叫它create吧。因为这个方案会创建一个新的日志文件给程序输出日志,而且第二个方案名copytruncate是个配置项,与create配置项是互斥的。
这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:
- 重命名程序当前正在输出日志的程序。因为重命名只会修改目录文件的内容,而进程操作文件靠的是inode编号,所以并不影响程序继续输出日志。
- 创建新的日志文件,文件名和原来日志文件一样。虽然新的日志文件和原来日志文件的名字一样,但是inode编号不一样,所以程序输出的日志还是往原日志文件输出。
- 通过某些方式通知程序,重新打开日志文件。程序重新打开日志文件,靠的是文件路径而不是inode编号,所以打开的是新的日志文件。
什么方式通知程序我重新打开日志呢,简单粗暴的方法是杀死进程重新打开。很多场景这种作法会影响在线的服务,于是有些程序提供了重新打开日志的接口,比如可以通过信号通知nginx。各种IPC方式都可以,前提是程序自身要支持这个功能。
有个地方值得一提,一个程序可能输出了多个需要滚动的日志文件。每滚动一个就通知程序重新打开所有日志文件不太划得来。有个sharedscripts
的参数,让程序把所有日志都重命名了以后,只通知一次。
方案2:copytruncate
如果程序不支持重新打开日志的功能,又不能粗暴地重启程序,怎么滚动日志呢?copytruncate的方案出场了。
这个方案的思路是把正在输出的日志拷(copy)一份出来,再清空(trucate)原来的日志。详细步骤如下:
- 拷贝程序当前正在输出的日志文件,保存文件名为滚动结果文件名。这期间程序照常输出日志到原来的文件中,原来的文件名也没有变。
- 清空程序正在输出的日志文件。清空后程序输出的日志还是输出到这个日志文件中,因为清空文件只是把文件的内容删除了,文件的inode编号并没有发生变化,变化的是元信息中文件内容的信息。
结果上看,旧的日志内容存在滚动的文件里,新的日志输出到空的文件里。实现了日志的滚动。
这个方案有两个有趣的地方。
- 文件清空并不影响到输出日志的程序的文件表里的文件位置信息,因为各进程的文件表是独立的。那么文件清空后,程序输出的日志应该接着之前日志的偏移位置输出,这个位置之前会被
\0
填充才对。但实际上logroate清空日志文件后,程序输出的日志都是从文件开始处开始写的。这是怎么做到的?这个问题让我纠结了很久,直到某天灵光一闪,这不是logrotate做的,而是成熟的写日志的方式,都是用O_APPEND
的方式写的。如果程序没有用O_APPEND
方式打开日志文件,变会出现copytruncate后日志文件前面会被一堆\0
填充的情况。 - 日志在拷贝完到清空文件这段时间内,程序输出的日志没有备份就清空了,这些日志不是丢了吗?是的,copytruncate有丢失部分日志内容的风险。所以能用create的方案就别用copytruncate。所以很多程序提供了通知我更新打开日志文件的功能来支持create方案,或者自己做了日志滚动,不依赖logrotate。
指定每日0点转储文件
方式1: 修改anacrontab调用 logrotate 的时机(极其不推荐, 会影响其他服务的调用时机)
方式2: crontab 手动定义配置文件(目前个人推荐)
创建配置文件
/etc/logrotate_daily0.conf
dateext missingok notifempty include /etc/logrotate_daily0.d
创建目录
/etc/logrotate_daily0.d
主要不要使用默认目录, 否则会被正常的 logrotate 调度执行到
- 在上述目录下创建自己想要的配置文件
在 crontab 配置定时任务
0 0 * * * /usr/sbin/logrotate /etc/logrotate_daily0.conf &> /dev/null
一键脚本
cat > /etc/logrotate_daily0.conf <<'EOF'
dateext
rotate 7
missingok
notifempty
include /etc/logrotate_daily0.d
EOF
mkdir /etc/logrotate_daily0.d
cat >> /var/spool/cron/root <<'EOF'
# 0点执行日志轮转
0 0 * * * /usr/sbin/logrotate -f /etc/logrotate_daily0.conf &> /dev/null
EOF
crontab /var/spool/cron/root
cat > /etc/logrotate_daily0.d/nginx <<'EOF'
/data/nginx/logs/*.log {
missingok
notifempty
daily
compress
dateext
nocreate
rotate 31
sharedscripts
postrotate
/bin/kill -USR1 $(ps -ef|grep nginx|grep master|grep -v grep|awk '{print $2}') || true
endscript
}
EOF
方式3: 手动指定每日 logrotate 的调用(不推荐, 会影响其他已配置的日志轮转)
移除默认定时配置
mv /etc/cron.daily/logrotate /usr/local/bin/logrotate.sh
在 crontab 配置定时任务
0 0 * * * /bin/bash /usr/local/bin/logrotate.sh
Ansi
https://github.com/fidian/ansi
这是一个用于生成 ANSI 转义序列的脚本, 可用于:
- 移动光标
- 文本加粗
- 添加颜色
- ...
下载并使用
wget https://raw.githubusercontent.com/fidian/ansi/master/ansi -O /usr/local/bin/ansi
chmod a+x /usr/local/bin/ansi
ansi -h
示例
# 红色字体, 加粗
ansi --bold --bg-red "请用 root 账户执行本脚本";
echo -n '['; ansi -n --bold --green "DONE"; echo ']';
echo -n '['; ansi --bold --red "ERROR"; echo ']';
expect
expect是一个自动化交互套件,主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信
相关链接:
- expect - 自动交互脚本 (未读)
安装
yum install -y expect
注意 expect 脚本开头一行是 #!/usr/bin/expect
, 或者在执行时使用 expect <脚本>
来执行脚本.
命令
set timeout
设置超时时间
# -1 表示不限制执行超时时间
set timeout -1
默认是 30s, 若执行会超过这个时间, 要注意设置"超时时间", 避免脚本被强行中断
set
定义变量
set 变量名 变量值
# set password "123456"
puts
输出变量
spanw
交互程序开始后面跟命令或者指定程序
spanw ssh [email protected]
expect "*password"
send "123456\n"
# 进入交互模式
interact
expect
语法
expect "文本匹配"
支持通配符匹配expect eof
等待spawn的执行结束
expect "待匹配文本"
send "xxx\r"
expect "*支持通配符*"
send "xxx\r"
# 等价于上面两条, 更简洁
expect {
"待匹配的文本" { send "xxxx\r"; exp_continue }
"*支持通配符*" { send "xxx\r"}
}
# 等待 spawn 的脚本结束
expect eof
exp_continue
在expect中多次匹配就需要用到
expect {
"待匹配的文本" { send "xxxx\r"; exp_continue }
"*支持通配符*" { send "xxx\r"}
}
send
用于发送指定的字符串信息
expect "xxx"
send "xxx\r"
send_user
用来打印输出 相当于shell中的echo
send_user "echo something"
exit
interact
进入交互模式
关键字
if
流程控制
if { ... } {
# ...
}
lindex
获取数组的第N个参数
# 使用 lindex 关键字获取第N个参数
set 变量名 [lindex $argv 0]
set 变量名 [lindex $argv 1]
...
# 参数总数
if {$argc < 1} {
send_user "..."
exit
}
file
https://wxnacy.com/2018/05/31...
示例
示例: 一个 expect 脚本的模板
#!/usr/bin/expect
set PASSWORD "ssh密码"
if {$argc < 1} {
send_user "usage: $argv0 \n"
exit
}
set srcdir [lindex $argv 0]
if {[file isdirectory "$srcdir"] != 1} {
send_user "not exists dir: $srcdir\n"
exit
}
spawn bash deploy.sh $srcdir
# 这是一个自动输入 ssh 密码的应答
expect {
"yes/no" { send "yes\n"; exp_continue }
"password:" { send "${PASSWORD}\n" }
}
expect eof
上面是一个在不使用秘钥情况下完成 rsync 免手动输入密码的处理.
示例: 自动输入 mysql 密码
#!/usr/bin/expect
set password xxxx
set name root
spawn mysql -u $name -p
expect "*password:*"
send "$password\r"
interact
示例: 在 shell 中嵌套 expect
#!/bin/bash
for i in `cat /home/admin/timeout_login.txt`
do
/usr/bin/expect << EOF
spawn /usr/bin/ssh -t -p 22022 admin@$i "sudo su -"
expect {
"yes/no" { send "yes\r" }
}
expect {
"password:" { send "xxo1#qaz\r" }
}
expect {
"*password*:" { send "xx1#qaz\r" }
}
expect "*]#"
send "df -Th\r"
expect "*]#"
send "exit\r"
expect eof
EOF
done