1

[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 <logsize>                   # 当日志文件到达指定的大小时才转储(例如 10(字节), 100k,4M, 1G
maxsize <size>                    # 与时间间隔一同配置, 当日志文件超过该 maxsize 时, 即使未到指定时间间隔也会轮转
minsize <size>                    # 与时间间隔一同配置, 若日志文件未超过该 minsize 时, 即使到达指定时间间隔也不会轮转.

# 对旧日志操作方式(默认策略应该是重命名原日志文件)
默认方式                         # 修改日志文件名(实际是修改所属目录inode中的信息), 若该文件已被进程打开, 通常需要配置 postrotate 发送信号给该进程以重新打开日志文件.
copytruncate                    # 用于还在打开中的日志文件,把当前日志备份并截断,是先拷贝再清空的方式,拷贝和清空之间有一个时间差,可能会丢失部分日志数据
nocopytruncate                    # 备份日志文件但是不截断

# 压缩
compress                         # 压缩(gzip)日志文件的所有非当前版本
nocompress                        # (默认)不压缩
delaycompress                    # 本次转储的日志在下一次转储时才压缩.
nodelaycompress                    # (默认)不延迟压缩

# 邮件
mail <mail>                           # 转储的日志发送到指定邮箱
nomail                            # (默认)不发送转储的日志文件到邮箱

# 错误处理
errors <mail>                      # 转储时的错误信息发送到指定邮箱
missingok                        # 如果日志文件丢失,不要显示错误

# 空文件处理
notifempty                       # 如果日志文件为空,则不轮换日志文件(即忽略空文件).
ifempty                            # (默认)即使空文件也转储

# 存放目录
olddir <dir>                     # 转储后的日志放在指定目录下
noolddir                        # (默认)转储后的日志放在和当前日志文件同一个目录下

# 转储日志命名
dateext                            # 指定转储后的日志文件以当前日期为格式结尾,如 access.log-20200426.gz. 
                                # 默认是以自增序号, 比如 messages -> messages.1 -> messages.2
dateformat <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 <n>                       # 保留转储后的日志文件数量, 0指没有备份(轮转后马上删除旧日志), n指保留n个备份

# 创建新日志
create <mode> <user> <group>    # 轮换原始文件并创建具有指定权限、用户和组的新文件, 示例: 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配置项是互斥的。

这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:

  1. 重命名程序当前正在输出日志的程序。因为重命名只会修改目录文件的内容,而进程操作文件靠的是inode编号,所以并不影响程序继续输出日志。
  2. 创建新的日志文件,文件名和原来日志文件一样。虽然新的日志文件和原来日志文件的名字一样,但是inode编号不一样,所以程序输出的日志还是往原日志文件输出。
  3. 通过某些方式通知程序,重新打开日志文件。程序重新打开日志文件,靠的是文件路径而不是inode编号,所以打开的是新的日志文件。

什么方式通知程序我重新打开日志呢,简单粗暴的方法是杀死进程重新打开。很多场景这种作法会影响在线的服务,于是有些程序提供了重新打开日志的接口,比如可以通过信号通知nginx。各种IPC方式都可以,前提是程序自身要支持这个功能。

有个地方值得一提,一个程序可能输出了多个需要滚动的日志文件。每滚动一个就通知程序重新打开所有日志文件不太划得来。有个sharedscripts的参数,让程序把所有日志都重命名了以后,只通知一次。

方案2:copytruncate

如果程序不支持重新打开日志的功能,又不能粗暴地重启程序,怎么滚动日志呢?copytruncate的方案出场了。

这个方案的思路是把正在输出的日志拷(copy)一份出来,再清空(trucate)原来的日志。详细步骤如下:

  1. 拷贝程序当前正在输出的日志文件,保存文件名为滚动结果文件名。这期间程序照常输出日志到原来的文件中,原来的文件名也没有变。
  2. 清空程序正在输出的日志文件。清空后程序输出的日志还是输出到这个日志文件中,因为清空文件只是把文件的内容删除了,文件的inode编号并没有发生变化,变化的是元信息中文件内容的信息。

结果上看,旧的日志内容存在滚动的文件里,新的日志输出到空的文件里。实现了日志的滚动。

这个方案有两个有趣的地方。

  1. 文件清空并不影响到输出日志的程序的文件表里的文件位置信息,因为各进程的文件表是独立的。那么文件清空后,程序输出的日志应该接着之前日志的偏移位置输出,这个位置之前会被\0填充才对。但实际上logroate清空日志文件后,程序输出的日志都是从文件开始处开始写的。这是怎么做到的?这个问题让我纠结了很久,直到某天灵光一闪,这不是logrotate做的,而是成熟的写日志的方式,都是用O_APPEND的方式写的。如果程序没有用O_APPEND方式打开日志文件,变会出现copytruncate后日志文件前面会被一堆\0填充的情况。
  2. 日志在拷贝完到清空文件这段时间内,程序输出的日志没有备份就清空了,这些日志不是丢了吗?是的,copytruncate有丢失部分日志内容的风险。所以能用create的方案就别用copytruncate。所以很多程序提供了通知我更新打开日志文件的功能来支持create方案,或者自己做了日志滚动,不依赖logrotate。

指定每日0点转储文件

方式1: 修改anacrontab调用 logrotate 的时机(极其不推荐, 会影响其他服务的调用时机)

方式2: crontab 手动定义配置文件(目前个人推荐)

  1. 创建配置文件 /etc/logrotate_daily0.conf

    dateext
    missingok
    notifempty
    
    include /etc/logrotate_daily0.d
  2. 创建目录 /etc/logrotate_daily0.d

    主要不要使用默认目录, 否则会被正常的 logrotate 调度执行到
  3. 在上述目录下创建自己想要的配置文件
  4. 在 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 的调用(不推荐, 会影响其他已配置的日志轮转)

  1. 移除默认定时配置

    mv /etc/cron.daily/logrotate /usr/local/bin/logrotate.sh
  2. 在 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 ']';

img

img

img

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 xxx@x.x.x.x
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 <dir>\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

嘉兴ing
284 声望24 粉丝

PHPer@厦门