Linux 日志管理

本文合并自:1-8-Linux系统服务与日志管理.md(日志管理部分)、03-Rsyslog日志服务.md、08-日志与系统信息.md、09-日志管理.md、05-日志轮转练习.md

适用环境:RHEL/CentOS 5(syslog)、RHEL/CentOS 6(rsyslog)、RHEL/CentOS 7(rsyslog + systemd-journald 并存)。

内容覆盖:日志的作用与分类、/var/log 下常见日志文件、二进制日志与专用查看命令、rsyslog 架构与配置语法、facility/level 全表、远程日志集中收集实操、logger 测试、systemd-journald 与 journalctl、logrotate 日志轮转(含完整练习与参考答案)、日志分析实战、auditd 审计、常见故障排错清单。

系统启动流程、运行级别与服务管理请见 08-01;ELK 日志分析平台请见 08-04-ELK日志分析.md;Zabbix 监控告警请见 08-05-Zabbix监控.md。


一、日志的作用与分类

1.1 什么是日志

日志用于记录系统或应用在运行过程中产生的信息。通过日志,运维人员可以:

  • 了解系统或应用的运行状态;
  • 排查故障、定位错误原因;
  • 审计安全事件(谁在什么时间做了什么);
  • 分析业务访问量、性能瓶颈与趋势。

系统日志记录系统上的各种活动:用户登入登出、系统开关机、Web/FTP 访问、端口扫描、服务启停、内核硬件检测等,供管理员排查故障、发现潜在问题。

1.2 日志系统的工作方式

Linux 的日志系统本身是一个系统服务。其他进程把日志信息发给日志服务,日志服务根据数据特征(服务类型 facility、级别 level)结合自身配置,将信息写入相应文件或做其他处理(转发、广播、丢弃等)。

应用进程 / 内核 → 日志服务(rsyslog / journald) → 按规则分流
                                                   ├── 写入 /var/log/*.log
                                                   ├── 输出到终端 / 广播用户
                                                   ├── 转发到远程日志服务器
                                                   └── 丢弃

1.3 日志的分类

分类 说明 典型位置 / 查看方式
内核日志 内核启动与运行期间产生的信息,硬件检测、驱动加载、OOM、磁盘错误等 内核环形缓冲区,dmesg、/var/log/dmesg、journalctl -k
系统日志 系统与各类守护进程的通用运行信息 /var/log/messages、journalctl
应用日志 各应用自行管理的日志(Apache、Nginx、MySQL、Tomcat 等) /var/log/httpd/、/var/log/nginx/、/var/log/mysqld.log 等
审计日志 内核审计子系统 auditd 记录的系统调用、文件访问、权限变更 /var/log/audit/audit.log,ausearch/aureport
用户登录日志 用户登录成功/失败、当前在线、最后登录时间 wtmp/btmp/utmp/lastlog(二进制),last/lastb/who/w/lastlog
安全认证日志 SSH、FTP、su、sudo、PAM 等认证与授权事件 /var/log/secure

1.4 各版本日志服务对照

系统版本 服务名称 主配置文件 说明
RHEL/CentOS 5 syslog /etc/syslog.conf 传统 syslogd,功能较少
RHEL/CentOS 6 rsyslog /etc/rsyslog.conf syslog 增强版:模块化、TCP/UDP 远程传输、模板、过滤、写数据库
RHEL/CentOS 7 rsyslog + systemd-journald /etc/rsyslog.conf、/etc/systemd/journald.conf journald 统一采集,可转发 rsyslog 落盘为文本,两者并存

rsyslog 是 syslog 的增强版本,支持模块化、TCP/UDP 远程传输、模板、过滤等更多特性。

查看 syslog 相关的设施与级别定义,可借助 xinetd 手册:

man xinetd.conf

查找 log_type:

log_type  determines where the service log output is sent. There are two formats:

        SYSLOG  syslog_facility [syslog_level]
        The  log  output  is  sent  to syslog at the specified facility. Possible facility names
        include: daemon, auth, authpriv, user, mail, lpr, news, uucp,  ftp  local0-7.  Possible
        level  names include: emerg, alert, crit, err, warning, notice, info, debug.  If a level
        is not present, the messages will be recorded at the info level.

二、/var/log 下常见日志文件对照表

2.1 总览表

日志文件 用途 查看方法
/var/log/messages 最重要的系统整体日志,大多数服务的通用信息,几乎所有系统重要错误都会记录在此 tail -f /var/log/messages、grep
/var/log/secure 登录认证与安全相关:SSH、FTP、Telnet、POP3、PAM、su、sudo tail -f /var/log/secure、grep Failed
/var/log/cron crond 定时任务执行日志 tail -f /var/log/cron
/var/log/boot.log 系统启动过程日志,也包含某些服务的启动日志 cat /var/log/boot.log
/var/log/dmesg 开机时内核检测过程中产生的硬件信息 cat /var/log/dmesg、dmesg
/var/log/maillog 邮件服务(postfix/sendmail)相关日志 tail -f /var/log/maillog
/var/log/spooler 新闻组、UUCP 等 spool 类日志(现代系统基本为空) cat /var/log/spooler
/var/log/yum.log yum 安装/卸载/更新软件包的操作日志 cat /var/log/yum.log
/var/log/acpid 高级电源管理(ACPI)日志 cat /var/log/acpid
/var/log/wtmp 所有用户登录、注销记录(二进制) last
/var/log/btmp 失败的登录记录(二进制) lastb
/var/run/utmp 当前登录用户信息(二进制) who、w
/var/log/lastlog 每个用户最后一次登录信息(二进制) lastlog
/var/log/audit/audit.log auditd 审计子系统日志 ausearch、aureport、tail
/var/log/httpd/access_log、error_log Apache 访问日志与错误日志(目录 /var/log/httpd/) tail -f、awk 统计
/var/log/nginx/access.log、error.log Nginx 访问日志与错误日志 tail -f、awk 统计
/var/log/mysqld.log(或 /var/log/mariadb/mariadb.log) MySQL/MariaDB 服务运行日志、启动失败原因、慢查询提示 tail -f /var/log/mysqld.log
/var/log/journal/ 或 /run/log/journal/ systemd-journald 的二进制日志库 journalctl

注意:wtmp、btmp、utmp、lastlog 等文件不是纯文本,必须使用 last、lastb、who、lastlog 等工具查看,直接 cat 会输出乱码。

2.2 常用查看命令

# 实时跟踪日志尾部(排障最常用)
tail -f /var/log/messages
tail -f /var/log/secure
tailf /var/log/messages          # 旧系统上的等价命令

# 查看最后 N 行
tail -n 100 /var/log/messages
tail -100 /var/log/messages

# 从头分页阅读
less /var/log/messages
more /var/log/messages

# 关键字检索
grep -i error /var/log/messages
grep -n 'Failed password' /var/log/secure

# 查看日志文件大小与目录占用
ls -lh /var/log/
du -sh /var/log/
du -sh /var/log/* | sort -rh | head -10

三、二进制日志与专用命令

3.1 wtmp 与 last

/var/log/wtmp 记录所有用户的登录与注销历史,是二进制文件,用 last 读取。

last                                     # 所有用户登入登出记录
last -F                                  # 打印完整时间(含年份与秒)
last -n 10                               # 只看最近 10 条
last root                                # 只看 root 的登录记录
last -x                                  # 显示系统关机、运行级别变更等记录
last -x | grep -E '^(shutdown|reboot)'   # 查看开机/关机时间
last -f /var/log/wtmp.1                  # 指定读取轮转后的旧文件

说明:

  • last 读取 /var/log/wtmp(二进制);
  • 删除用户后 last 仍能看到其历史记录,而 lastlog 则看不到。

3.2 btmp 与 lastb

/var/log/btmp 记录失败的登录尝试,是排查暴力破解的关键。

lastb                       # 登录失败记录(默认仅 root 可查看)
lastb -n 20                 # 最近 20 条失败记录
lastb | head -50
ls -l /var/log/btmp
# -rw-------. 1 root utmp 6528 May  9 19:26 /var/log/btmp

说明:lastb 读取 /var/log/btmp(二进制),权限通常为 -rw-------,属主 root、属组 utmp,因此普通用户无法查看。

3.3 utmp 与 who / w

/var/run/utmp 记录当前登录用户,用 who、w 查看。

who                 # 查看当前在线用户
who -b              # 最后一次系统启动时间
who -r              # 查看当前运行级别
who -aH             # 列出当前用户、系统引导、运行级别等全部信息(带表头)
w                   # 在线用户及其正在执行的命令、负载
whoami              # 当前有效用户名

who -aH 输出示例:

           系统引导 2018-10-25 17:56
           运行级别 5 2018-10-25 09:56
soul     ? :0       2018-10-25 09:56   ?      2115   (:0)
soul     + pts/0    2018-10-25 09:57   旧的   3201   (:0)

配套的消息发送命令(用于通知在线用户即将维护):

write soul /dev/pts/5                    # 给指定用户/终端发消息,Ctrl+D 结束
wall                                     # 给所有在线用户发消息,Ctrl+D 结束
mesg n                                   # 关闭接收消息(普通用户无法再给该用户发消息)
mesg y                                   # 打开接收消息
echo "Message content" > /dev/pts/5      # 即使用户 mesg n,root 仍可向其终端写入

3.4 lastlog

/var/log/lastlog 记录每个用户最后一次登录的时间与来源。

lastlog                     # 所有用户最近一次登录记录(不显示图形界面登录)
lastlog -u root             # 指定用户
lastlog -t 7                # 只显示最近 7 天内登录过的用户

3.5 dmesg 与内核环形缓冲区

内核把启动与运行期间的消息写入一块固定大小的环形缓冲区(ring buffer),新消息会覆盖最旧的消息。dmesg 用于读取该缓冲区。

dmesg                       # 查看内核环形缓冲区全部内容
dmesg | less                # 分页阅读
dmesg -T                    # 把时间戳转换为人类可读的日期时间(重要!)
dmesg -T | tail -30         # 最近 30 条内核消息
dmesg -l err,crit           # 只看错误及以上级别
dmesg -k                    # 只看内核消息
dmesg -w                    # 实时跟踪新产生的内核消息(等价于 tail -f)
dmesg -c                    # 显示后清空缓冲区
dmesg -H                    # 人性化输出(分页 + 彩色 + 相对时间)
dmesg | grep -i -E 'error|fail|sda|eth'   # 排查磁盘/网卡/硬件问题

默认 dmesg 的时间戳是"系统启动后经过的秒数",很难直接对应真实时间,排障时务必加 -T。

CentOS 7 及以上也可用 journalctl -k(等价于 dmesg)、journalctl -k -b -1(上次启动的内核日志)。


四、rsyslog 日志服务

4.1 rsyslog 架构与三大模块

rsyslog 采用模块化设计,日志处理流程可归纳为三个阶段:

input(输入/采集) → filter(过滤/规则匹配) → output(输出/动作)
阶段 作用 对应模块示例
input 采集日志:本地 socket、内核、远程网络、文件 imuxsock、imklog、imudp、imtcp、imfile、imjournal
filter 按 facility.level 选择器、属性、表达式过滤日志 选择器语法、if ... then、property filter
output 决定日志去向:写文件、写终端、转发、入库、执行程序、丢弃 omfile、omfwd、ommysql、omprog

常用模块说明:

模块 类型 作用
imuxsock input 接收本地 Unix socket(/dev/log)的日志,即 logger、应用 syslog() 调用的来源
imklog input 读取内核日志(内核环形缓冲区)
imjournal input 从 systemd journal 读取日志(CentOS 7 默认方式之一)
imudp input 以 UDP 方式接收远程日志(默认 514)
imtcp input 以 TCP 方式接收远程日志(默认 514),可靠但开销略高
imfile input 把普通文本文件(如 Nginx access.log)纳入 rsyslog 采集
omfile output 写入本地文件(内置,默认动作)
omfwd output 转发到远程日志服务器(@ UDP / @@ TCP)
ommysql output 写入 MySQL 数据库
omprog output 把日志交给外部程序处理

4.2 /etc/rsyslog.conf 结构

主配置文件 /etc/rsyslog.conf 通常分为三大段:

vim /etc/rsyslog.conf
#### MODULES ####            模块加载区
$ModLoad imuxsock            # 提供本地系统日志支持(logger 命令)
$ModLoad imklog              # 提供内核日志支持
#$ModLoad immark             # 提供 --MARK-- 周期标记消息
#$ModLoad imudp              # 开启 UDP 接收
#$UDPServerRun 514
#$ModLoad imtcp              # 开启 TCP 接收
#$InputTCPServerRun 514

#### GLOBAL DIRECTIVES ####  全局指令区(模板、工作目录、文件权限等)
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
$IncludeConfig /etc/rsyslog.d/*.conf     # 引入子配置目录
$WorkDirectory /var/lib/rsyslog

#### RULES ####              规则区(选择器 + 动作)
*.info;mail.none;authpriv.none;cron.none    /var/log/messages
authpriv.*                                  /var/log/secure
mail.*                                      -/var/log/maillog
cron.*                                      /var/log/cron
*.emerg                                     *
uucp,news.crit                              /var/log/spooler
local7.*                                    /var/log/boot.log
路径 作用
/etc/rsyslog.conf 主配置文件
/etc/rsyslog.d/*.conf 子配置目录,推荐把自定义规则单独放在这里,避免升级时被覆盖

4.3 facility(日志设施/服务类型)全表

facility 用来标记一种类型日志的来源,相当于"日志设备"。

facility 说明
auth 认证相关,PAM 产生的日志
authpriv ssh、ftp 等登录信息的验证消息(私有认证信息,权限更严格)
cron 定时任务(crond、atd)
daemon 守护进程相关的消息(内部服务器/系统服务)
kern 内核信息(用户进程无法使用该设施)
lpr 打印服务相关的消息
mail 内部邮件服务器相关的消息
mark 时间戳标记,周期性写入 --MARK-- 用于判断日志服务是否存活
news 新闻交换网络,基于 uucp
syslog 日志服务自身产生的消息
user 用户程序产生的相关消息(默认设施)
uucp Unix-to-Unix Copy,Email 的前身
ftp ftp 服务器相关的消息
local0 ~ local7 用户自定义消息;local7 通常被 Cisco 和 Windows 服务器使用,CentOS 上 local7 默认写 boot.log

4.4 severity(日志级别)全表

level(严重级别,也叫 priority)指定记录日志信息的详细程度。

级别 数值 英文 含义与说明
emerg / panic 0 Emergency 紧急,系统不可用、接近崩溃,一般为硬件问题
alert 1 Alerts 报警,需要马上处理;应用已出现很多重大错误,比 crit 更严重
crit 2 Critical 关键,非常严重的状况,比 err 更重要的错误
err / error 3 Errors 错误,重大错误信息
warning / warn 4 Warnings 警示,提示信息,可能有问题出现
notice 5 Notification 通知,普通但很重要的信息,除 info 之外还需注意的信息
info 6 Information 消息,最常见的级别,一些基本说明
debug 7 Debugging 调试日志,最详细,通常难以阅读,一般不启用
none — None 不算级别,表示该设施的信息什么都不记录

数值越大,级别越低、消息越详细;数值越小,级别越严重。默认写法(如 cron.info)表示"大于或等于该级别",即 info、notice、warning、err、crit、alert、emerg 都会被记录。

4.5 选择器语法与符号

基本格式:

日志设施.[连接符号]日志级别    日志处理方式(action)

示例:

mail.info   /var/log/maillog

表示:mail 设施产生的、级别大于等于 info 的日志,写入 /var/log/maillog。

符号完整含义:

符号 作用
. 分隔服务(facility)与级别(level);无其他修饰时表示"大于或等于此级别"(最常用)
* 通配:*.级别 表示任何服务;设施.* 表示任何级别;*.* 表示所有服务所有级别
= 只等于此级别,如 cron.=info
! 排除操作。前面有相同服务的表达式时才有意义,表示从前面包含的内容中排除某些级别。cron.!err 表示排除 err 及以上;cron.!=err 表示仅排除等于 err
none 该设施什么都不记录,常用于"排除某几类日志",如 *.info;mail.none
; 分隔不同的"服务.级别"组合
, 分隔多个服务(共用同一级别),如 mail,cron.info
-(动作侧) 写在目标文件路径前,表示不立即刷盘(先写内存缓冲,攒够再落盘)

关于 . / .= / .! 的对照(旧文档常用写法):

符号 含义
. 大于或等于此级别(最常用)
.= 只等于此级别
.! 不等于(排除)此级别

4.6 选择器配置示例

cron.*                          # cron 服务的所有级别
*.info                          # 所有服务 info 及以上级别
cron.info                       # cron 服务 info 及以上级别
cron.info;cron.!err             # >=info 且 <err(即 info 到 warning)
cron.=info                      # 等于 info 级别
cron.info;cron.!=err            # >=info 但不等于 err
*.warning;mail.info             # 所有服务 >=warning,加上 mail 服务 >=info
mail,cron.info                  # mail 与 cron 服务均 >=info
cron.err  /var/log/cron.log     # 文件名前无减号:异步写入(立即刷盘)
mail.err  -/var/log/mail.log    # 文件名前有减号:同步写入,rsyslog 不会马上把数据写入文件

典型需求示例:

示例 1:将 mail 应用的日志记录到 /var/log/maillog

mail.info   /var/log/maillog

示例 2:将 news 和 mail 的所有日志写入 /var/log/mylog,并把两者的警告信息额外写入 /var/log/my.log

news.*;mail.*           /var/log/mylog
news.=warn;mail.=warn   /var/log/my.log

多个应用/设施之间用 ; 隔开;指定单一级别用 = 接级别。

示例 3:将除 cron 和 mail 外的所有日志记录到 /var/log/mylog

*.*;mail;news.none      /var/log/mylog

或(推荐、更严谨的写法):

*.*;mail.none;news.none /var/log/mylog
*.*;cron.none;mail.none    /var/log/mylog

也可简写为:

*.*;cron,mail.none         /var/log/mylog

4.7 动作(action):日志的六种去向

动作类型 写法 说明
写文件 cron.* /var/log/cron 最常见;前置 - 表示不立即刷盘
写终端/设备 mail.=info /dev/tty12 输出到第 12 个终端,便于值守观察
广播给用户 *.emerg * 或 *.alert root,czl * 广播给所有在线用户;也可指定用户列表
丢弃 local3.* ~ 或 & stop ~(旧语法)/ stop(新语法)丢弃匹配的日志,用于降噪
转发远程 local5.* @@192.168.1.2:514 @ = UDP,@@ = TCP
写数据库 *.* :ommysql:127.0.0.1,Syslog,rsyslog,passwd 需加载 ommysql 模块并导入建库脚本
执行程序 *.* ^/usr/local/bin/alert.sh 把日志作为参数交给外部脚本(可用于告警)
命名管道 *.* \|/var/log/mypipe 写入 FIFO,由其他程序实时消费

特殊写法说明:

*.emerg     *

将所有应用最严重的信息以广播形式发送给登录该系统的每一个用户。

mail.*      -/var/log/maillog

将 mail 应用所有等级的日志写入 /var/log/maillog。路径前的 - 表示先将日志保存到内存,数据量够大时才写入文件:

  • 优点:对日志量大的服务可提升写盘性能;
  • 缺点:非正常关机时,内存中尚未落盘的日志会丢失。

完整的高级示例解析:

*.=crit;kern.none   /var/adm/critical
# 除 kern 外所有服务的 crit 级别信息写入 /var/adm/critical

kern.*  /var/adm/kernel
# kern 服务所有信息

kern.crit  @@10.1.1.1:514
# kern 服务 crit 及以上信息发送到远程日志服务器 10.1.1.1 的 TCP 514 端口
# (单 @ 表示 UDP,双 @@ 表示 TCP)

kern.info;kern.!err  /var/adm/kernel-info
# kern 服务 info 到 warning 的信息

mail.=info  /dev/tty12
# mail 服务等于 info 级别的信息输出到第 12 个终端

mail.*;mail.!=info  /var/adm/mail
# mail 服务除 info 之外的所有信息

mail,news.=info  -/var/adm/info
# mail 与 news 的 info 级别信息同步写入 /var/adm/info

*.=emerg  *
# 所有 emerg 信息发送给所有登录到系统上的用户

*.alert root,czl
# 所有 alert 信息发送给在线用户 root 和 czl

4.8 模板(template)自定义日志格式

模板用于定义写入文件或转发时的日志格式,也可用于动态生成文件名(远程日志按主机名分目录的关键)。

传统(legacy)语法:

# 定义日志内容格式
$template MyFormat,"%timegenerated% %HOSTNAME% %syslogtag% %fromhost-ip% %msg%\n"

# 使用模板:动作后加分号和模板名
*.info    /var/log/custom.log;MyFormat

# 定义动态文件名模板(按主机名 + 日期分目录)
$template RemoteLogs,"/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log"
*.*       ?RemoteLogs        # 动态文件名前用 ? 引用
& stop                       # 处理后停止,避免重复写入本地文件

RainerScript(新语法,rsyslog 7+ 推荐):

template(name="RemoteLogs" type="string"
         string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")

*.* action(type="omfile" dynaFile="RemoteLogs")

常用模板属性(property):

属性 含义
%timegenerated% 日志生成时间(本机接收时间)
%timereported% 日志中自带的时间戳
%HOSTNAME% 产生日志的主机名
%fromhost-ip% 发送方 IP 地址
%syslogfacility-text% 设施名(文本)
%syslogseverity-text% 级别名(文本)
%syslogtag% 标签(通常是 程序名[PID]:)
%programname% 程序名
%msg% 日志正文
%$YEAR% %$MONTH% %$DAY% 系统当前年、月、日,用于动态路径

内置模板:

$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat   # 传统格式(秒级精度)
$ActionFileDefaultTemplate RSYSLOG_FileFormat              # 高精度时间戳 + 时区

4.9 logger 命令:手动写日志用于测试

logger 用于向 syslog 发送日志信息,特别适合调试配置文件,也可在脚本中记录日志。

logger "简单消息,默认以 user.notice 写入"
logger -t "Logger" -p mail.info "Testing log info"
logger -t "Cron"   -p cron.info "Testing cron info"
logger -t "哈哈" "你好"
logger -p local5.info -t SSHTEST "远程日志测试消息"
logger -i -t myapp "带 PID 的消息"
logger -f /etc/fstab                       # 把文件内容逐行写入日志
logger -s -p user.err "同时输出到标准错误"
logger -n 192.168.1.2 -P 514 -d "直接发往远程日志服务器(UDP)"
选项 作用
-t TAG 指定日志中的标签(tag)
-p facility.level 指定设施与级别,默认 user.notice
-i 在日志中记录 logger 进程的 PID
-f FILE 把文件内容作为日志消息
-s 同时把消息输出到标准错误
-n HOST / -P PORT / -d 直接发往远程主机(-d 用 UDP,-T 用 TCP)

验证效果:

logger -t "哈哈" "你好"
tail -3 /var/log/messages

4.10 rsyslog 配置语法检查与调试

# 语法检查(不启动服务),CentOS 7
rsyslogd -N1
rsyslogd -N1 -f /etc/rsyslog.conf

# 查看生效的完整配置(含 include 的子文件)
rsyslogd -o /tmp/rsyslog.full.conf

# 前台调试运行,打印详细处理过程
rsyslogd -dn

# 服务管理(CentOS 6)
service rsyslog restart
/etc/init.d/rsyslog restart
chkconfig rsyslog on

# 服务管理(CentOS 7)
systemctl restart rsyslog
systemctl status rsyslog
systemctl enable rsyslog
journalctl -u rsyslog -n 50            # 查看 rsyslog 自身的错误

# 确认监听端口
netstat -ntlup | grep :514
ss -ntlup | grep :514

4.11 远程日志集中收集完整实操

将所有服务器的日志统一发送到一台机器上管理,便于集中存储、检索和审计。

实验拓扑:

日志服务器(Log Server)         日志客户端(Log Client)
192.168.1.2          <------   192.168.1.61
       rsyslog                   rsyslog

需求:把 192.168.1.61 上 ssh 服务的日志远程记录到 192.168.1.2 的 /var/log/ssh。

所有机器先关闭防火墙和 SELinux(生产环境应改为放行 514 端口):

# CentOS 6
iptables -F
setenforce 0

# CentOS 7
systemctl stop firewalld
setenforce 0
# 或者只放行 514
firewall-cmd --permanent --add-port=514/tcp
firewall-cmd --permanent --add-port=514/udp
firewall-cmd --reload
semanage port -a -t syslogd_port_t -p tcp 514    # SELinux 开启时需放行端口

步骤一:日志服务器端开启 514 端口接收(192.168.1.2)

编辑 /etc/rsyslog.conf,将模块区第 17、18 行的注释打开:

$ModLoad imtcp
$InputTCPServerRun 514

也可同时启用 UDP:

$ModLoad imudp
$UDPServerRun 514

TCP/UDP 传输可同时开启,不影响远程日志服务器工作。UDP 开销小但可能丢包,TCP 可靠、推荐用于重要日志。

重启服务并验证端口监听:

/etc/init.d/rsyslog restart          # CentOS 6
systemctl restart rsyslog            # CentOS 7
netstat -ntlup | grep :514

输出:

tcp        0      0 0.0.0.0:514     0.0.0.0:*     LISTEN      7295/rsyslogd
tcp        0      0 :::514          :::*          LISTEN      7295/rsyslogd

syslog 默认端口为 514。

步骤二:客户端配置转发(192.168.1.61)

先让 ssh 使用独立的 local5 设施,便于服务端区分:

vim /etc/ssh/sshd_config

在文件后添加:

SyslogFacility local5
/etc/init.d/sshd restart             # CentOS 6
systemctl restart sshd               # CentOS 7

配置 rsyslog 远程发送:

vim /etc/rsyslog.conf
local5.*    @@192.168.1.2:514

@@ 表示 TCP 协议;UDP 只有一个 @。

若要把本机全部日志都转发,可写 *.* @@192.168.1.2:514。

/etc/init.d/rsyslog restart

带磁盘队列的可靠转发(生产环境推荐,服务端宕机时本地缓存不丢日志):

$WorkDirectory /var/lib/rsyslog
$ActionQueueFileName fwdRule1
$ActionQueueMaxDiskSpace 1g
$ActionQueueSaveOnShutdown on
$ActionQueueType LinkedList
$ActionResumeRetryCount -1
*.*  @@192.168.1.2:514

步骤三:服务端指定日志文件

回到 192.168.1.2,编辑 /etc/rsyslog.conf,在最后添加:

local5.*    /var/log/ssh
/etc/init.d/rsyslog restart

步骤四:测试

从其他机器 ssh 连接 192.168.1.61,然后在 192.168.1.2 上查看:

cat /var/log/ssh
tail -f /var/log/ssh

即可看到 192.168.1.61 的日志。也可用 logger 在客户端直接造一条测试日志:

# 客户端执行
logger -p local5.info -t TEST "hello from 192.168.1.61"
# 服务端查看
tail -f /var/log/ssh

步骤五:按主机名分目录存储(多客户端场景)

若 100 台主机日志都传到同一个文件,如何区分来源?日志中有一列是主机名(主机头),例如 qianyun.org 中的 qianyun,所以所有主机必须绑定不同且唯一的主机名。

更好的做法是在服务端用动态文件名模板按主机名分目录存储:

vim /etc/rsyslog.d/remote.conf
# 加载 TCP/UDP 接收模块
$ModLoad imtcp
$InputTCPServerRun 514
$ModLoad imudp
$UDPServerRun 514

# 定义按主机名 + 程序名分目录的动态路径
$template RemoteHost,"/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%-%PROGRAMNAME%.log"

# 只处理来自远程的日志(本机日志仍按默认规则走)
:fromhost-ip, !isequal, "127.0.0.1"  ?RemoteHost
& stop

RainerScript 等价写法:

module(load="imtcp")
input(type="imtcp" port="514")

template(name="RemoteHost" type="string"
         string="/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%-%PROGRAMNAME%.log")

if $fromhost-ip != "127.0.0.1" then {
    action(type="omfile" dynaFile="RemoteHost")
    stop
}

重启并查看效果:

systemctl restart rsyslog
tree /var/log/remote/
# /var/log/remote/
# ├── web01
# │   └── 2026-08-22-sshd.log
# └── db01
#     └── 2026-08-22-mysqld.log

集中收集的日志同样需要配置 logrotate(见第六节),否则日志服务器磁盘很快被写满。

需要检索、聚合、可视化时,请使用 ELK 平台,参见 08-04-ELK日志分析.md。


五、systemd-journald(CentOS 7+)

5.1 与 rsyslog 的关系与共存

CentOS 7 起,systemd 自带日志服务 systemd-journald,它与 rsyslog 并存而非替代。只看 /var/log/messages 会漏掉大量信息,尤其是服务启动失败的原因。

内核 / 服务 stdout+stderr / syslog() 调用 / journal API
        ↓
   systemd-journald(统一收集,二进制结构化存储)
        ↓                        ↓
  journalctl 查询          转发给 rsyslog(ForwardToSyslog=yes)
                                 ↓
                          /var/log/messages 等文本文件
  • journald 是采集者:把内核缓冲区、服务的标准输出/错误、syslog() 调用全部收进来。
  • rsyslog 从 journal 读取(imjournal 模块)后按 facility.priority 规则落到文本文件(即第四节的配置)。
  • 所以同一条日志可能同时出现在 journalctl 和 /var/log/messages 中。

CentOS 6 与 CentOS 7 的差异对照:

项目 CentOS 6 CentOS 7
日志服务 仅 rsyslog(RHEL 5 为 syslog) systemd-journald + rsyslog 并存
服务管理 service rsyslog restart / /etc/init.d/rsyslog restart systemctl restart rsyslog
内核日志 dmesg、/var/log/dmesg dmesg、journalctl -k
服务日志查看 tail -f /var/log/messages journalctl -u <unit>、tail -f /var/log/messages
启动日志 /var/log/boot.log(local7.*) journalctl -b、/var/log/boot.log
日志持久化 天然持久(文本文件) journal 默认易失,需手动开启持久化
轮转机制 logrotate 文本日志用 logrotate;journal 自带轮转(SystemMaxUse=)

rsyslog 与 journald 对比

对比项 rsyslog(/var/log/messages) journald(journalctl)
存储格式 纯文本,可直接 cat/grep/awk 二进制,必须用 journalctl 读取
数据结构 单行非结构化字符串 结构化字段(_PID、_UID、_SYSTEMD_UNIT 等)
查询能力 依赖 grep/sed 文本匹配 内置按 unit/时间/级别/字段过滤
持久化 默认持久,写入 /var/log/ 默认易失,需手动开启持久化
轮转机制 由 logrotate 管理 自带轮转,受 SystemMaxUse= 控制
日志分割 按 facility.priority 分文件 单一日志库,查询时动态过滤
采集范围 走 syslog() 的程序 额外覆盖服务 stdout/stderr、内核、早期启动
远程传输 成熟(@@host:514,见 4.11 节) 需 systemd-journal-remote,生态较弱
适用场景 长期归档、集中日志平台、文本工具链 本地故障排查、服务启动调试、精确定位

实践建议:排查当下问题用 journalctl,长期归档与集中分析用 rsyslog。两者不是二选一,CentOS 7 默认就是 journald 采集 + 转发 rsyslog 落盘的组合。

5.2 存储位置:volatile 与 persistent

路径 类型 说明
/run/log/journal/ 易失(volatile) 位于 tmpfs,重启即丢失,CentOS 7 默认行为
/var/log/journal/ 持久(persistent) 落到磁盘,重启后仍可查询历史日志

注意:这是新手最容易踩的坑——服务器异常重启后想查故障原因,执行 journalctl -b -1 却发现没有任何记录,因为默认配置下 journal 日志随重启一起消失了。生产环境务必开启持久化。

5.3 开启日志持久化

方式一:创建目录(journald 检测到目录存在即自动持久化)

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal   # 修正目录权限与属主
sudo systemctl restart systemd-journald

方式二:显式修改配置文件 /etc/systemd/journald.conf

[Journal]
Storage=persistent        # auto(默认)/ volatile / persistent / none
SystemMaxUse=500M         # journal 最多占用 500M 磁盘
SystemKeepFree=1G         # 至少给磁盘留 1G 空闲
SystemMaxFileSize=50M     # 单个日志文件最大 50M
MaxRetentionSec=1month    # 日志最长保留 1 个月
MaxFileSec=1week          # 单个日志文件最长跨度 1 周后强制轮转
RateLimitInterval=30s     # 限流窗口
RateLimitBurst=1000       # 限流窗口内最多接收条数(防日志风暴)
ForwardToSyslog=yes       # 是否转发给 rsyslog
Compress=yes              # 大条目压缩存储
sudo systemctl restart systemd-journald
journalctl --disk-usage        # 验证是否已写入 /var/log/journal
ls -ld /var/log/journal/*
Storage 取值 行为
auto /var/log/journal/ 存在则持久化,否则易失(默认)
persistent 自动创建目录并持久化
volatile 仅存内存,重启丢失
none 不存储,仅转发

5.4 日志转发给 rsyslog

CentOS 7 中 rsyslog 通过 imjournal 模块读取 journal:

# /etc/rsyslog.conf
$ModLoad imjournal
$imjournalStateFile imjournal.state
$OmitLocalLogging on            # on 表示不再从 /dev/log 直接读(避免重复)

若希望由 journald 主动转发,则在 /etc/systemd/journald.conf 中设置:

[Journal]
ForwardToSyslog=yes       # 转发给 rsyslog(写入 /var/log/messages 等)
ForwardToKMsg=no          # 转发到内核环形缓冲区
ForwardToConsole=no       # 转发到控制台
ForwardToWall=yes         # 高级别消息广播给所有登录用户
systemctl restart systemd-journald
systemctl restart rsyslog

5.5 journalctl 常用选项全览

核心查询用法

journalctl                          # 查看全部日志(分页,最早在前)
journalctl -n 50                    # 只看最后 50 行
journalctl -n                       # 默认最后 10 行
journalctl -r                       # 倒序,最新的在最前面
journalctl -f                       # 实时跟踪(等价于 tail -f)
journalctl --no-pager               # 不分页,直接输出(便于管道处理)
journalctl -a                       # 完整显示(不截断长字段、显示不可打印字符)

按 unit 过滤(最常用)

journalctl -u nginx.service         # 只看 nginx 服务日志
journalctl -u nginx -f              # 实时跟踪 nginx(.service 可省略)
journalctl -u nginx -u php-fpm      # 同时看多个服务
journalctl -u nginx --since today   # 组合时间条件
journalctl --user-unit myapp        # 用户级 unit

按启动次数过滤

journalctl -b                       # 本次启动以来的日志
journalctl -b -1                    # 上一次启动的日志(排查异常重启的关键)
journalctl -b -2                    # 上上次启动
journalctl --list-boots             # 列出所有可用的启动记录及其编号

按时间过滤

journalctl --since "2026-08-01 00:00" --until "2026-08-02 00:00"
journalctl --since today
journalctl --since yesterday --until now
journalctl --since "1 hour ago"
journalctl --since "-30min"
journalctl --since "09:00" --until "10:30"

按级别与来源过滤

journalctl -p err                   # err 及以上级别(级别名同 4.4 节 severity)
journalctl -p 3                     # 也可用数值(3 = err)
journalctl -p warning..err          # 指定级别区间
journalctl -k                       # 只看内核日志(等价于 dmesg)
journalctl -k -p err                # 内核错误
journalctl -k -b -1                 # 上次启动的内核日志

按结构化字段匹配(journal 的核心优势)

journalctl _PID=1234                # 指定进程 ID
journalctl _UID=1000                # 指定用户 ID
journalctl _GID=100                 # 指定组 ID
journalctl _COMM=sshd               # 指定进程名
journalctl _EXE=/usr/sbin/sshd      # 指定可执行文件
journalctl _SYSTEMD_UNIT=sshd.service
journalctl _TRANSPORT=kernel        # 来源通道:kernel/syslog/journal/stdout/audit
journalctl SYSLOG_FACILITY=10       # 按 syslog 设施数值(10 = authpriv)
journalctl PRIORITY=3
journalctl /usr/sbin/sshd           # 按可执行文件路径(简写形式)
journalctl -F _SYSTEMD_UNIT         # 列出该字段的所有可能取值
journalctl _COMM=sshd _PID=1234     # 多字段为 AND
journalctl _COMM=sshd + _COMM=su    # "+" 表示 OR
journalctl --fields                 # 列出日志中出现过的所有字段名

输出格式(-o)

journalctl -u nginx -o short           # 默认格式,类似传统 syslog 单行
journalctl -u nginx -o short-precise   # 带毫秒精度的时间戳
journalctl -u nginx -o short-iso       # ISO 8601 时间格式
journalctl -u nginx -o short-monotonic # 相对开机的单调时间
journalctl -u nginx -o verbose         # 显示全部结构化字段(排障最全)
journalctl -u nginx -o json            # 结构化 JSON,一行一条
journalctl -u nginx -o json-pretty     # 格式化 JSON,便于脚本解析
journalctl -u nginx -o cat             # 只输出消息正文,不带前缀
journalctl -u nginx -o export          # 二进制导出格式,用于传输/备份

附加解释与错误排查

journalctl -x                       # 为日志条目附加解释性说明
journalctl -xe                      # 附加说明并跳到末尾(排查服务启动失败第一步)
journalctl --verify                 # 校验日志文件完整性

journalctl -xe 是服务启动失败的标准排查动作:

sudo systemctl start tomcat
# Job for tomcat.service failed because ... See "systemctl status tomcat.service"
# and "journalctl -xe" for details.

sudo journalctl -xe
  • -x:为日志条目附加解释性说明(Subject/Defined-By/Support 等帮助文本)。
  • -e:直接跳到日志末尾,即最新的错误现场。

配合使用:

sudo systemctl status tomcat -l --no-pager   # 先看状态摘要与最后几行日志
sudo journalctl -u tomcat -n 100 --no-pager  # 再看该服务完整错误上下文
sudo journalctl -u tomcat -b --no-pager      # 看本次启动以来的全部记录

常见能从 journalctl -xe 中直接定位的问题:

日志关键字 原因
Failed to determine user credentials User= 指定的用户不存在
No such file or directory ExecStart 路径写错或缺少解释器
Executable path is not absolute ExecStart 必须用绝对路径
PID file ... not readable Type=forking 但 PIDFile= 路径不对
Start operation timed out Type= 选错(unit 文件编写详见 08-01)
status=203/EXEC 脚本无执行权限或 shebang 有问题

5.6 磁盘占用与清理

journalctl --disk-usage             # 查看当前日志占用空间
journalctl --vacuum-size=200M       # 清理旧日志,保留总量不超过 200M
journalctl --vacuum-time=2weeks     # 删除 2 周以前的日志
journalctl --vacuum-files=5         # 只保留最近 5 个日志文件
journalctl --rotate                 # 立即触发一次 journal 轮转
journalctl --flush                  # 把 /run 中的日志刷到 /var/log/journal
journalctl --verify                 # 校验日志文件完整性

手动 vacuum 是应急手段;长期控制应在 /etc/systemd/journald.conf 中设置 SystemMaxUse=,让 journald 自动按上限滚动,无需依赖 logrotate(journal 有自己的轮转机制,不受第六节 logrotate 管理)。


六、日志轮转 logrotate

6.1 为什么要轮转

随着系统运行,日志文件会持续增大,带来三个问题:

  1. 单个文件过大,grep/vim 打开缓慢甚至不可用;
  2. 占满 /var 或根分区磁盘,导致服务无法写入、系统异常;
  3. 历史日志与当前日志混在一起,不利于归档和审计。

logrotate 用于对日志文件进行切割、备份、归档、压缩、删除,并创建新日志文件,避免单个日志文件无限增长。

可以按 大小 或 时间(天/周/月/年) 进行轮转。查看 /var/log/ 目录常能看到形如 messages-20140724 的文件名,这表明该日志在 7 月 24 号被轮转过。

ls /var/log/
# messages  messages-20140724  messages-20140717.gz ...

6.2 配置文件结构

路径 作用
/etc/logrotate.conf 主配置文件,定义全局属性,并引用 /etc/logrotate.d/ 下的文件
/etc/logrotate.d/ 子配置目录,各应用服务独立配置,其中定义会覆盖主配置;通常每个软件包在此放置一份
/var/lib/logrotate/logrotate.status 默认状态文件,记录每个日志上次轮转的时间(判断周期是否到达的依据)
/etc/cron.daily/logrotate 每日触发脚本
/etc/anacrontab 定义每日任务执行时间,从而触发 /etc/cron.daily/logrotate
/usr/lib/systemd/system/logrotate.timer CentOS 7.4+ 改用 systemd timer 触发

可通过 man logrotate 查阅 CONFIGURATION FILE 段的完整指令说明。

6.3 触发机制:cron 与 systemd timer

logrotate 本身不是守护进程,它是被周期性调用的一次性命令。

CentOS 6 / 早期 CentOS 7(cron 方式):

/etc/cron.daily/logrotate 是一个 shell 脚本,每天由 cron 在 /etc/crontab 指定的时间启动:

cat /etc/cron.daily/logrotate
#!/bin/sh
/usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
    /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit 0

/etc/anacrontab 决定 daily 任务的实际执行时间与关机补跑策略。

CentOS 7.4+ / CentOS 8(systemd timer 方式):

systemctl list-timers | grep logrotate
systemctl status logrotate.timer
systemctl cat logrotate.timer
# /usr/lib/systemd/system/logrotate.timer
[Unit]
Description=Daily rotation of log files

[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true

[Install]
WantedBy=timers.target
systemctl enable --now logrotate.timer
systemctl start logrotate.service       # 立即手动跑一次

若需要比"每天一次"更频繁的轮转(例如每小时、每 5 分钟),可以自行添加 cron 条目或自定义 timer:

*/5 * * * * /usr/sbin/logrotate /etc/logrotate.d/nginx

6.4 主配置文件详解

vim /etc/logrotate.conf
weekly                  # 日志文件每周轮转一次(另有 daily/monthly/yearly)

rotate 4                # 轮转后只保留四份

create                  # 轮转后创建新的空日志文件

#compress               # 默认轮转后的日志文件不压缩

#dateext                # 轮转的日志文件加时间后缀,测试时建议注释

include /etc/logrotate.d    # 引入子配置目录

/var/log/wtmp {
    monthly             # 此文件每月轮转,覆盖上面的每周
    create 0664 root utmp   # 轮转文件的权限、owner、group
    minsize 1M          # 最小 1M 和每月时间两条件满足其一即轮转
    rotate 1
}

/var/log/btmp {
    missingok           # 如果日志丢失也不报错
    monthly
    create 0600 root utmp
    rotate 1
}

6.5 常用指令全表

指令 作用
daily 每天轮转一次
weekly 每周轮转一次
monthly 每月轮转一次
yearly 每年轮转一次
hourly 每小时轮转(需配合每小时的触发任务)
size SIZE 文件只要大于指定大小就轮转,忽略周期(如 size 30k、size 100M)
minsize SIZE 文件大于指定大小 且 周期到了才轮转(如 minsize 30K)
maxsize SIZE 周期未到但已超过该大小也立即轮转
rotate N 同一文件最多保留 N 个轮转副本,超过后删除;rotate 0 表示不保留
compress 用 gzip 压缩轮转后的日志
nocompress 不压缩(默认)
delaycompress 下一次轮转时才压缩(常与 compress 配合,避免程序仍持有旧文件句柄导致压缩到写入中的文件)
compresscmd / uncompresscmd 指定压缩/解压命令(如改用 xz)
compressoptions 传给压缩命令的参数(如 -9)
missingok 日志文件不存在时不报错,继续处理下一个
nomissingok 文件不存在时报错(默认)
notifempty 日志文件为空时不轮转
ifempty 即使为空也轮转(默认)
create [mode owner group] 轮转后立即创建新的空日志文件并指定权限、属主、属组(如 create 0664 root utmp)
nocreate 不创建新文件
copytruncate 先复制原文件再清空原文件(truncate),进程句柄不变,无需重启服务
nocopytruncate 复制但不清空原文件(默认关闭)
copy 只复制不清空,也不轮转原文件
dateext 用日期作为轮转后文件的后缀(messages-20260822),而不是数字 .1 .2
dateformat 自定义日期格式,如 dateformat -%Y%m%d%H(支持 %Y %m %d %H %s)
dateyesterday 用前一天的日期作后缀(更符合"这份日志属于昨天"的语义)
olddir DIR 把轮转后的文件移动到指定目录
noolddir 轮转后文件与原文件同目录(默认)
createolddir olddir 不存在时自动创建
sharedscripts 无论有多少文件待轮转,prerotate/postrotate 只执行一次(与通配符配合使用)
nosharedscripts 每个文件都执行一次脚本(默认)
prerotate ... endscript 符合轮转条件时,轮转前执行的 shell 代码
postrotate ... endscript 轮转后执行的 shell 代码
firstaction ... endscript 所有文件轮转前,最先执行一次
lastaction ... endscript 所有文件轮转后,最后执行一次
su USER GROUP 以指定用户/组身份轮转(日志目录属主非 root 时必需)
include DIR/FILE 引入子配置目录或文件
mail EMAIL 轮转后发送邮件给指定用户
nomail 不发送邮件
maillast 发送即将被删除的最老一份(主题为 about-to-expire)
mailfirst 发送刚轮转出的最新一份(主题为 just-rotated)
tabooext [+] LIST 忽略指定扩展名的配置文件
start N 轮转后缀的起始编号(默认 1)
shred 删除旧日志时用 shred 安全擦除

6.6 copytruncate 与 create 的区别与适用场景

这是 logrotate 最重要的一个知识点。

问题的来源:某服务将日志写入 aaa.log,手动执行 mv aaa.log aaa.log.1 && touch aaa.log,新日志会写入哪个文件?

答案:仍然写入 aaa.log.1。

原因:服务进程通过文件描述符写入文件,同一分区内 mv 不会改变 inode,服务仍持有旧 inode。新建的空文件 inode 不同,服务不会自动切换。

因此 logrotate 提供两种解决思路:

方式 原理 优点 缺点 适用场景
create + postrotate 信号 重命名原文件 → 创建新文件 → 给进程发 HUP/USR1 让它重新打开日志 不丢日志、不占额外 IO、日志边界清晰 需要程序支持重新打开日志的信号 rsyslog、Nginx、Apache、sshd 等标准守护进程
copytruncate 把原文件内容复制成副本 → 然后把原文件截断为 0 字节;进程句柄和 inode 都不变 无需通知进程、无需重启,通用性最强 复制与截断之间写入的日志会丢失;大文件复制占用双倍磁盘与 IO 程序不支持重新打开日志(很多 Java 应用、Tomcat catalina.out、自研程序、MySQL 部分日志)

create 方式(推荐,配合信号):

/var/log/nginx/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 nginx adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

copytruncate 方式:

/usr/local/tomcat/logs/catalina.out {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

使用 copytruncate 时不要同时写 create,两者语义冲突(create 会被忽略)。

为什么要 kill -HUP?

一个服务日志记录到 aaa.log,手动 mv aaa.log aaa.log.1 再 touch aaa.log,新日志仍会记录到 aaa.log.1,因为同分区 mv 文件 inode 不变。需要 kill -HUP 其 pid(有些服务是 kill -USR1)让服务重新打开日志文件:

kill -HUP `cat /var/run/syslogd.pid`

常见服务的"重新打开日志"信号:

服务 信号 / 命令
rsyslog / syslogd kill -HUP $(cat /var/run/syslogd.pid) 或 systemctl reload rsyslog
Nginx kill -USR1 $(cat /var/run/nginx.pid)
Apache httpd apachectl graceful 或 systemctl reload httpd
MySQL mysqladmin flush-logs
sshd kill -HUP $(cat /var/run/sshd.pid)
不支持信号的程序 使用 copytruncate

6.7 子配置文件详解

vim /etc/logrotate.d/syslog

未指定的参数以主配置的默认方式轮转:

/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
    sharedscripts               # 切换时脚本只执行一次
    postrotate                  # rotate 后执行的脚本
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

脚本相关指令:

指令 含义
sharedscripts 对配置中的多个日志只执行一次脚本
prerotate ... endscript 轮转前执行的脚本
postrotate ... endscript 轮转后执行的脚本

postrotate 中对 rsyslog 的 pid 发信号,让其刷新并重新打开日志文件(pid 实际不变)。

6.8 手动执行与调试

# 调试/预演:只打印将要做什么,不真正执行(最重要的调试手段)
logrotate -d /etc/logrotate.conf
logrotate -d /etc/logrotate.d/nginx

# 显示详细过程
logrotate -v /etc/logrotate.conf

# 强制轮转,忽略周期与大小条件(测试用)
logrotate -f /etc/logrotate.conf
logrotate -fv /etc/logrotate.conf
logrotate -f /etc/logrotate.d/nginx

# 指定状态文件(不污染系统状态)
logrotate -d -s /tmp/test.status /etc/logrotate.d/nginx

# 查看轮转状态记录
cat /var/lib/logrotate/logrotate.status

# 查看轮转结果
ls -lh /var/log/
选项 作用
-d / --debug 调试模式(隐含 -v),只输出计划动作,不做任何修改,也不更新状态文件
-v / --verbose 输出详细过程
-f / --force 强制轮转,忽略周期与大小条件
-s FILE / --state FILE 指定状态文件
-m CMD / --mail CMD 指定发信命令
--usage / --help 帮助

-f 强制轮转方便测试,后面只能接主配置文件或具体子配置文件;可加 v 参数查看过程。

6.9 自定义应用轮转配置完整示例

6.9.1 简单示例:为 ssh 日志单独配置轮转

写在 /etc/logrotate.conf 中:

/var/log/ssh {
    monthly
    rotate 2
    missingok
    create 0644 root utmp
}

6.9.2 Nginx 轮转配置

vim /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily                       # 每天轮转
    rotate 30                   # 保留 30 天
    missingok                   # 文件不存在不报错
    notifempty                  # 空文件不轮转
    compress                    # gzip 压缩
    delaycompress               # 延迟一轮再压缩
    dateext                     # 用日期做后缀
    dateformat -%Y%m%d
    create 0640 nginx adm       # 新文件权限
    olddir /var/log/nginx/old   # 归档到单独目录
    createolddir
    sharedscripts               # 多个 .log 只执行一次脚本
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}

6.9.3 MySQL / MariaDB 轮转配置

vim /etc/logrotate.d/mysql
/var/log/mysqld.log /var/log/mysql/slow.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 mysql mysql
    su mysql mysql
    sharedscripts
    postrotate
        # 用 flush-logs 让 mysqld 重新打开日志文件
        if test -x /usr/bin/mysqladmin && \
           /usr/bin/mysqladmin ping &>/dev/null; then
            /usr/bin/mysqladmin --defaults-file=/root/.my.cnf flush-logs
        fi
    endscript
}

/root/.my.cnf 中保存轮转专用账号的凭据,需 chmod 600:

[mysqladmin]
user=root
password=YourPassword

6.9.4 Tomcat catalina.out 轮转配置(copytruncate 场景)

vim /etc/logrotate.d/tomcat
/usr/local/tomcat/logs/catalina.out {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    dateext
    copytruncate            # Tomcat 不支持重新打开日志,必须用 copytruncate
    size 100M               # 超过 100M 立即轮转,不等周期
}

6.9.5 Apache httpd 轮转配置

/var/log/httpd/*log {
    daily
    rotate 52
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
    endscript
}

6.9.6 综合例题:针对 ssh 服务做日志轮转(每两天一次、保留所有日志、按时间分类)

touch /etc/sshlogrotate.conf
vim /etc/sshlogrotate.conf
daily
rotate 4
create

/var/log/ssh {
    create 0600 root sshd
    sharedscripts
    prerotate
        mkdir -p /tmp/ssh/`date +%F`
        cp -rf /var/log/ssh.* /tmp/ssh/`date +%F`
    endscript

    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
        echo "ssh轮转成功了" | mail -s "logrotated succeed" root
    endscript
}

添加定时任务,每两天执行一次:

crontab -e
0 2 */2 * *  logrotate -f /etc/sshlogrotate.conf

6.10 logrotate 邮件报警

6.10.1 安装并启动 postfix

Linux 默认可本机收发邮件,需启动 postfix 服务:

yum install postfix* -y
/etc/init.d/postfix restart          # CentOS 6
chkconfig postfix on
systemctl restart postfix            # CentOS 7
systemctl enable postfix

测试邮件:

# 将字符串作为邮件正文发给 root
echo "邮件内容" | mail -s "邮件主题" root

# 将 /etc/fstab 内容作为邮件正文
mail -s "邮件主题" root < /etc/fstab

# 查看 root 的邮件
cat /var/mail/root
mail -u root

6.10.2 使用内置 mail 参数

轮转后向 test 用户发送邮件:

/var/log/ssh {
    monthly
    rotate 2
    missingok
    mail test       # 指定邮件接收者
    maillast        # 轮转后发邮件,主题为 about-to-expire 的文件 /var/log/ssh.3
}
/var/log/ssh {
    monthly
    rotate 2
    missingok
    mail test
    mailfirst       # 轮转后发邮件,主题为 just-rotated 的文件 /var/log/ssh.1
}
参数 含义
mail EMAIL 轮转后发送邮件给指定用户
maillast 发送即将被删除的最老一份(about-to-expire)
mailfirst 发送刚轮转出的最新一份(just-rotated)

6.10.3 自定义邮件正文与主题

使用 postrotate 调用 mail 命令可灵活控制:

/var/log/ssh {
    monthly
    rotate 2
    missingok
    sharedscripts
    postrotate
        echo "ssh轮转成功了.xxxxx" | mail -s "logrotated succeed" test
    endscript
}

6.11 date 时间格式(编写归档脚本常用)

date -d "4 days ago" +%d    # 4 天前的日
date -d "-4 days" +%d       # 4 天前的日
date -d "+4 days" +%d       # 4 天后的日
date -d "4 days" +%d        # 4 天后的日
date -d "tomorrow" +%d
date -d "yesterday" +%d
date -d "4 month" +%m
date -d "1 year" +%Y
date +%F                    # 2026-08-22
date +%Y%m%d%H%M%S

更多格式参见 man date。

6.12 日志文件的 append-only 属性保护

为避免现有日志被意外覆盖/删除,可给日志文件加 a 属性,只允许在文件尾部追加,不能修改已有数据、不能删除文件。

sudo chattr +a filename    # 添加 a 属性
sudo chattr -a filename    # 删除 a 属性
sudo lsattr filename       # 查看隐藏属性

注意:这只适用于以追加方式打开日志的程序(如 rsyslog);wtmp、btmp 不能使用这种方法。加了 a 属性的文件也无法被 logrotate 用 create 方式轮转(重命名会失败),需先 chattr -a。


七、日志分析实战

7.1 分析方法概览

  • 脚本分析:使用 shell/grep/awk/sed 等自行编写分析脚本,最灵活;
  • 软件分析:常见开源软件有 awstats、webalizer(多用于 Web 日志);
  • 系统自带:logwatch 可分析 /var/log/ 下的日志并以邮件形式发送报告;
  • 专业工具:如 Oracle 等数据库的日志巡检工具;
  • 集中平台:日志量大、需要检索与可视化时使用 ELK,参见 08-04-ELK日志分析.md;需要指标告警时使用 Zabbix,参见 08-05-Zabbix监控.md。

7.2 grep / awk / sed 常用套路

# 关键字过滤(不区分大小写、显示行号)
grep -in 'error' /var/log/messages

# 多关键字(或)
grep -E 'error|fail|warn' /var/log/messages

# 排除干扰行
grep -i error /var/log/messages | grep -v 'ignored'

# 显示匹配行的上下文(前 3 行、后 3 行)
grep -B 3 -A 3 'Segmentation fault' /var/log/messages
grep -C 5 'OOM' /var/log/messages

# 只统计出现次数
grep -c 'Failed password' /var/log/secure

# 递归搜索整个日志目录
grep -rn 'connection refused' /var/log/ 2>/dev/null

# 压缩日志直接搜索
zgrep 'error' /var/log/messages-20260801.gz
zcat /var/log/nginx/access.log-*.gz | awk '{print $1}' | sort | uniq -c

# awk 取字段(默认空格分隔)
awk '{print $1, $2, $3, $5}' /var/log/secure

# awk 条件过滤 + 统计
awk '$6 ~ /Failed/ {print $11}' /var/log/secure | sort | uniq -c | sort -rn

# sed 提取指定行区间
sed -n '100,200p' /var/log/messages

# sed 按时间范围截取(从某时间戳到某时间戳)
sed -n '/Aug 22 09:00/,/Aug 22 10:00/p' /var/log/messages

# 实时跟踪 + 过滤
tail -f /var/log/nginx/access.log | grep --line-buffered ' 500 '

7.3 统计登录失败与暴力破解 IP

# 1) 统计失败登录的来源 IP 及次数(降序)
grep 'Failed password' /var/log/secure \
  | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

# 2) 另一种取 IP 方式(更稳健,用正则抽取)
grep 'Failed password' /var/log/secure \
  | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' \
  | sort | uniq -c | sort -rn | head -20

# 3) 统计被尝试的用户名
grep 'Failed password' /var/log/secure \
  | awk '{for(i=1;i<=NF;i++) if($i=="for") print $(i+1)}' \
  | sort | uniq -c | sort -rn

# 4) 统计无效用户(不存在的用户名)
grep 'Invalid user' /var/log/secure | awk '{print $8, $10}' | sort | uniq -c | sort -rn

# 5) 成功登录记录(对照检查是否被攻破)
grep 'Accepted password' /var/log/secure | awk '{print $1,$2,$3,$9,$11}'

# 6) 用二进制日志统计失败登录
lastb | awk '{print $3}' | sort | uniq -c | sort -rn | head -20

# 7) journald 方式(CentOS 7)
journalctl -u sshd --since today | grep 'Failed password'
journalctl _COMM=sshd -p warning --since "-1day"

# 8) 找出失败超过 10 次的 IP,写入黑名单(可配合 firewalld/iptables 封禁)
grep 'Failed password' /var/log/secure \
  | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' \
  | sort | uniq -c | awk '$1>10 {print $2}' > /tmp/blacklist.txt

while read ip; do
    firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=$ip drop"
done < /tmp/blacklist.txt
firewall-cmd --reload

一个完整的暴力破解检测脚本示例:

#!/bin/bash
# deny_hack.sh —— 统计 SSH 失败登录并封禁高频 IP
LOG=/var/log/secure
LIMIT=10
DENY=/etc/hosts.deny

grep 'Failed password' $LOG \
  | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' \
  | sort | uniq -c | sort -rn \
  | while read count ip; do
        if [ "$count" -gt "$LIMIT" ]; then
            grep -q "$ip" $DENY || echo "sshd:$ip:deny" >> $DENY
            logger -t deny_hack -p auth.warning "blocked $ip ($count failures)"
        fi
    done

7.4 统计访问量与状态码(Web 日志)

以 Nginx/Apache 的组合日志格式为例:

192.168.1.100 - - [22/Aug/2026:10:23:45 +0800] "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0"
    $1                      $4                    $6    $7        $8      $9  $10
LOG=/var/log/nginx/access.log

# 1) 总请求数
wc -l $LOG

# 2) 独立 IP 数(UV 粗略估算)
awk '{print $1}' $LOG | sort -u | wc -l

# 3) 访问量最高的前 20 个 IP
awk '{print $1}' $LOG | sort | uniq -c | sort -rn | head -20

# 4) 状态码分布
awk '{print $9}' $LOG | sort | uniq -c | sort -rn

# 5) 所有 404 请求及其 URL
awk '$9==404 {print $7}' $LOG | sort | uniq -c | sort -rn | head -20

# 6) 所有 5xx 错误(服务端故障)
awk '$9 ~ /^5/ {print $0}' $LOG | tail -50

# 7) 访问量最高的 URL(热点资源)
awk '{print $7}' $LOG | sort | uniq -c | sort -rn | head -20

# 8) 流量统计(第 10 字段为响应字节数,换算为 MB)
awk '{sum+=$10} END {printf "total: %.2f MB\n", sum/1024/1024}' $LOG

# 9) 消耗流量最多的 URL
awk '{a[$7]+=$10} END {for(i in a) printf "%.2f MB %s\n", a[i]/1024/1024, i}' $LOG \
  | sort -rn | head -20

# 10) 每小时请求量(PV 趋势)
awk -F'[:[]' '{print $2":"$3}' $LOG | sort | uniq -c

# 11) 每分钟请求量峰值
awk '{print $4}' $LOG | cut -d: -f1-3 | sort | uniq -c | sort -rn | head -10

# 12) User-Agent 统计(识别爬虫)
awk -F'"' '{print $6}' $LOG | sort | uniq -c | sort -rn | head -20

# 13) 来源页面(Referer)统计
awk -F'"' '{print $4}' $LOG | sort | uniq -c | sort -rn | head -20

# 14) 某个 IP 的全部行为
grep '^192.168.1.100 ' $LOG | awk '{print $4, $7, $9}'

# 15) 平均响应时间(需日志格式中含 $request_time,假设为最后一个字段)
awk '{sum+=$NF; n++} END {printf "avg: %.3f s\n", sum/n}' $LOG

7.5 按时间段筛选

# 1) sed 按起止行内容截取(syslog 格式)
sed -n '/Aug 22 09:00:00/,/Aug 22 10:00:00/p' /var/log/messages

# 2) awk 匹配时间字段(Web 日志)
awk '$4 >= "[22/Aug/2026:09:00:00" && $4 <= "[22/Aug/2026:10:00:00"' \
    /var/log/nginx/access.log

# 3) grep 按小时粗筛
grep '22/Aug/2026:09:' /var/log/nginx/access.log

# 4) journald 精确时间过滤(最方便)
journalctl --since "2026-08-22 09:00" --until "2026-08-22 10:00"
journalctl --since "-2 hours" -p err

# 5) 找出指定时间之后修改过的日志文件
find /var/log -type f -newermt "2026-08-22 09:00" -ls

# 6) 按天分割大日志
awk '{print > ("/tmp/split-" $1 "-" $2 ".log")}' /var/log/messages

7.6 logwatch 自动分析报告

安装 logwatch:

yum install logwatch -y
rpm -qa | grep logwatch
logwatch-7.3.6-49.el6.noarch

查看配置文件清单:

rpm -qc logwatch-7.3.6-49.el6.noarch
/etc/logwatch/conf/ignore.conf
/etc/logwatch/conf/logwatch.conf
/etc/logwatch/conf/override.conf

查看主配置:

vim /usr/share/logwatch/default.conf/logwatch.conf
cat /usr/share/logwatch/default.conf/logwatch.conf | grep -v '#' | grep -v ^$
LogDir = /var/log                            # 日志目录
TmpDir = /var/cache/logwatch
MailTo = root                                # 发邮件给谁
MailFrom = Logwatch
Print =
Range = yesterday                            # 分析日志的日期范围
Detail = Low                                 # 详细程度:Low/Medium/High
Service = All
mailer = "sendmail -t"

运行分析:

/etc/init.d/postfix restart
logwatch
cat /var/mail/root

# 常用参数
logwatch --detail High --range today --service sshd --print
logwatch --range "between -7 days and today" --mailto root

7.7 日志合并

日志合并可通过追加、排序(sort)实现。

示例文件:

cat 1.txt
1
3
5
7
cat 2.txt
8
4
9
cat 3.txt
6
10
2

合并并按数字排序输出到 4.txt:

sort -n -o 4.txt 1.txt 2.txt 3.txt

其他常用合并方式:

cat access.log-2026082*.log > /tmp/all.log            # 简单追加
zcat access.log-*.gz | sort -k4 > /tmp/all.log        # 解压后按时间字段排序
sort -m -k4 a.log b.log > merged.log                  # 已排序文件的归并(省内存)

7.8 日志量与磁盘占用治理

# 1) 查看各分区使用率,确认 /var 是否告急
df -h
df -i                     # inode 也可能先耗尽

# 2) 定位大日志文件
du -sh /var/log/
du -sh /var/log/* | sort -rh | head -20
find /var/log -type f -size +100M -exec ls -lh {} \;

# 3) journal 占用
journalctl --disk-usage

# 4) 找出已被删除但仍被进程占用的文件(磁盘不释放的典型原因)
lsof | grep deleted
lsof +L1

# 5) 正确清空一个正在被写入的大日志(不要 rm!)
> /var/log/huge.log
truncate -s 0 /var/log/huge.log
cat /dev/null > /var/log/huge.log

# 6) 清理超过 30 天的历史日志
find /var/log -name "*.gz" -mtime +30 -delete
find /var/log -name "*.log-*" -mtime +30 -delete

# 7) 应急清理 journal
journalctl --vacuum-size=200M
journalctl --vacuum-time=7d

治理原则:

  1. 不要用 rm 删除正在被写入的日志,进程仍持有 inode,磁盘空间不会释放;应使用 > file 清空,或走 logrotate 的 copytruncate。
  2. 为所有自定义应用日志配置 logrotate,不要只依赖手工清理。
  3. 为 journal 设置 SystemMaxUse=,避免无上限增长。
  4. 高日志量服务考虑降低级别(把 debug/info 调为 warning),或用 rsyslog 的限流指令。
  5. 大流量场景把日志目录单独挂载一个分区/LVM,避免写满根分区导致系统不可用。

rsyslog 限流与降噪示例:

# 单个进程 5 秒内最多 500 条,超出丢弃(防日志风暴)
$SystemLogRateLimitInterval 5
$SystemLogRateLimitBurst 500

# 丢弃指定噪音日志
:msg, contains, "Deprecated warning"  ~
if $programname == 'noisy-app' and $syslogseverity >= 6 then stop

八、审计 auditd

auditd 是内核审计子系统的用户态守护进程,记录谁在什么时间对什么文件/系统调用做了什么,日志写入 /var/log/audit/audit.log。它独立于 rsyslog 与 journald,是满足合规审计(等级保护、PCI-DSS)的关键组件。

8.1 安装与服务管理

yum install audit audit-libs -y

# CentOS 6
service auditd start
chkconfig auditd on
service auditd status

# CentOS 7(注意:auditd 不能用 systemctl restart,需用 service)
systemctl start auditd
systemctl enable auditd
service auditd restart          # CentOS 7 上重启 auditd 的正确方式
systemctl status auditd

主要文件:

路径 作用
/etc/audit/auditd.conf 守护进程配置:日志大小、轮转、磁盘满时的动作
/etc/audit/audit.rules(CentOS 6)/ /etc/audit/rules.d/*.rules(CentOS 7) 持久化审计规则
/var/log/audit/audit.log 审计日志

/etc/audit/auditd.conf 关键项:

log_file = /var/log/audit/audit.log
max_log_file = 100              # 单文件最大 100 MB
num_logs = 10                   # 保留 10 份
max_log_file_action = ROTATE    # 达到上限后轮转(IGNORE/SYSLOG/SUSPEND/ROTATE/KEEP_LOGS)
space_left = 200                # 剩余 200MB 触发 space_left_action
space_left_action = SYSLOG
admin_space_left_action = SUSPEND
disk_full_action = SUSPEND
flush = INCREMENTAL_ASYNC

auditd 有自己的轮转机制(max_log_file + num_logs),一般不需要也不建议再用 logrotate 管理 audit.log。

8.2 auditctl 规则管理

auditctl -s                     # 查看审计状态(enabled、pid、丢包数等)
auditctl -l                     # 列出当前生效的规则
auditctl -D                     # 删除所有规则(临时)
auditctl -e 1                   # 启用审计(0 禁用,2 锁定至重启)
auditctl -b 8192                # 设置内核缓冲区大小
auditctl -r 100                 # 设置每秒消息速率上限

文件/目录监控规则(watch 规则):

# -w 监控路径  -p 权限(r读 w写 x执行 a属性变更)  -k 关键字(便于检索)
auditctl -w /etc/passwd -p wa -k passwd_changes
auditctl -w /etc/shadow -p wa -k shadow_changes
auditctl -w /etc/sudoers -p wa -k sudoers_changes
auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config
auditctl -w /var/log/audit/ -p wa -k audit_log_tamper
auditctl -w /usr/bin/passwd -p x -k passwd_exec
auditctl -w /etc/ -p wa -k etc_changes            # 监控整个目录(递归)

# 删除某条规则(把 -w 换成 -W,参数保持一致)
auditctl -W /etc/passwd -p wa -k passwd_changes

系统调用监控规则(syscall 规则):

# -a 追加规则  always,exit:在系统调用退出时总是记录
# -F 过滤条件  -S 系统调用名

# 监控所有删除/重命名操作(uid>=1000 即普通用户)
auditctl -a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat \
         -F auid>=1000 -F auid!=4294967295 -k delete

# 监控权限变更
auditctl -a always,exit -F arch=b64 -S chmod -S fchmod -S chown -S fchown \
         -F auid>=1000 -k perm_mod

# 监控挂载操作
auditctl -a always,exit -F arch=b64 -S mount -k mounts

# 监控指定用户的所有命令执行
auditctl -a always,exit -F arch=b64 -S execve -F auid=1001 -k user1001_cmd

# 监控对某目录的访问失败(权限拒绝)
auditctl -a always,exit -F dir=/data/secret -F success=0 -k secret_denied

规则持久化:

# CentOS 7:写入 rules.d 下的文件
vim /etc/audit/rules.d/audit.rules
## 先删除已有规则
-D
## 缓冲区
-b 8192
## 关键文件监控
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/group -p wa -k group_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /etc/ssh/sshd_config -p wa -k sshd_config
## 删除操作监控
-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete
## 权限变更监控
-a always,exit -F arch=b64 -S chmod -S fchmod -S chown -S fchown -F auid>=1000 -k perm_mod
## 锁定规则,禁止运行时修改(放在最后,需重启生效)
# -e 2
augenrules --load             # 加载 rules.d 下的规则(CentOS 7)
service auditd restart
auditctl -l                   # 验证

8.3 ausearch 检索审计日志

ausearch -k passwd_changes                    # 按关键字(-k)检索
ausearch -k delete -i                         # -i 把数字 ID 翻译成名字(可读性关键)
ausearch -f /etc/passwd -i                    # 按文件名检索
ausearch -ui 1000 -i                          # 按用户 ID
ausearch -ul root -i                          # 按登录用户名
ausearch -p 1234                              # 按进程 ID
ausearch -sc execve -i                         # 按系统调用
ausearch -m USER_LOGIN -i                     # 按消息类型
ausearch -m USER_LOGIN --success no -i        # 登录失败事件
ausearch -ts today -i                         # 时间起点:today/yesterday/recent/this-week
ausearch -ts 08/22/2026 09:00:00 -te 08/22/2026 10:00:00 -i
ausearch -ts recent -m AVC -i                 # SELinux 拒绝事件
ausearch -x /usr/bin/passwd -i                 # 按可执行文件
ausearch -i --input /var/log/audit/audit.log.1  # 指定旧日志文件

常见消息类型(-m):

类型 含义
USER_LOGIN / USER_LOGOUT 用户登录/登出
USER_AUTH / USER_ACCT 认证、账号检查
USER_CMD 通过 sudo 执行的命令
CRED_ACQ / CRED_DISP 凭据获取/释放
SYSCALL / PATH / CWD 系统调用及其涉及的路径
CONFIG_CHANGE 审计规则本身被修改
AVC SELinux 访问拒绝
ANOM_ABEND 进程异常终止

8.4 aureport 生成审计报表

aureport                                  # 汇总报表
aureport -au                              # 认证事件报表(authentication)
aureport -au --failed                     # 认证失败报表(找暴力破解)
aureport -au --success                    # 认证成功报表
aureport -l                               # 登录报表
aureport -l --failed -i
aureport -u                               # 用户活动报表
aureport -f                               # 文件访问报表
aureport -f --failed -i                   # 访问被拒的文件
aureport -s                               # 系统调用报表
aureport -x                               # 可执行文件报表
aureport -x --summary                     # 汇总(按次数统计)
aureport -c                               # 配置变更报表
aureport -m                               # 账号修改报表
aureport -k                               # 按关键字汇总
aureport -p                               # 进程报表
aureport -ts today -te now -i --summary   # 指定时间范围
aureport --start yesterday --end now -i

8.5 监控文件变更完整实操

# 1) 添加规则:监控 /etc/passwd 的写入与属性变更
auditctl -w /etc/passwd -p wa -k passwd_watch
auditctl -l

# 2) 触发一次变更
useradd testaudit

# 3) 检索审计记录
ausearch -k passwd_watch -i | tail -40

输出片段(-i 翻译后)示例:

type=SYSCALL msg=audit(08/22/2026 10:15:32.123:456) : arch=x86_64 syscall=openat
success=yes exit=4 ... auid=root uid=root gid=root euid=root
comm=useradd exe=/usr/sbin/useradd key=passwd_watch
type=PATH msg=audit(...) : item=0 name=/etc/passwd inode=1234567 ...

关键字段解读:

字段 含义
auid 审计用户 ID(原始登录用户,即使 su 到 root 也不变,追责关键)
uid / euid 实际/有效用户 ID
comm 触发事件的命令名
exe 命令的完整路径
key 规则中 -k 指定的关键字
success 操作是否成功
syscall 触发的系统调用
# 4) 生成文件访问报表
aureport -f -i --summary

# 5) 清理规则
auditctl -W /etc/passwd -p wa -k passwd_watch

补充:/var/log/audit/audit.log 只有 root 可读;SELinux 的拒绝事件也记录在此,ausearch -m AVC -ts recent 是排查 SELinux 问题的标准手段(详见防火墙与 SELinux 相关文档)。


九、排错清单

9.1 日志不写入 / 日志为空

检查点 命令 / 处理
日志服务是否运行 systemctl status rsyslog / service rsyslog status;ps -ef \| grep rsyslog
配置语法是否有误 rsyslogd -N1;查看 journalctl -u rsyslog -n 50 中的报错
配置是否已重载 systemctl restart rsyslog(改完必须重启/reload)
选择器写错 用 logger -p local5.info -t TEST "hi" 造日志验证规则是否命中
前面的规则用了 stop/~ 匹配到更早的规则被丢弃,检查规则顺序
目标目录不存在或无权限 ls -ld /var/log/xxx/;rsyslog 需要目录存在(或用 $CreateDirs on)
磁盘或 inode 满 df -h、df -i
文件被加了 a/i 属性 lsattr /var/log/xxx;必要时 chattr -a
SELinux 阻止写入非标准路径 ausearch -m AVC -ts recent;semanage fcontext -a -t var_log_t "/data/logs(/.*)?" 后 restorecon -Rv /data/logs
轮转后进程仍写旧 inode 见 6.6 节,需 kill -HUP/USR1 或改用 copytruncate
日志被限流丢弃 检查 $SystemLogRateLimitBurst、journald 的 RateLimitBurst
journal 查不到历史 journal 默认易失,需开启持久化(5.3 节)

9.2 日志文件过大占满磁盘

现象 原因与处理
df -h 显示满,但 du -sh 加起来远小于占用 有被删除但仍被占用的文件:lsof \| grep deleted,重启对应进程或用 > /proc/<pid>/fd/<n> 清空
某个日志文件几十 G 用 > file 或 truncate -s 0 file 清空,不要 rm;随后补上 logrotate 配置
logrotate 已配置但文件仍增长 检查 logrotate -d 输出、/var/lib/logrotate/logrotate.status 中的时间、size/minsize 条件是否满足
journal 占用过大 journalctl --disk-usage;journalctl --vacuum-size=200M;配置 SystemMaxUse=
df -i 显示 inode 满 大量小日志碎片文件,find /var/log -type f \| wc -l,清理历史文件、启用 compress
应用 debug 级别开太多 调高日志级别,或用 rsyslog 限流/丢弃规则降噪
根本治理 日志目录单独分区/LVM;集中收集到日志服务器(4.11 节)并在本地只留短期

9.3 时间戳 / 时区不对

检查点 命令 / 处理
系统时区 timedatectl(CentOS 7);timedatectl set-timezone Asia/Shanghai;CentOS 6:cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
系统时间是否准确 date;ntpdate ntp.aliyun.com;systemctl enable --now chronyd;chronyc sources -v
硬件时钟与系统时钟不一致 hwclock --show;hwclock -w(系统→硬件)/ hwclock -s(硬件→系统)
RTC 按 UTC 还是本地时间 timedatectl 输出中的 RTC in local TZ;timedatectl set-local-rtc 0
日志时间戳无年份、无时区 rsyslog 传统格式只到秒且无时区,改用高精度模板:$ActionFileDefaultTemplate RSYSLOG_FileFormat
远程日志时间与服务端不符 远程日志默认保留发送端时间戳;确保所有客户端 NTP 同步;或在服务端模板改用 %timegenerated% 统一为接收时间
Docker/容器内日志时区错 挂载 /etc/localtime:/etc/localtime:ro 或设置 TZ=Asia/Shanghai
dmesg 时间看不懂 用 dmesg -T
journal 显示时区 journalctl --utc 用 UTC 显示;否则按本地时区

9.4 轮转不生效

检查点 命令 / 处理
配置语法/匹配是否正确 logrotate -d /etc/logrotate.d/xxx(预演,重点看 "considering log ... " 与 "log does not need rotating")
周期未到 cat /var/log/logrotate.status 查看上次轮转时间;测试用 logrotate -f 强制
触发任务未运行 systemctl list-timers \| grep logrotate;systemctl status logrotate.timer;CentOS 6 检查 crond 是否运行、/etc/cron.daily/logrotate 是否可执行
配置文件名含被忽略的扩展名 logrotate 忽略 .rpmsave、.rpmnew、.dpkg-dist、~ 等(tabooext);文件名不要带点号后缀
配置文件权限/属主不对 必须 root 可读;目录 /etc/logrotate.d 权限 755
日志文件为空 配了 notifempty 时空文件不轮转,属正常行为
日志目录属主非 root 报 parent directory has insecure permissions 或 skipping,需加 su USER GROUP 指令
轮转成功但进程还写旧文件 缺少 postrotate 信号,或该程序不支持,改用 copytruncate(6.6 节)
同一日志被两处配置重复管理 例如自己写的配置与 rpm 自带配置冲突,报 duplicate log entry,删掉其中一份
文件加了 a 属性 create 方式无法重命名,先 chattr -a 或改用 copytruncate
SELinux 阻止 ausearch -m AVC -ts recent \| grep logrotate

9.5 远程日志收不到

按"客户端 → 网络 → 服务端"顺序排查:

# ---------- 服务端 ----------
# 1) 模块是否加载、端口是否监听
grep -E 'imtcp|imudp|514' /etc/rsyslog.conf
netstat -ntlup | grep :514
ss -ntlup | grep :514

# 2) 防火墙是否放行
iptables -L -n | grep 514                    # CentOS 6
firewall-cmd --list-ports                    # CentOS 7
firewall-cmd --permanent --add-port=514/tcp; firewall-cmd --reload

# 3) SELinux 是否允许绑定 514
getenforce
semanage port -l | grep syslog
semanage port -a -t syslogd_port_t -p tcp 514

# 4) 抓包确认是否收到数据
tcpdump -i any -nn port 514
tcpdump -i eth0 -nn -A udp port 514

# ---------- 客户端 ----------
# 5) 转发规则是否正确(@ = UDP,@@ = TCP,端口不能漏)
grep '@' /etc/rsyslog.conf

# 6) 网络连通性
ping 192.168.1.2
telnet 192.168.1.2 514
nc -zv 192.168.1.2 514
nc -uzv 192.168.1.2 514

# 7) 造一条测试日志
logger -p local5.info -t REMOTETEST "hello $(hostname) $(date)"

# 8) 查看客户端自身报错
journalctl -u rsyslog -n 50
tail -f /var/log/messages | grep rsyslogd

常见原因汇总:

原因 处理
服务端未打开 imtcp/imudp 注释 取消注释并重启 rsyslog
防火墙/安全组未放行 514 放行 TCP 与 UDP 514
SELinux 未允许非默认端口 semanage port -a -t syslogd_port_t -p tcp 514
客户端写成单 @ 但服务端只开 TCP(或反之) 协议对齐:@=UDP,@@=TCP
客户端规则未匹配到日志 检查 facility 是否与应用配置一致(如 SyslogFacility local5)
服务端有规则但没有落盘文件 服务端需要一条 local5.* /var/log/ssh 之类的动作规则
多主机日志混在一起无法区分 主机名必须唯一,并用动态模板按 %HOSTNAME% 分目录(4.11 节步骤五)
UDP 丢包 改用 TCP,并配置磁盘队列(4.11 节步骤二)
服务端磁盘满 df -h,为集中日志配置 logrotate
服务端日志被 stop 规则提前丢弃 调整规则顺序

9.6 其他常见坑

  1. rsyslog 配置中,-/path 表示不立即刷盘,非正常关机会丢日志。
  2. logrotate 的 size 会忽略周期立即轮转,minsize 则需同时满足大小和周期。
  3. 给日志文件加 a 属性仅适用于追加写入的日志;wtmp、btmp 不适用。
  4. CentOS 7 默认不持久化 journal 日志,重启后 /run/log/journal/ 内容丢失;需创建 /var/log/journal/ 并重启 systemd-journald。
  5. CentOS 7 上重启 auditd 要用 service auditd restart,systemctl restart auditd 可能被拒绝。
  6. 二进制日志(wtmp/btmp/utmp/lastlog/journal)不能用 cat/grep 直接读。
  7. 用 rm 删日志不会释放磁盘(进程仍持有 inode),要用 > file 清空。
  8. journalctl 和 /var/log/messages 可能显示同一条日志,不是重复记录问题。

十、练习

10.1 rsyslog 基础练习

  1. 把所有应用的日志(info 及以上)写入 /var/log/logfile,该如何配置?
  2. 把 ssh 的日志指定记录到 /var/log/ssh 下,有哪些方法?
  3. 上网查阅并了解日志分析与日志合并的方法、概念。

参考答案

题 1:

vim /etc/rsyslog.conf
*.info    /var/log/logfile
service rsyslog restart
ls -l /var/log/logfile

系统会自动创建该文件:

-rw------- 1 root root 784 10月 26 01:20 /var/log/logfile

题 2:将 ssh 日志记录到 /var/log/ssh 的方法

方法一:将 ssh 托管到 xinetd 下,直接写文件:

log_type = file /var/log/ssh

方法二:托管到 xinetd 下,走 syslog:

log_type = syslog local5 info

并在 /etc/rsyslog.conf 中添加:

local5.info    /var/log/ssh

方法三:不托管到 xinetd,直接使用 sshd:

vim /etc/ssh/sshd_config
SyslogFacility local5
/etc/init.d/sshd restart
vim /etc/rsyslog.conf
local5.*    /var/log/ssh
/etc/init.d/rsyslog restart

10.2 logrotate 顺序思考题

先执行:

logger -t "哈哈" "你好"
tail -3 /var/log/messages

默认此信息在当前 messages 中。

场景 A:

sharedscripts
postrotate
    logger -t "呵呵" "再见"
    /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript

答:logger 在 kill -HUP 之前执行,rsyslog 仍持有旧文件句柄,日志写入旧文件,轮转后位于 messages.1。

场景 B:

sharedscripts
postrotate
    /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    logger -t "呵呵" "再见"
endscript

答:kill -HUP 先执行,rsyslog 重新打开新文件,日志写入 messages。

10.3 日志轮转练习(clientssh.log 完整题)

题目

  1. 为某个服务日志(假设为 /var/log/clientssh.log)配置日志轮转,要求:
    1. 配置写到 /etc/logrotate.conf 中,或者写到 /etc/logrotate.d/ 目录下的子配置文件中;
    2. 每天轮转一次;
    3. 最多保留 4 个副本。
  2. 在对 /var/log/clientssh.log 进行轮转之前,自动把最后一个副本(即 /var/log/clientssh.log.4)拷贝到 /backup/4天前的年/4天前的月/4天前的天.clientssh.log。
    • 例如:今天是 2014-07-28,则拷贝后的文件名为 /backup/2014/7/24.clientssh.log。
  3. 轮转完成后,发一封邮件通知 root 用户,邮件主题和正文自定义。

参考解答

编辑 /etc/logrotate.conf(也可在 /etc/logrotate.d/ 下新建独立文件):

vim /etc/logrotate.conf

加入以下配置:

/var/log/clientssh.log {
    rotate 4
    daily
    create 0664 root root
    sharedscripts
    prerotate
        mkdir -p /backup/`date -d "-4 days" +%Y`/`date -d "-4 days" +%m`/
        cp /var/log/clientssh.log.4 /backup/`date -d "-4 days" +%Y`/`date -d "-4 days" +%m`/`date -d "-4 days" +%d`.clientssh.log
    endscript
    sharedscripts
    postrotate
        kill -HUP `cat /var/run/sshd.pid`
        echo "日志轮转了" | mail -s 'ssh日志轮转了' root
    endscript
}

指令解析

指令/脚本段 作用
rotate 4 最多保留 4 个历史副本
daily 每天轮转一次
create 0664 root root 轮转后以指定权限和属主重新创建空日志文件
sharedscripts 多次匹配日志时只执行一次脚本(与通配符配合使用)
prerotate ... endscript 轮转之前执行的 Shell 命令:创建按年月日分层的备份目录,并将 .log.4 拷贝为 4 天前日期命名的归档文件
postrotate ... endscript 轮转之后执行的 Shell 命令:给 sshd 发送 HUP 信号让其重新打开日志文件,并向 root 发送通知邮件
date -d "-4 days" +%Y/%m/%d 取 4 天前的年、月、日,用于按题目命名归档文件

关于 kill -HUP

kill -HUP $(cat /var/run/sshd.pid)(原文写作反引号形式)并非杀掉 sshd,而是通知它重新读取配置/重新打开日志文件。如果对应服务不支持 HUP 重新打开日志,可改用 copytruncate 方式,无需重启进程。

手动测试

配置完成后,可使用以下命令手动触发一次轮转(调试用):

logrotate -d /etc/logrotate.conf     # 预演,不真正执行
logrotate -f /etc/logrotate.conf     # 强制执行一次轮转

确认邮件是否发出:

mail -u root

10.4 练习:will-log 独立轮转配置

为 will-log 配置独立轮转:

  1. 创建 15M 的日志文件 /var/log/will-log;
  2. 创建独立轮转配置 /etc/logrotate.d/will-log;
  3. 同一个文件最多轮转 7 次;
  4. 只要文件大于 20M 就轮转,不管周期是否已到;
  5. 轮转之后创建新文件,权限 0664,属主 root:root;
  6. 用日期作为轮转后文件的后缀名(dateext);
  7. 如果日志文件不存在,不报错(missingok);
  8. 该服务每天自动轮转一次(daily);
  9. 每分钟向该日志文件追加 10M 数据;
  10. 每分钟调用一次该轮转服务(可用 logrotate /etc/logrotate.d/will-log 或配合 cron)。

参考配置:

/var/log/will-log {
    daily
    rotate 7
    size 20M
    create 0664 root root
    dateext
    missingok
}

参考操作步骤:

# 1) 创建 15M 日志文件
head -c 15M /dev/zero > /var/log/will-log

# 2) 写入轮转配置
vim /etc/logrotate.d/will-log

# 3) 每分钟追加 10M 数据 + 每分钟触发轮转
crontab -e
* * * * * head -c 10M /dev/zero >> /var/log/will-log
* * * * * /usr/sbin/logrotate /etc/logrotate.d/will-log
# 4) 观察结果
watch -n 10 'ls -lh /var/log/will-log*'

10.5 练习:远程日志集中收集

  1. 准备两台机器:日志服务器 192.168.1.2、客户端 192.168.1.61。
  2. 在服务端开启 TCP 514 接收,验证端口监听。
  3. 在客户端把 sshd 的 facility 改为 local5,并把 local5.* 转发到服务端。
  4. 在服务端把 local5.* 落盘到 /var/log/ssh,从第三台机器 ssh 客户端并验证日志。
  5. 进阶:在服务端使用动态模板,把不同客户端的日志分别存放到 /var/log/remote/<主机名>/<日期>-<程序名>.log。
  6. 进阶:为 /var/log/remote/ 下的日志编写 logrotate 配置,按天轮转、压缩、保留 30 天。

(参考步骤见 4.11 节全部内容;轮转配置可参考 6.9 节。)

10.6 练习:journald 持久化与查询

  1. 确认当前 journal 是易失还是持久(查看 /run/log/journal/ 与 /var/log/journal/)。
  2. 通过创建目录与修改 journald.conf 两种方式开启持久化,并限制最大占用 500M。
  3. 重启系统,使用 journalctl -b -1 验证能查到上次启动的日志。
  4. 分别用 -u、-p、--since、-k、_COMM= 查询 sshd 相关日志。
  5. 用 -o json-pretty 输出一条日志,观察结构化字段。
  6. 用 --disk-usage 与 --vacuum-time=1week 完成一次清理。

10.7 练习:日志分析

  1. 统计 /var/log/secure 中失败登录次数最多的前 10 个 IP。
  2. 找出这些 IP 尝试过的用户名清单。
  3. 统计 Nginx 访问日志的状态码分布、Top 20 URL、每小时 PV。
  4. 找出 5xx 错误对应的完整请求行,并按 URL 汇总。
  5. 编写脚本:每 5 分钟检测失败登录超过 10 次的 IP 并写入 /etc/hosts.deny,同时用 logger 记录一条 auth.warning 日志。

(参考命令见 7.3、7.4 节。)

10.8 练习:auditd 文件审计

  1. 安装并启动 auditd,确认 auditctl -s 输出。
  2. 添加规则监控 /etc/passwd、/etc/shadow、/etc/sudoers 的写入与属性变更。
  3. 执行 useradd、passwd、visudo 触发事件。
  4. 用 ausearch -k <key> -i 检索并解读 auid、comm、exe 字段。
  5. 用 aureport -f -i --summary、aureport -au --failed 生成报表。
  6. 把规则写入 /etc/audit/rules.d/audit.rules 实现持久化,重启后用 auditctl -l 验证。

十一、命令速查

类别 命令 作用
文本日志 tail -f /var/log/messages 实时跟踪系统日志
文本日志 tail -n 100 FILE / less FILE 查看尾部 / 分页阅读
文本日志 grep -in 'error' FILE 关键字检索
文本日志 zgrep 'error' FILE.gz 检索压缩日志
登录记录 last / last -F / last -x 读 wtmp:登录历史、完整时间、开关机记录
登录记录 lastb 读 btmp:失败登录记录
登录记录 who / who -aH / who -b / w 读 utmp:在线用户、启动时间
登录记录 lastlog / lastlog -u USER 用户最后一次登录
消息 write / wall / mesg 发送/控制终端消息
内核日志 dmesg -T / dmesg -w / dmesg -l err 内核环形缓冲区(务必加 -T)
rsyslog logger -t TAG -p facility.priority "msg" 发送测试日志
rsyslog rsyslogd -N1 配置语法检查
rsyslog systemctl restart rsyslog / service rsyslog restart 重启日志服务(C7/C6)
rsyslog netstat -ntlup \| grep :514 / ss -ntlup \| grep :514 确认远程接收端口
rsyslog @host:514 / @@host:514 转发:UDP / TCP
journal journalctl -u UNIT -f 实时跟踪指定服务日志
journal journalctl -xe 排查服务启动失败的第一步
journal journalctl -b / -b -1 / --list-boots 本次/上次启动的日志
journal journalctl -p err --since today 按级别与时间过滤
journal journalctl -k 只看内核日志
journal journalctl _COMM=sshd _PID=123 按结构化字段过滤
journal journalctl -o verbose / -o json-pretty / -o cat 切换输出格式
journal journalctl --disk-usage 查看日志占用空间
journal journalctl --vacuum-size=200M / --vacuum-time=2weeks 清理日志
journal /etc/systemd/journald.conf → Storage=persistent 开启持久化
轮转 logrotate -d /etc/logrotate.conf 调试预演(不修改文件)
轮转 logrotate -fv /etc/logrotate.conf 强制轮转并显示过程
轮转 cat /var/lib/logrotate/logrotate.status 查看上次轮转时间
轮转 systemctl list-timers \| grep logrotate 查看轮转触发计划(C7.4+)
轮转 kill -HUP / kill -USR1 / copytruncate 让进程重新打开日志的三种思路
属性 chattr +a / -a / lsattr 日志追加保护属性
审计 auditctl -l / -s / -w PATH -p wa -k KEY 审计规则管理
审计 ausearch -k KEY -i / -f FILE -i / -ts today 检索审计日志
审计 aureport -au --failed / -f --summary 生成审计报表
审计 augenrules --load / service auditd restart 加载持久化规则
分析 awk '{print $1}' access.log \| sort \| uniq -c \| sort -rn 统计 Top IP
分析 logwatch --detail High --range today --print 自动分析报告
分析 sort -n -o out.txt a.txt b.txt 合并并排序多份日志
治理 du -sh /var/log/* \| sort -rh \| head 定位大日志
治理 lsof \| grep deleted 找出已删除但仍占用磁盘的日志
治理 > FILE / truncate -s 0 FILE 正确清空正在写入的日志

十二、相关文档

主题 文档
系统启动流程、运行级别、systemd 服务管理与 unit 编写、at/cron/systemd timer 定时任务 08-01
ELK(Elasticsearch + Logstash + Kibana)日志集中检索与可视化平台 08-04-ELK日志分析.md
Zabbix 监控与告警 08-05-Zabbix监控.md
dump/restore 文件系统备份与恢复、LVM 快照备份 06-备份与恢复.md
防火墙放行 514 端口、SELinux 端口与文件上下文 防火墙与 SELinux 相关文档
最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏