Linux日志管理体系:rsyslog + journald + logrotate 实战指南
一、概述
1.1 背景介绍
线上故障排查,90%的时间花在翻日志上。日志管理做得好不好,直接决定了故障恢复的速度。我们团队之前处理过一次数据库主从切换故障,因为日志分散在十几台机器上、格式不统一、部分还被logrotate清掉了,排查花了将近4个小时。后来把日志体系重新梳理了一遍,同类故障的定位时间缩短到15分钟以内。
CentOS 7之后systemd引入了journald,和传统的rsyslog并存。很多运维搞不清两者的关系和配合方式,要么只用rsyslog忽略了journald的结构化查询能力,要么只看journalctl不知道日志落盘到了哪里。实际生产环境中,两者协同使用才是正确姿势。
本篇从rsyslog、journald、logrotate三个核心组件入手,覆盖日志收集、存储、轮转、远程转发的完整链路,所有配置都在CentOS 7/8和Ubuntu 20.04/22.04上线上验证过。
1.2 技术特点
rsyslog基于facility/severity的分类机制:沿用了传统syslog的分类体系,通过facility(设施)标识日志来源(kern、auth、cron、local0-local7等),通过severity(级别)标识严重程度(emerg到debug共8级)。这套机制虽然古老但非常实用,配合过滤规则可以精确控制每条日志的去向。rsyslog在此基础上扩展了基于属性和基于表达式的高级过滤,处理能力实测可达每秒20万条以上。
journald二进制结构化存储支持索引查询:journald把日志存成二进制格式,自带索引,支持按服务名、PID、时间范围、优先级等多维度组合查询。查某个服务最近1小时的错误日志,一条journalctl命令就搞定,不用grep翻文件。缺点是二进制格式不能直接用文本工具处理,磁盘损坏时恢复难度大。
两者可协同工作互补:默认配置下journald收集所有systemd管理的服务日志,同时通过imjournal模块转发给rsyslog落盘为文本文件。这样既保留了journald的结构化查询能力,又有rsyslog的文本日志兜底。远程转发、日志归档这些事交给rsyslog更合适,实时查询用journalctl更方便。
1.3 适用场景
单机日志规范化管理:中小规模环境(50台以内),每台机器本地做好日志分类、轮转、保留策略,配合journalctl做快速查询。这是最基础的场景,也是后续集中化日志的前提。日志分类不清晰的机器,接入ELK之后只会制造更多混乱。
集中式日志收集架构:通过rsyslog的远程转发功能,把多台机器的日志汇聚到中心日志服务器。中等规模(50-500台)用rsyslog直接转发到中心rsyslog服务器就够了,成本低、稳定。我们团队管理的200多台机器就是这个方案,跑了3年没出过问题。
合规审计与日志留存:金融、政务等行业对日志有明确的留存要求(通常6个月到1年)。需要配合logrotate做压缩归档,配合rsyslog做远程备份,确保日志不丢失、不被篡改。这个场景下建议开启rsyslog的RELP协议做可靠传输。
1.4 环境要求
| | |
|---|
| CentOS 7+/RHEL 7+/Ubuntu 18.04+ | 需要systemd支持,CentOS 6用的是rsyslog+syslog-ng方案,不在本文范围 |
| | CentOS 7自带8.24,功能够用;8.2102+支持更多输出模块和更好的性能 |
| | |
| | |
| | 生产环境日志量大的机器建议单独挂载/var/log分区,避免日志撑满根分区 |
| | |
二、详细步骤
2.1 准备工作
2.1.1 Linux日志体系架构
先搞清楚日志在系统里的流转路径,不然后面的配置改起来心里没底。
内核消息 ──→ /dev/kmsg ──→ rsyslog(imklog) ──→ /var/log/kern.log
↓
systemd服务 ──→ journald ──→ rsyslog(imjournal) ──→ /var/log/messages
↓ /var/log/secure
二进制日志 /var/log/cron
/var/log/journal/ /var/log/maillog
...
应用程序 ──→ syslog() ──→ /dev/log ──→ rsyslog(imuxsock) ──→ 按规则分发
应用程序 ──→ 直接写文件 ──→ /var/log/app/xxx.log ──→ logrotate轮转
几个关键点:
- systemd管理的服务,stdout/stderr会被journald捕获,不需要应用自己处理日志文件
- 传统的syslog()调用通过/dev/log这个unix socket进入rsyslog
- journald默认会把日志转发给rsyslog,所以rsyslog的/var/log/messages里能看到systemd服务的日志
- 应用自己写的日志文件(如nginx的access.log),不经过rsyslog,需要logrotate单独管理
2.1.2 系统检查
# 检查系统版本
cat /etc/os-release
# 确认systemd版本
systemctl --version
# 检查rsyslog是否安装和运行
rpm -qa | grep rsyslog # RHEL/CentOS
dpkg -l | grep rsyslog # Ubuntu/Debian
systemctl status rsyslog
# 检查journald状态
systemctl status systemd-journald
# 查看当前日志占用空间
du -sh /var/log/
journalctl --disk-usage
# 检查/var/log分区剩余空间
df -h /var/log/
2.1.3 安装依赖
CentOS/RHEL系统rsyslog默认已安装,Ubuntu需要确认:
# CentOS/RHEL - 安装rsyslog及常用模块
sudo yum install -y rsyslog rsyslog-relp rsyslog-mmjsonparse
# Ubuntu/Debian - 安装rsyslog及常用模块
sudo apt install -y rsyslog rsyslog-relp rsyslog-mmjsonparse
# 确认logrotate已安装(一般系统自带)
which logrotate
logrotate --version
# 如果需要远程转发用RELP协议,确认模块已安装
rpm -qa | grep rsyslog-relp # RHEL/CentOS
dpkg -l | grep rsyslog-relp # Ubuntu/Debian
2.2 核心配置
2.2.1 rsyslog核心配置详解
rsyslog的主配置文件是/etc/rsyslog.conf,配置分为三大块:模块加载、全局指令、规则。
模块加载部分:
# /etc/rsyslog.conf - 模块加载段
# 提供对本地系统日志的支持(通过/dev/log socket)
module(load="imuxsock"
SysSock.Use="on"
SysSock.Name="/dev/log"
SysSock.RateLimit.Interval="5"
SysSock.RateLimit.Burst="2000")
# 从journald读取日志
module(load="imjournal"
StateFile="imjournal.state"
IgnorePreviousMessages="off"
DefaultSeverity="5"
DefaultFacility="user")
# 内核日志
module(load="imklog")
# 标记消息(定期写入-- MARK --,用于确认rsyslog还活着)
module(load="immark" interval="600")
# TCP接收(如果本机是日志中心服务器才需要开启)
# module(load="imtcp" MaxSessions="500")
# input(type="imtcp" port="514" ruleset="remote")
# UDP接收
# module(load="imudp")
# input(type="imudp" port="514" ruleset="remote")
facility和severity级别:
这是rsyslog分类日志的基础,必须搞清楚:
Facility(设施)- 标识日志来源:
0 kern 内核消息
1 user 用户级消息(默认)
2 mail 邮件系统
3 daemon 系统守护进程
4 auth 认证/授权消息(login, su)
5 syslog rsyslog自身消息
6 lpr 打印系统
7 news 新闻系统
8 uucp UUCP子系统
9 cron 定时任务
10 authpriv 私有认证消息(ssh, sudo)
11 ftp FTP守护进程
16-23 local0-local7 本地自定义(给应用用的)
Severity(级别)- 从高到低:
0 emerg 系统不可用
1 alert 必须立即处理
2 crit 严重错误
3 err 一般错误
4 warning 警告
5 notice 正常但值得注意
6 info 信息
7 debug 调试信息
生产环境建议:应用日志用local0-local7,不同应用分配不同的facility,方便分类存储。我们团队的分配方案是local0给nginx,local1给应用服务,local2给数据库相关,local3给监控脚本。
过滤规则配置:
rsyslog支持三种过滤语法,从简单到复杂:
# 1. 传统选择器语法(最常用)
# 格式:facility.severity action
# severity表示该级别及以上
# 所有设施的info及以上级别 → /var/log/messages(排除mail/authpriv/cron)
*.info;mail.none;authpriv.none;cron.none /var/log/messages
# 认证相关日志
authpriv.* /var/log/secure
# 邮件日志
mail.* -/var/log/maillog
# cron日志
cron.* /var/log/cron
# 紧急消息发给所有登录用户
*.emerg :omusrmsg:*
# local0的所有日志(给nginx用)
local0.* /var/log/nginx/syslog.log
# 2. 基于属性的过滤(RainerScript)
# 按程序名过滤
:programname, isequal, "sshd" /var/log/sshd.log
# 按消息内容过滤
:msg, contains, "error" /var/log/error-all.log
# 按来源IP过滤(远程日志场景)
:fromhost-ip, startswith, "192.168.1." /var/log/remote/lan.log
# 3. 基于表达式的过滤(最灵活)
if$programname == 'nginx' and $syslogseverity <= 3 then {
action(type="omfile" file="/var/log/nginx/error.log")
stop
}
文件名前面加-表示异步写入,不是每条日志都fsync,性能好但断电可能丢几条。生产环境对性能敏感的日志(如access log)建议加-,安全审计日志不要加。
模板定义:
# 自定义日志格式模板
# 带毫秒时间戳的格式
template(name="precise"type="string"
string="%timegenerated:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n")
# JSON格式(方便后续接入ELK)
template(name="json-syslog"type="list") {
constant(value="{")
constant(value="\"@timestamp\":\"") property(name="timegenerated" dateFormat="rfc3339")
constant(value="\",\"host\":\"") property(name="hostname")
constant(value="\",\"severity\":\"") property(name="syslogseverity-text")
constant(value="\",\"facility\":\"") property(name="syslogfacility-text")
constant(value="\",\"program\":\"") property(name="programname")
constant(value="\",\"pid\":\"") property(name="procid")
constant(value="\",\"message\":\"") property(name="msg" format="jsonf")
constant(value="\"}\n")
}
# 远程日志按主机名分目录存储
template(name="RemoteHost"type="string"
string="/var/log/remote/%HOSTNAME%/%programname%.log")
# 应用模板到规则
*.info;mail.none;authpriv.none;cron.none action(type="omfile" file="/var/log/messages" template="precise")
远程日志转发:
# === 客户端配置(发送端)===
# TCP转发(推荐,比UDP可靠)
# @@表示TCP,@表示UDP
*.* @@192.168.1.100:514
# 带队列的TCP转发(防止网络中断时丢日志)
action(type="omfwd"
target="192.168.1.100"
port="514"
protocol="tcp"
action.resumeRetryCount="-1"
queue.type="LinkedList"
queue.filename="fwd_to_logserver"
queue.maxDiskSpace="1g"
queue.saveOnShutdown="on"
queue.size="50000"
queue.discardMark="48000"
queue.discardSeverity="7"
action.resumeInterval="10")
# RELP转发(最可靠,保证不丢日志)
module(load="omrelp")
action(type="omrelp"
target="192.168.1.100"
port="2514"
action.resumeRetryCount="-1"
queue.type="LinkedList"
queue.filename="relp_to_logserver"
queue.maxDiskSpace="2g"
queue.saveOnShutdown="on")
# === 服务端配置(接收端)===
# TCP接收
module(load="imtcp" MaxSessions="1000")
input(type="imtcp" port="514" ruleset="remote")
# RELP接收
module(load="imrelp")
input(type="imrelp" port="2514" ruleset="remote")
# 远程日志处理规则集
ruleset(name="remote") {
# 按主机名分目录存储
action(type="omfile" dynaFile="RemoteHost")
}
2.2.2 journald配置
journald的配置文件是/etc/systemd/journald.conf,改完需要重启journald生效。
# /etc/systemd/journald.conf
[Journal]
# 存储方式:persistent=持久化到/var/log/journal,volatile=仅内存,auto=有目录就持久化
# 生产环境必须设为persistent,不然重启后日志全丢
Storage=persistent
# 日志最大占用磁盘空间(持久化存储)
# 设太大会挤占业务空间,设太小查不到历史日志
# 实测500M-2G比较合理,根据机器日志量调整
SystemMaxUse=1G
# 单个日志文件最大大小
SystemMaxFileSize=100M
# 日志保留时间
# 生产环境建议30天,合规要求高的设更长
MaxRetentionSec=30day
# 内存中日志最大占用(volatile模式或持久化前的缓冲)
RuntimeMaxUse=200M
# 速率限制 - 防止某个服务疯狂刷日志拖垮系统
# 每30秒内最多记录10000条,超过的丢弃
RateLimitIntervalSec=30s
RateLimitBurst=10000
# 是否转发到syslog(rsyslog)
# 设为yes,journald收到的日志会转发给rsyslog
ForwardToSyslog=yes
# 是否转发到内核日志缓冲区
ForwardToKMsg=no
# 是否转发到控制台
ForwardToConsole=no
# 是否转发到wall
ForwardToWall=yes
# 压缩大于指定大小的日志条目
Compress=yes
# 是否给日志加密封印(需要FSS密钥)
Seal=no
改完配置后重启journald:
# 创建持久化存储目录(如果不存在)
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
# 重启journald
sudo systemctl restart systemd-journald
# 验证持久化是否生效
ls -la /var/log/journal/
journalctl --disk-usage
这里有个坑:Storage=persistent设置后,如果/var/log/journal/目录不存在,journald不会自动创建。必须手动创建目录或者执行systemd-tmpfiles --create。我们线上有台机器就因为这个问题,以为开了持久化实际上日志一直在内存里,重启后全丢了。
journalctl常用查询命令:
# 查看指定服务的日志
journalctl -u nginx.service
# 查看最近1小时的日志
journalctl --since "1 hour ago"
# 查看指定时间范围
journalctl --since "2024-01-15 10:00:00" --until "2024-01-15 12:00:00"
# 只看错误及以上级别
journalctl -p err
# 按PID查看
journalctl _PID=1234
# 按可执行文件路径查看
journalctl _EXE=/usr/sbin/sshd
# 实时跟踪(类似tail -f)
journalctl -f -u nginx.service
# 输出为JSON格式(方便脚本处理)
journalctl -u nginx.service -o json-pretty
# 查看内核日志
journalctl -k
# 查看本次启动的日志
journalctl -b 0
# 查看上次启动的日志(排查重启原因很有用)
journalctl -b -1
# 查看启动列表
journalctl --list-boots
2.2.3 rsyslog与journald协同配置
默认情况下CentOS 7/8的rsyslog通过imjournal模块从journald读取日志,这个协同关系需要理解清楚:
应用/服务 → journald(二进制存储)→ imjournal模块 → rsyslog → 文本日志文件
→ 远程转发
确认协同配置正常:
# 检查rsyslog是否加载了imjournal模块
grep -n "imjournal" /etc/rsyslog.conf
# 正常应该看到类似这行:
# module(load="imjournal" StateFile="imjournal.state")
# 检查imjournal状态文件
ls -la /var/lib/rsyslog/imjournal.state
# 如果用的是imuxsock而不是imjournal(Ubuntu默认方式)
# journald通过/dev/log socket把日志传给rsyslog
grep -n "imuxsock" /etc/rsyslog.conf
两种协同模式的选择:
生产环境建议保持系统默认的协同模式,不要随意切换。如果遇到imjournal的状态文件损坏导致日志重复,删除/var/lib/rsyslog/imjournal.state后重启rsyslog即可。
2.2.4 logrotate日志轮转配置
logrotate是日志轮转的标准工具,由crond每天触发执行。主配置文件/etc/logrotate.conf,各应用的轮转规则放在/etc/logrotate.d/目录下。
logrotate全局配置:
# /etc/logrotate.conf
# 默认每周轮转
weekly
# 保留4份历史日志
rotate 4
# 轮转后创建新的空日志文件
create
# 使用日期作为轮转文件后缀(推荐,比数字后缀直观)
dateext
dateformat -%Y%m%d
# 压缩历史日志
compress
# 延迟一个周期再压缩(方便查看最近一次的轮转日志)
delaycompress
# 包含/etc/logrotate.d/目录下的配置
include /etc/logrotate.d
自定义应用日志轮转:
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
sharedscripts
postrotate
# 给nginx发信号重新打开日志文件
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}
# /etc/logrotate.d/app-service
/var/log/app/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
# copytruncate模式:复制当前日志后清空原文件
# 适用于不支持信号重载的应用(如某些Java应用)
copytruncate
# 单个日志文件超过200M就轮转,不等到周期
maxsize 200M
# 轮转后执行的脚本
postrotate
# 通知监控系统
logger -t logrotate "App log rotated"
endscript
}
copytruncate vs create模式的选择:
这个选择很关键,选错了会丢日志或者应用写不进日志:
生产环境建议:能用create+postrotate发信号的就用create,实在不行再用copytruncate。copytruncate在日志量大的时候(比如每秒几千行),丢失的那几行可能包含关键信息。
2.3 启动和验证
2.3.1 启动服务
# 启动rsyslog
sudo systemctl start rsyslog
sudo systemctl enable rsyslog
sudo systemctl status rsyslog
# journald一般随系统启动,确认状态
sudo systemctl status systemd-journald
# 验证rsyslog配置文件语法
sudo rsyslogd -N1
# 输出 "rsyslogd: End of config validation run" 表示配置正确
# 如果修改了配置,重启rsyslog
sudo systemctl restart rsyslog
# 查看rsyslog启动日志,确认没有报错
journalctl -u rsyslog --since "5 minutes ago" --no-pager
2.3.2 功能验证
# 1. 验证本地日志写入
logger -p local0.info "Test message from logger command"
# 检查是否写入了对应的日志文件
tail -1 /var/log/messages
# 2. 验证指定facility的日志路由
logger -p local0.err "Test local0 error message"
# 如果配置了local0.*的规则,检查对应文件
# 3. 验证journald记录
journalctl --since "1 minute ago" | grep "Test message"
# 4. 验证日志轮转配置
# 手动执行logrotate测试(debug模式,不实际执行)
sudo logrotate -d /etc/logrotate.d/nginx
# 手动强制执行一次轮转
sudo logrotate -f /etc/logrotate.d/nginx
# 检查轮转结果
ls -la /var/log/nginx/
# 5. 验证远程转发(如果配置了的话)
# 在发送端执行
logger -p local0.info "Remote test message $(date)"
# 在接收端检查
tail -1 /var/log/remote/$(hostname)/local0.log
# 6. 检查rsyslog内部统计
sudo rsyslogd -N1 2>&1 | head -20
三、示例代码和配置
3.1 完整配置示例
3.1.1 生产级rsyslog.conf完整配置
这份配置在我们团队200+台CentOS 7/8服务器上跑了3年,经过多次迭代,覆盖了本地日志分类、远程转发、队列防丢失等场景。
# 文件路径:/etc/rsyslog.conf
# 生产环境rsyslog配置 - 适用于CentOS 7/8
# 最后修改:2024-01
#################
# 全局指令
#################
# 工作目录(队列文件、状态文件存放位置)
global(
workDirectory="/var/lib/rsyslog"
maxMessageSize="64k"
preserveFQDN="on"
)
# 默认文件权限
module(load="builtin:omfile"
fileOwner="root"
fileGroup="adm"
fileCreateMode="0640"
dirCreateMode="0755")
#################
# 模块加载
#################
# 本地日志socket
module(load="imuxsock"
SysSock.Use="on"
SysSock.RateLimit.Interval="5"
SysSock.RateLimit.Burst="2000")
# 从journald读取
module(load="imjournal"
StateFile="imjournal.state"
IgnorePreviousMessages="off")
# 内核日志
module(load="imklog")
# 标记消息,每10分钟写一次MARK
module(load="immark" interval="600")
# TCP接收模块(仅日志中心服务器开启,普通节点注释掉)
# module(load="imtcp" MaxSessions="1000")
# input(type="imtcp" port="514" ruleset="remote")
# RELP接收模块(仅日志中心服务器开启)
# module(load="imrelp")
# input(type="imrelp" port="2514" ruleset="remote")
#################
# 模板定义
#################
# 精确时间戳格式(带毫秒)
template(name="precise"type="string"
string="%timegenerated:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n")
# JSON格式(接入ELK用)
template(name="json-syslog"type="list") {
constant(value="{")
constant(value="\"@timestamp\":\"") property(name="timegenerated" dateFormat="rfc3339")
constant(value="\",\"host\":\"") property(name="hostname")
constant(value="\",\"severity\":\"") property(name="syslogseverity-text")
constant(value="\",\"facility\":\"") property(name="syslogfacility-text")
constant(value="\",\"program\":\"") property(name="programname")
constant(value="\",\"pid\":\"") property(name="procid")
constant(value="\",\"message\":\"") property(name="msg" format="jsonf")
constant(value="\"}\n")
}
# 远程日志按主机名+程序名分目录
template(name="RemoteHostLog"type="string"
string="/var/log/remote/%HOSTNAME%/%programname%.log")
# 远程日志按日期分目录
template(name="RemoteDateLog"type="string"
string="/var/log/remote/%HOSTNAME%/%$year%-%$month%-%$day%/%programname%.log")
#################
# 本地日志规则
#################
# 内核日志
kern.* /var/log/kern.log
# 认证日志(ssh登录、sudo操作等,安全审计必看)
authpriv.* /var/log/secure
# 邮件日志(异步写入,前面加-)
mail.* -/var/log/maillog
# cron日志
cron.* /var/log/cron
# 紧急消息广播给所有登录用户
*.emerg :omusrmsg:*
# 通用消息日志(排除已单独处理的facility)
*.info;mail.none;authpriv.none;cron.none action(type="omfile"
file="/var/log/messages"
template="precise")
# local0 - nginx日志(通过syslog输出的部分)
local0.* /var/log/nginx/syslog.log
# local1 - 应用服务日志
local1.* /var/log/app/service.log
# local2 - 数据库相关日志
local2.* /var/log/db/database.log
# local3 - 监控脚本日志
local3.* /var/log/monitor/scripts.log
# 按程序名单独记录sshd日志(方便安全审计)
:programname, isequal, "sshd" /var/log/sshd.log
# 记录所有error及以上级别到单独文件(快速定位问题)
*.err /var/log/error-all.log
#################
# 远程转发(客户端配置)
#################
# TCP转发到日志中心,带磁盘队列防丢失
action(type="omfwd"
target="log-center.internal.com"
port="514"
protocol="tcp"
template="json-syslog"
action.resumeRetryCount="-1"
action.resumeInterval="10"
queue.type="LinkedList"
queue.filename="fwd_to_logcenter"
queue.maxDiskSpace="1g"
queue.saveOnShutdown="on"
queue.size="50000"
queue.discardMark="48000"
queue.discardSeverity="7"
queue.checkpointInterval="100"
queue.timeoutEnqueue="0")
#################
# 远程日志接收规则集(仅日志中心服务器使用)
#################
# ruleset(name="remote") {
# action(type="omfile"
# dynaFile="RemoteHostLog"
# template="precise"
# queue.type="LinkedList"
# queue.filename="remote_write"
# queue.maxDiskSpace="2g"
# queue.saveOnShutdown="on")
# }
3.1.2 自定义应用日志收集的rsyslog配置
把应用日志通过rsyslog收集,比应用自己写文件更灵活,可以统一做转发、过滤、格式化。
# 文件路径:/etc/rsyslog.d/10-app-collect.conf
# 功能:收集指定目录下的应用日志文件,转发到日志中心
# 加载文件输入模块
module(load="imfile")
# 监控Java应用日志
input(type="imfile"
File="/opt/app/logs/application.log"
Tag="java-app:"
Severity="info"
Facility="local1"
PersistStateInterval="200"
readTimeout="10"
freshStartTail="on"
reopenOnTruncate="on")
# 监控Java应用错误日志
input(type="imfile"
File="/opt/app/logs/error.log"
Tag="java-app-error:"
Severity="err"
Facility="local1"
PersistStateInterval="100"
freshStartTail="on"
reopenOnTruncate="on")
# 监控多个应用日志(通配符)
input(type="imfile"
File="/opt/apps/*/logs/*.log"
Tag="multi-app:"
Severity="info"
Facility="local1"
freshStartTail="on"
reopenOnTruncate="on")
# 过滤和转发规则
if$syslogtag startswith 'java-app'then {
# 本地存一份
action(type="omfile"
file="/var/log/app/java-collected.log"
template="precise")
# 转发到日志中心
action(type="omfwd"
target="log-center.internal.com"
port="514"
protocol="tcp"
template="json-syslog")
stop
}
几个注意点:
freshStartTail="on" 表示首次监控时从文件末尾开始读,不读历史内容。生产环境建议开启,否则第一次启动会把整个日志文件重新发一遍。reopenOnTruncate="on" 配合logrotate的copytruncate模式使用,文件被截断后自动重新打开。PersistStateInterval 控制状态持久化频率,值越小越安全但IO开销越大,200是个平衡值。
3.1.3 logrotate完整配置脚本
#!/bin/bash
# 文件名:/usr/local/bin/setup-logrotate.sh
# 功能:一键部署标准化的logrotate配置
# 适用:CentOS 7/8, Ubuntu 20.04/22.04
set -euo pipefail
LOGROTATE_DIR="/etc/logrotate.d"
echo"=== 部署logrotate标准配置 ==="
# 备份现有配置
BACKUP_DIR="/etc/logrotate.d.bak.$(date +%Y%m%d%H%M%S)"
cp -r "$LOGROTATE_DIR""$BACKUP_DIR"
echo"已备份现有配置到 $BACKUP_DIR"
# 系统日志轮转配置
cat > "${LOGROTATE_DIR}/syslog" << 'EOF'
/var/log/messages
/var/log/secure
/var/log/maillog
/var/log/cron
/var/log/kern.log
/var/log/error-all.log
{
daily
rotate 30
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
sharedscripts
postrotate
/usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
endscript
}
EOF
echo"已配置系统日志轮转"
# Nginx日志轮转配置
cat > "${LOGROTATE_DIR}/nginx" << 'EOF'
/var/log/nginx/*.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}
EOF
echo"已配置Nginx日志轮转"
# 应用日志轮转配置(copytruncate模式)
cat > "${LOGROTATE_DIR}/app-logs" << 'EOF'
/var/log/app/*.log
/opt/app/logs/*.log
{
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
copytruncate
maxsize 500M
su root root
}
EOF
echo"已配置应用日志轮转"
# 远程日志轮转配置(日志中心服务器用)
cat > "${LOGROTATE_DIR}/remote-logs" << 'EOF'
/var/log/remote/*/*.log {
daily
rotate 60
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
sharedscripts
postrotate
/usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
endscript
}
EOF
echo"已配置远程日志轮转"
# 验证所有配置
echo""
echo"=== 验证配置 ==="
for conf in"${LOGROTATE_DIR}"/*; do
if logrotate -d "$conf" > /dev/null 2>&1; then
echo"[OK] $(basename $conf)"
else
echo"[FAIL] $(basename $conf) - 请检查配置"
logrotate -d "$conf" 2>&1 | tail -5
fi
done
echo""
echo"部署完成。可以用 logrotate -d /etc/logrotate.d/xxx 测试单个配置"
echo"强制执行轮转:logrotate -f /etc/logrotate.d/xxx"
3.2 实际应用案例
案例一:日志分析统计脚本
场景描述:运维日常需要快速统计日志中的错误频率、访问TOP IP、异常时间段等信息。手动grep效率太低,写个脚本自动化。
实现代码:
#!/bin/bash
# 文件名:/usr/local/bin/log-analyzer.sh
# 功能:日志分析统计工具
# 用法:log-analyzer.sh <日志文件> [分析类型]
# 分析类型:error-stat | top-ip | time-dist | all
set -euo pipefail
LOG_FILE="${1:?用法: $0 <日志文件> [error-stat|top-ip|time-dist|all]}"
ANALYSIS="${2:-all}"
if [ ! -f "$LOG_FILE" ]; then
echo"错误:文件 $LOG_FILE 不存在"
exit 1
fi
TOTAL_LINES=$(wc -l < "$LOG_FILE")
echo"=========================================="
echo"日志分析报告"
echo"文件:$LOG_FILE"
echo"总行数:$TOTAL_LINES"
echo"文件大小:$(du -h "$LOG_FILE" | awk '{print $1}')"
echo"时间范围:$(head -1 "$LOG_FILE" | awk '{print $1, $2}') ~ $(tail -1 "$LOG_FILE" | awk '{print $1, $2}')"
echo"=========================================="
# 错误统计
error_stat() {
echo""
echo"--- 错误级别统计 ---"
echo"EMERG : $(grep -ci 'emerg' "$LOG_FILE" 2>/dev/null || echo 0)"
echo"ALERT : $(grep -ci 'alert' "$LOG_FILE" 2>/dev/null || echo 0)"
echo"CRIT : $(grep -ci 'crit' "$LOG_FILE" 2>/dev/null || echo 0)"
echo"ERROR : $(grep -ci 'error\|err\b' "$LOG_FILE" 2>/dev/null || echo 0)"
echo"WARNING: $(grep -ci 'warn' "$LOG_FILE" 2>/dev/null || echo 0)"
echo""
echo"--- 最近20条错误 ---"
grep -i 'error\|crit\|alert\|emerg'"$LOG_FILE" | tail -20
echo""
echo"--- 错误关键词TOP10 ---"
grep -ioP '(?:error|fail|refused|denied|timeout|unreachable)\S*'"$LOG_FILE" \
| sort | uniq -c | sort -rn | head -10
}
# TOP IP统计(适用于包含IP的日志,如auth.log、access.log)
top_ip() {
echo""
echo"--- 访问IP TOP20 ---"
grep -oP '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'"$LOG_FILE" \
| sort | uniq -c | sort -rn | head -20
echo""
echo"--- 失败登录IP TOP10 ---"
grep -i 'failed\|failure\|invalid'"$LOG_FILE" \
| grep -oP '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' \
| sort | uniq -c | sort -rn | head -10
}
# 时间分布统计
time_dist() {
echo""
echo"--- 每小时日志量分布 ---"
awk '{print $3}'"$LOG_FILE" \
| grep -oP '^\d{2}' \
| sort | uniq -c | sort -k2 \
| awk '{printf "%s:00 %6d ", $2, $1; for(i=0;i<$1/100;i++) printf "#"; print ""}'
}
case"$ANALYSIS"in
error-stat) error_stat ;;
top-ip) top_ip ;;
time-dist) time_dist ;;
all) error_stat; top_ip; time_dist ;;
*) echo"未知分析类型:$ANALYSIS"; exit 1 ;;
esac
echo""
echo"=========================================="
echo"分析完成:$(date '+%Y-%m-%d %H:%M:%S')"
echo"=========================================="
运行结果:
==========================================
日志分析报告
文件:/var/log/secure
总行数:15823
文件大小:1.8M
时间范围:Jan 15 00:01:02 ~ Jan 15 23:59:58
==========================================
--- 错误级别统计 ---
EMERG : 0
ALERT : 0
CRIT : 2
ERROR : 47
WARNING: 12
--- 访问IP TOP20 ---
3842 192.168.1.50
1205 10.0.0.15
892 172.16.0.100
...
--- 每小时日志量分布 ---
00:00 342 ###
01:00 128 #
02:00 95
03:00 87
...
10:00 2105 #####################
11:00 1893 ##################
案例二:日志脱敏处理
场景描述:日志中可能包含手机号、身份证号、银行卡号等敏感信息。合规要求这些信息在存储和转发前必须脱敏。通过rsyslog的模板和属性替换功能实现实时脱敏。
实现步骤:
rsyslog脱敏配置:
# 文件路径:/etc/rsyslog.d/20-desensitize.conf
# 功能:日志脱敏 - 手机号、身份证号、银行卡号
# 加载正则替换模块
module(load="mmexternal")
# 方案一:使用mmnormalize + 自定义脚本(推荐,灵活性高)
# 需要配合外部脚本实现
# 方案二:使用模板中的正则替换(rsyslog 8.32+支持)
# 手机号脱敏:13812345678 → 138****5678
template(name="desensitized"type="string"
string="%timegenerated:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::regex-replace(/(1[3-9][0-9])[0-9]{4}([0-9]{4})/\\1****\\2):::drop-last-lf%\n")
# 应用脱敏模板到指定日志
if$syslogfacility-text == 'local1'then {
action(type="omfile"
file="/var/log/app/service-desensitized.log"
template="desensitized")
}
外部脱敏脚本(更灵活的方案):
#!/bin/bash
# 文件名:/usr/local/bin/log-desensitize.sh
# 功能:日志脱敏处理脚本
# 用法:可作为rsyslog的omprog外部程序,也可独立使用
# 独立用法:cat input.log | log-desensitize.sh > output.log
while IFS= read -r line; do
# 手机号脱敏:13812345678 → 138****5678
line=$(echo"$line" | sed -E 's/(1[3-9][0-9])[0-9]{4}([0-9]{4})/\1****\2/g')
# 身份证号脱敏:110101199001011234 → 110101****1234
line=$(echo"$line" | sed -E 's/([0-9]{6})[0-9]{8}([0-9]{4})/\1********\2/g')
# 银行卡号脱敏:6222021234567890123 → 6222****0123
line=$(echo"$line" | sed -E 's/([0-9]{4})[0-9]{11,15}([0-9]{4})/\1****\2/g')
# 邮箱脱敏:user@example.com → u***@example.com
line=$(echo"$line" | sed -E 's/([a-zA-Z0-9])[a-zA-Z0-9.]*(@[a-zA-Z0-9.-]+)/\1***\2/g')
echo"$line"
done
rsyslog配合omprog使用脱敏脚本:
# /etc/rsyslog.d/21-omprog-desensitize.conf
module(load="omprog")
# 通过外部脚本处理后写入文件
if$syslogfacility-text == 'local1'then {
action(type="omprog"
binary="/usr/local/bin/log-desensitize.sh"
output="/var/log/app/desensitized.log"
confirmMessages="off"
useTransactions="off")
}
案例三:集中日志架构快速搭建
场景描述:50台服务器需要把日志集中到一台日志中心服务器,用rsyslog原生方案,不上ELK(成本和复杂度都低很多)。
架构:
节点1 ──┐
节点2 ──┤ TCP/514
节点3 ──┼──────────→ 日志中心服务器 ──→ /var/log/remote/主机名/程序名.log
... ──┤ ──→ logrotate轮转
节点50 ──┘ ──→ 定期归档到NFS/对象存储
日志中心服务器配置:
# /etc/rsyslog.d/00-server.conf
# 加载TCP接收模块
module(load="imtcp" MaxSessions="1000")
input(type="imtcp" port="514" ruleset="remote")
# 按主机名分目录的模板
template(name="RemoteLog"type="string"
string="/var/log/remote/%HOSTNAME%/%programname%.log")
# 所有远程日志汇总文件
template(name="RemoteAll"type="string"
string="/var/log/remote/all-hosts.log")
# 精确时间戳模板
template(name="RemoteFmt"type="string"
string="%timegenerated:::date-rfc3339% [%HOSTNAME%] %syslogtag%%msg:::drop-last-lf%\n")
# 远程日志处理规则集
ruleset(name="remote") {
# 按主机名分目录存储
action(type="omfile"
dynaFile="RemoteLog"
template="RemoteFmt"
dynaFileCacheSize="100"
ioBufferSize="64k"
flushOnTXEnd="off"
asyncWriting="on"
flushInterval="1"
queue.type="LinkedList"
queue.filename="remote_disk_q"
queue.maxDiskSpace="2g"
queue.saveOnShutdown="on"
queue.size="100000")
# 同时写一份汇总日志(方便全局搜索)
action(type="omfile"
file="/var/log/remote/all-hosts.log"
template="RemoteFmt"
asyncWriting="on"
flushInterval="2")
}
客户端一键部署脚本:
#!/bin/bash
# 文件名:deploy-log-client.sh
# 功能:在客户端节点部署rsyslog远程转发配置
# 用法:deploy-log-client.sh <日志中心IP>
LOG_SERVER="${1:?用法: $0 <日志中心服务器IP>}"
cat > /etc/rsyslog.d/99-forward.conf << EOF
# 转发所有日志到日志中心
action(type="omfwd"
target="${LOG_SERVER}"
port="514"
protocol="tcp"
template="RSYSLOG_SyslogProtocol23Format"
action.resumeRetryCount="-1"
action.resumeInterval="10"
queue.type="LinkedList"
queue.filename="fwd_to_center"
queue.maxDiskSpace="500m"
queue.saveOnShutdown="on"
queue.size="10000")
EOF
# 验证配置
rsyslogd -N1
if [ $? -eq 0 ]; then
systemctl restart rsyslog
echo"配置成功,已开始转发日志到 ${LOG_SERVER}:514"
# 发一条测试日志
logger -t deploy-test "Log forwarding configured to ${LOG_SERVER}"
else
echo"配置有误,请检查"
rm -f /etc/rsyslog.d/99-forward.conf
exit 1
fi
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 性能优化
rsyslog异步写入和缓冲配置:默认rsyslog每条日志都同步写磁盘,高并发场景下IO压力很大。实测在每秒5000条日志的场景下,开启异步写入后rsyslog的CPU占用从12%降到3%,磁盘IOPS从4800降到600。
# /etc/rsyslog.d/01-performance.conf
# 主消息队列优化
main_queue(
queue.type="LinkedList"
queue.filename="main_q"
queue.maxDiskSpace="1g"
queue.saveOnShutdown="on"
queue.size="100000"
queue.discardMark="90000"
queue.discardSeverity="7"
queue.workerThreads="4"
queue.dequeueBatchSize="1000"
queue.timeoutEnqueue="0"
)
# 文件输出异步写入
# 在action中添加以下参数:
# asyncWriting="on" 开启异步
# flushOnTXEnd="off" 不在每个事务结束时flush
# flushInterval="1" 每1秒flush一次
# ioBufferSize="64k" IO缓冲区64KB
journald速率限制调优:默认的速率限制(每30秒1万条)对大多数场景够用,但如果应用在异常时会短时间爆发大量日志,需要适当调高。我们有个Java服务在GC风暴时30秒内能产生5万条日志,默认限制下4万条被丢弃了,排查问题时关键信息全没了。
# /etc/systemd/journald.conf
[Journal]
RateLimitIntervalSec=30s
RateLimitBurst=50000
改完执行 systemctl restart systemd-journald 生效。这个参数改大了会增加磁盘IO,根据实际日志量调整,别一上来就设成0(关闭限制)。
logrotate使用dateext避免文件名冲突:默认的数字后缀(.1 .2 .3)在手动执行logrotate时容易出现文件名冲突。用dateext后缀格式为messages-20240115,直观且不会冲突。
# 在logrotate配置中添加
dateext
dateformat -%Y%m%d
# 如果同一天可能轮转多次(配合maxsize使用),加上时间
dateformat -%Y%m%d-%H%M%S
4.1.2 安全加固
限制日志文件权限:日志文件中可能包含敏感信息(密码、token、IP地址),权限不能太松。
# rsyslog创建文件的默认权限
# 在rsyslog.conf的全局配置中设置
module(load="builtin:omfile"
fileOwner="root"
fileGroup="adm"
fileCreateMode="0640"
dirCreateMode="0750")
# 修复现有日志文件权限
chmod 640 /var/log/messages /var/log/secure /var/log/cron
chown root:adm /var/log/messages /var/log/secure /var/log/cron
# 敏感日志(如secure)可以设置更严格的权限
chmod 600 /var/log/secure
rsyslog远程传输加密:日志通过网络传输时,明文TCP/UDP容易被嗅探。生产环境如果日志中有敏感信息,必须用TLS加密。
# 客户端TLS转发配置
global(
defaultNetstreamDriverCAFile="/etc/pki/rsyslog/ca.pem"
defaultNetstreamDriverCertFile="/etc/pki/rsyslog/client-cert.pem"
defaultNetstreamDriverKeyFile="/etc/pki/rsyslog/client-key.pem"
)
action(type="omfwd"
target="log-center.internal.com"
port="6514"
protocol="tcp"
streamDriver="gtls"
streamDriverMode="1"
streamDriverAuthMode="x509/name"
streamDriverPermittedPeers="log-center.internal.com")
防止日志被篡改:对于安全审计场景,本地日志可能被入侵者删除或修改。建议同时转发到远程日志服务器,并且远程服务器上的日志设为只追加属性。
# 在日志服务器上,给日志文件设置只追加属性
chattr +a /var/log/remote/*/secure.log
# 查看属性
lsattr /var/log/remote/*/secure.log
# 注意:设了+a后logrotate无法正常轮转该文件
# 需要在logrotate的prerotate中先去掉属性
prerotate
chattr -a /var/log/remote/*/secure.log 2>/dev/null || true
endscript
postrotate
chattr +a /var/log/remote/*/secure.log 2>/dev/null || true
endscript
4.1.3 高可用配置
rsyslog双中心转发:日志中心服务器单点故障会导致日志丢失。配置两台日志中心,rsyslog同时转发到两台。
# 主日志中心
action(type="omfwd"
target="log-center-01.internal.com"
port="514"
protocol="tcp"
action.resumeRetryCount="-1"
queue.type="LinkedList"
queue.filename="fwd_primary"
queue.maxDiskSpace="1g"
queue.saveOnShutdown="on")
# 备日志中心
action(type="omfwd"
target="log-center-02.internal.com"
port="514"
protocol="tcp"
action.resumeRetryCount="-1"
queue.type="LinkedList"
queue.filename="fwd_backup"
queue.maxDiskSpace="1g"
queue.saveOnShutdown="on")
备份策略:日志中心服务器的日志定期归档到对象存储或NFS,保留策略按业务需求设定。我们的做法是热数据(7天内)放本地SSD,温数据(7-30天)放本地HDD,冷数据(30天以上)压缩后传到对象存储。
4.2 注意事项
4.2.1 配置注意事项
rsyslog配置文件的语法错误不会导致服务启动失败,但会导致部分规则不生效。改完配置一定要用 rsyslogd -N1 验证语法,不要直接restart碰运气。
- /var/log分区必须单独挂载或者设置磁盘配额。日志撑满根分区会导致整个系统不可用,这个事故我们经历过两次,每次都是半夜被叫起来处理。
- logrotate的postrotate脚本如果执行失败,不会回滚轮转操作。脚本里的命令要加错误处理,特别是kill信号那行,进程不存在时kill会报错。
- journald的
Storage=volatile模式下日志只在内存中,重启就没了。生产环境千万别用这个模式,除非你明确知道自己在做什么。
4.2.2 常见错误
| | |
|---|
| rsyslog启动后/var/log/messages不更新 | imjournal模块状态文件损坏,或SELinux阻止写入 | 删除/var/lib/rsyslog/imjournal.state后重启rsyslog;检查ausearch -m avc -ts recent |
| 使用create模式但postrotate没有通知应用重新打开文件 | 在postrotate中发送正确的信号(如nginx用USR1,rsyslog用HUP) |
| | 改用TCP或RELP协议;增大queue.maxDiskSpace |
| Storage未设为persistent,或日志被MaxRetentionSec清理 | 检查journald.conf的Storage设置;调整保留时间 |
| | 优化过滤规则,把高频匹配的规则放前面;开启异步写入 |
| logrotate报错"error: skipping because parent directory has insecure permissions" | | chmod 755 /var/log/xxx 确保目录权限正确;或在logrotate配置中加su root root |
4.2.3 兼容性问题
CentOS 7 vs CentOS 8/9的rsyslog差异:CentOS 7自带rsyslog 8.24,不支持一些新语法(如mmexternal模块的部分参数)。CentOS 8自带rsyslog 8.2102,功能更完整。如果需要在CentOS 7上用新功能,可以从rsyslog官方仓库安装新版本:
# CentOS 7安装rsyslog 8.2102+
curl -o /etc/yum.repos.d/rsyslog.repo \
https://www.rsyslog.com/repos/v8-stable/rsyslog-v8-stable-centos7.repo
yum install -y rsyslog
Ubuntu和CentOS的日志路径差异:Ubuntu默认用/var/log/syslog而不是/var/log/messages,认证日志在/var/log/auth.log而不是/var/log/secure。写跨平台的监控脚本时要注意这个差异。
systemd版本差异:CentOS 7的systemd 219不支持journalctl的部分参数(如--output=short-iso-precise),CentOS 8的systemd 239支持。跨版本使用时注意检查参数兼容性。
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
# 查看rsyslog自身日志(rsyslog出问题时第一个看的地方)
journalctl -u rsyslog --since "30 minutes ago" --no-pager
# 查看rsyslog内部状态(需要先开启统计模块)
# 在rsyslog.conf中添加:
# module(load="impstats" interval="60" severity="7" log.syslog="off" log.file="/var/log/rsyslog-stats.log")
tail -20 /var/log/rsyslog-stats.log
# 查看系统日志
tail -f /var/log/messages
# 查看认证日志(排查SSH登录问题)
tail -f /var/log/secure # CentOS/RHEL
tail -f /var/log/auth.log # Ubuntu/Debian
# 查看cron日志
tail -f /var/log/cron
# journalctl实时跟踪多个服务
journalctl -f -u rsyslog -u nginx -u sshd
# 查看指定优先级的日志
journalctl -p err --since "1 hour ago"
# 查看上次系统启动前的日志(排查重启原因)
journalctl -b -1 -p warning --no-pager
5.1.2 常见问题排查
问题一:rsyslog不写日志,/var/log/messages不更新
这是最常见的问题,排查思路按优先级:
# 第1步:确认rsyslog进程在运行
systemctl status rsyslog
ps aux | grep rsyslog
# 第2步:检查配置文件语法
rsyslogd -N1
# 如果报错,根据错误信息修复配置
# 第3步:检查SELinux是否阻止
getenforce
# 如果是Enforcing,查看是否有拒绝记录
ausearch -m avc -ts recent | grep rsyslog
# 临时关闭SELinux测试(确认是SELinux问题后再写规则放行)
setenforce 0
# 第4步:检查imjournal状态文件
ls -la /var/lib/rsyslog/imjournal.state
# 如果文件异常大或损坏,删除后重启
rm -f /var/lib/rsyslog/imjournal.state
systemctl restart rsyslog
# 第5步:检查磁盘空间
df -h /var/log/
# 如果磁盘满了,rsyslog写不进去但不会报错
# 第6步:检查文件权限
ls -la /var/log/messages
# 确认rsyslog进程用户(通常是root)有写权限
# 第7步:手动发一条测试日志
logger "rsyslog test $(date)"
tail -1 /var/log/messages
# 如果这条能写进去,说明rsyslog本身没问题,是日志源的问题
解决方案:
- 90%的情况是imjournal状态文件损坏,删除重启即可
- SELinux问题用
audit2allow生成放行规则
问题二:journald日志丢失,journalctl查不到历史记录
# 第1步:检查存储模式
grep -i storage /etc/systemd/journald.conf
# 如果是volatile或auto(且/var/log/journal不存在),重启后日志就没了
# 第2步:检查持久化目录
ls -la /var/log/journal/
# 如果目录不存在,创建并重启journald
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald
# 第3步:检查日志保留策略
grep -i "MaxRetention\|SystemMaxUse\|SystemMaxFileSize" /etc/systemd/journald.conf
# SystemMaxUse设太小会导致旧日志被快速清理
# 第4步:检查速率限制
grep -i "RateLimit" /etc/systemd/journald.conf
# 如果RateLimitBurst太小,高并发时日志会被丢弃
# 查看是否有被丢弃的记录
journalctl | grep "Suppressed"
# 第5步:检查磁盘空间和inode
df -h /var/log/journal/
df -i /var/log/journal/
解决方案:
- 设置
Storage=persistent并创建/var/log/journal/目录 RateLimitBurst根据实际日志量调整,别设太小
问题三:日志文件暴涨撑满磁盘
这个问题一旦发生就是P0级事故,根分区满了系统直接不可用。
# 紧急处理:快速定位大文件
du -sh /var/log/* | sort -rh | head -10
# 如果是某个日志文件特别大,先truncate释放空间
# 注意:不要用rm删除正在被写入的文件!rm后空间不会释放(进程还持有文件描述符)
# 正确做法是truncate
> /var/log/xxx.log
# 或者
truncate -s 0 /var/log/xxx.log
# 如果已经rm了文件但空间没释放,找到持有文件描述符的进程
lsof | grep deleted | grep "/var/log"
# 重启对应进程释放文件描述符
# 查看哪个进程在疯狂写日志
iotop -o -P
# 或者
lsof /var/log/xxx.log
# 紧急清理journald日志释放空间
journalctl --vacuum-size=200M
journalctl --vacuum-time=3d
# 清理已轮转的压缩日志
find /var/log -name "*.gz" -mtime +7 -delete
find /var/log -name "*.[0-9]" -mtime +7 -delete
根因排查和预防:
- 检查logrotate是否正常执行:
cat /var/lib/logrotate/logrotate.status - 检查是否有应用在debug模式运行(debug日志量是info的10-50倍)
- 设置磁盘监控告警,/var/log使用率超过70%就告警
- 给/var/log单独挂载分区,就算撑满也不影响根分区
问题四:logrotate不生效
# 第1步:确认logrotate定时任务存在
# CentOS 7/8
cat /etc/cron.daily/logrotate
# 或者检查systemd timer(CentOS 8+)
systemctl status logrotate.timer
systemctl list-timers | grep logrotate
# 第2步:手动执行测试
logrotate -d /etc/logrotate.d/nginx
# -d是debug模式,只显示会做什么,不实际执行
# 看输出中是否有error
# 第3步:强制执行一次
logrotate -f /etc/logrotate.d/nginx
# 检查是否生成了轮转文件
ls -la /var/log/nginx/
# 第4步:检查状态文件
cat /var/lib/logrotate/logrotate.status
# 看对应日志文件的最后轮转时间
# 如果时间不对,可以手动编辑这个文件
# 第5步:常见原因
# a. 日志文件路径写错了(通配符不匹配)
# b. notifempty + 空日志文件 = 不轮转
# c. 文件权限问题(su指令不对)
# d. dateext冲突(同一天已经轮转过,文件名重复)
5.1.3 调试模式
# rsyslog调试模式启动(前台运行,输出详细信息)
# 先停掉正在运行的rsyslog
systemctl stop rsyslog
# 以调试模式启动
rsyslogd -dn 2>&1 | tee /tmp/rsyslog-debug.log
# 在另一个终端发测试日志
logger -p local0.info "debug test message"
# 查看调试输出中的处理流程
# 调试完成后Ctrl+C停止,重新启动正常模式
systemctl start rsyslog
# journald调试
# 查看journald的内部状态
journalctl --header
# 显示日志文件的详细信息(大小、条目数等)
# 验证journald配置
systemd-analyze cat-config systemd/journald.conf
# 显示最终生效的配置(合并了所有配置片段)
# logrotate调试
logrotate -v /etc/logrotate.conf
# -v 显示详细过程
logrotate -dv /etc/logrotate.d/nginx
# -d + -v 最详细的调试输出
5.2 性能监控
5.2.1 关键指标监控
# rsyslog进程资源占用
top -p $(pgrep rsyslogd) -bn1
# rsyslog内存详情
ps aux | grep rsyslogd | grep -v grep
# rsyslog打开的文件描述符数量(太多说明动态文件缓存太大)
ls -la /proc/$(pgrep rsyslogd)/fd | wc -l
# journald磁盘占用
journalctl --disk-usage
# /var/log分区使用率
df -h /var/log/
# /var/log目录各子目录大小
du -sh /var/log/* 2>/dev/null | sort -rh | head -15
# 日志写入速率(每秒新增行数,观察10秒)
for i in $(seq 1 10); do
wc -l /var/log/messages
sleep 1
done
# rsyslog队列积压情况(需要开启impstats模块)
grep "queue" /var/log/rsyslog-stats.log | tail -5
# 检查rsyslog是否有丢弃日志
grep -i "discarded" /var/log/rsyslog-stats.log | tail -5
5.2.2 监控指标说明
5.2.3 Prometheus监控规则
# prometheus-rules/rsyslog-alerts.yml
# rsyslog和日志系统监控告警规则
# 需要配合node_exporter和自定义exporter使用
groups:
-name:log_system_alerts
interval:60s
rules:
# /var/log分区使用率告警
-alert:VarLogDiskUsageHigh
expr:|
(1 - node_filesystem_avail_bytes{mountpoint="/var/log"}
/ node_filesystem_size_bytes{mountpoint="/var/log"}) * 100 > 80
for:5m
labels:
severity:warning
annotations:
summary:"/var/log分区使用率超过80%"
description:"主机 {{ $labels.instance }} 的/var/log分区使用率为 {{ $value | printf \"%.1f\" }}%"
-alert:VarLogDiskUsageCritical
expr:|
(1 - node_filesystem_avail_bytes{mountpoint="/var/log"}
/ node_filesystem_size_bytes{mountpoint="/var/log"}) * 100 > 90
for:2m
labels:
severity:critical
annotations:
summary:"/var/log分区使用率超过90%,需要立即处理"
description:"主机 {{ $labels.instance }} 的/var/log分区使用率为 {{ $value | printf \"%.1f\" }}%,有撑满风险"
# 根分区使用率告警(日志可能在根分区)
-alert:RootDiskUsageHigh
expr:|
(1 - node_filesystem_avail_bytes{mountpoint="/"}
/ node_filesystem_size_bytes{mountpoint="/"}) * 100 > 85
for:5m
labels:
severity:warning
annotations:
summary:"根分区使用率超过85%"
description:"主机 {{ $labels.instance }} 根分区使用率 {{ $value | printf \"%.1f\" }}%,检查是否日志暴涨"
# rsyslog进程存活检查
-alert:RsyslogDown
expr:node_systemd_unit_state{name="rsyslog.service",state="active"}!=1
for:1m
labels:
severity:critical
annotations:
summary:"rsyslog服务未运行"
description:"主机 {{ $labels.instance }} 的rsyslog服务停止,日志可能丢失"
# rsyslog CPU使用率异常
-alert:RsyslogHighCPU
expr:|
rate(namedprocess_namegroup_cpu_seconds_total{groupname="rsyslogd"}[5m]) * 100 > 15
for:10m
labels:
severity:warning
annotations:
summary:"rsyslog CPU使用率异常"
description:"主机 {{ $labels.instance }} rsyslog CPU使用率持续超过15%"
自定义日志监控脚本(配合Prometheus textfile collector):
#!/bin/bash
# 文件名:/usr/local/bin/log-metrics.sh
# 功能:采集日志系统指标,输出Prometheus格式
# 配合node_exporter的textfile collector使用
# crontab: * * * * * /usr/local/bin/log-metrics.sh
METRICS_FILE="/var/lib/node_exporter/textfile_collector/log_metrics.prom"
TEMP_FILE="${METRICS_FILE}.tmp"
{
# /var/log目录总大小(字节)
VAR_LOG_SIZE=$(du -sb /var/log/ 2>/dev/null | awk '{print $1}')
echo"log_var_log_size_bytes ${VAR_LOG_SIZE:-0}"
# journald占用空间(字节)
JOURNAL_SIZE=$(journalctl --disk-usage 2>/dev/null | grep -oP '\d+\.\d+[MGK]' | head -1)
# 转换为字节
ifecho"$JOURNAL_SIZE" | grep -q 'G'; then
JOURNAL_BYTES=$(echo"$JOURNAL_SIZE" | sed 's/G//' | awk '{printf "%.0f", $1*1024*1024*1024}')
elifecho"$JOURNAL_SIZE" | grep -q 'M'; then
JOURNAL_BYTES=$(echo"$JOURNAL_SIZE" | sed 's/M//' | awk '{printf "%.0f", $1*1024*1024}')
else
JOURNAL_BYTES=0
fi
echo"log_journald_size_bytes ${JOURNAL_BYTES}"
# rsyslog进程状态(1=运行,0=停止)
if systemctl is-active rsyslog >/dev/null 2>&1; then
echo"log_rsyslog_running 1"
else
echo"log_rsyslog_running 0"
fi
# 最近1分钟的错误日志数量
ERROR_COUNT=$(journalctl --since "1 minute ago" -p err --no-pager 2>/dev/null | wc -l)
echo"log_error_count_1m ${ERROR_COUNT}"
# 各主要日志文件大小
for logfile in messages secure cron maillog; do
if [ -f "/var/log/${logfile}" ]; then
SIZE=$(stat -c%s "/var/log/${logfile}" 2>/dev/null || echo 0)
echo"log_file_size_bytes{file=\"${logfile}\"} ${SIZE}"
fi
done
} > "$TEMP_FILE"
mv "$TEMP_FILE""$METRICS_FILE"
5.3 备份与恢复
5.3.1 备份策略
#!/bin/bash
# 文件名:/usr/local/bin/log-backup.sh
# 功能:日志备份脚本 - 压缩归档到指定目录或远程存储
# crontab: 0 2 * * * /usr/local/bin/log-backup.sh
set -euo pipefail
# 配置
BACKUP_DIR="/data/log-backup"
REMOTE_DIR="rsync://backup-server/log-archive"
RETENTION_DAYS=90
DATE=$(date +%Y%m%d)
HOSTNAME=$(hostname -s)
# 创建备份目录
mkdir -p "${BACKUP_DIR}/${DATE}"
echo"[$(date)] 开始日志备份..."
# 备份rsyslog文本日志(只备份已轮转的压缩文件)
echo"备份rsyslog日志..."
find /var/log -maxdepth 1 -name "*.gz" -newer "${BACKUP_DIR}/.last_backup" 2>/dev/null \
| xargs -I{} cp {} "${BACKUP_DIR}/${DATE}/" 2>/dev/null || true
# 备份远程日志(日志中心服务器)
if [ -d /var/log/remote ]; then
echo"备份远程日志..."
find /var/log/remote -name "*.gz" -newer "${BACKUP_DIR}/.last_backup" 2>/dev/null \
| whileread f; do
REL_PATH=$(dirname "$f" | sed 's|/var/log/remote/||')
mkdir -p "${BACKUP_DIR}/${DATE}/remote/${REL_PATH}"
cp "$f""${BACKUP_DIR}/${DATE}/remote/${REL_PATH}/"
done
fi
# 导出journald日志(最近24小时)
echo"导出journald日志..."
journalctl --since "24 hours ago" --no-pager \
> "${BACKUP_DIR}/${DATE}/journald-${HOSTNAME}-${DATE}.log" 2>/dev/null
gzip "${BACKUP_DIR}/${DATE}/journald-${HOSTNAME}-${DATE}.log"
# 备份配置文件
echo"备份日志配置..."
tar czf "${BACKUP_DIR}/${DATE}/log-config-${DATE}.tar.gz" \
/etc/rsyslog.conf \
/etc/rsyslog.d/ \
/etc/systemd/journald.conf \
/etc/logrotate.conf \
/etc/logrotate.d/ \
2>/dev/null
# 同步到远程存储(可选)
# rsync -az "${BACKUP_DIR}/${DATE}/" "${REMOTE_DIR}/${HOSTNAME}/${DATE}/"
# 清理过期备份
echo"清理${RETENTION_DAYS}天前的备份..."
find "${BACKUP_DIR}" -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -exec rm -rf {} \;
# 更新最后备份时间戳
touch "${BACKUP_DIR}/.last_backup"
# 统计备份大小
BACKUP_SIZE=$(du -sh "${BACKUP_DIR}/${DATE}" | awk '{print $1}')
echo"[$(date)] 备份完成,本次备份大小:${BACKUP_SIZE}"
echo"备份路径:${BACKUP_DIR}/${DATE}/"
5.3.2 恢复流程
停止相关服务:
sudo systemctl stop rsyslog
恢复配置文件:
# 解压配置备份
tar xzf /data/log-backup/20240115/log-config-20240115.tar.gz -C /
# 验证配置
rsyslogd -N1
恢复日志文件(如果需要):
# 解压日志到原位置
cp /data/log-backup/20240115/*.gz /var/log/
gunzip /var/log/*.gz
重启服务并验证:
sudo systemctl start rsyslog
sudo systemctl status rsyslog
# 发测试日志验证
logger "Recovery test $(date)"
tail -1 /var/log/messages
六、总结
6.1 技术要点回顾
- rsyslog是Linux日志体系的核心:基于facility/severity的分类机制虽然古老但实用,配合模板和过滤规则可以精确控制每条日志的去向。生产环境必须掌握的配置:模块加载、过滤规则、模板定义、远程转发、队列防丢失。
- journald提供结构化查询能力:二进制存储+索引让日志查询效率比grep高一个数量级。
journalctl -u xxx -p err --since "1 hour ago" 这种组合查询在排障时非常高效。生产环境必须设置Storage=persistent并创建持久化目录。 - logrotate是日志轮转的标准方案:create和copytruncate两种模式的选择直接影响日志完整性。能用create+postrotate信号的场景不要用copytruncate。dateext比数字后缀更直观,推荐默认开启。
- 三者协同才是完整方案:journald负责收集systemd服务日志并提供查询接口,rsyslog负责分类存储和远程转发,logrotate负责轮转和清理。单独用任何一个都有短板。
- 磁盘空间管理是日志运维的生命线:/var/log分区单独挂载、logrotate正常运行、journald空间限制、磁盘监控告警,这四个环节缺一个都可能导致磁盘撑满的事故。
- 远程转发用TCP+队列,安全场景用TLS+RELP:UDP丢日志的概率在网络抖动时很高,生产环境不建议用。RELP协议保证不丢日志但性能略低,安全审计场景推荐。
6.2 进阶学习方向
ELK/EFK集中日志平台:rsyslog原生方案适合中等规模,超过500台机器或者需要全文检索、可视化分析时,需要上Elasticsearch+Logstash/Fluentd+Kibana。rsyslog可以作为日志采集端,通过omelasticsearch模块直接写入ES。
- 学习资源:Elastic官方文档 https://www.elastic.co/guide/
- 实践建议:先在测试环境搭一套单节点ELK,把rsyslog的日志接进去,体验全文检索和Kibana看板
日志标准化和可观测性:结构化日志(JSON格式)比纯文本日志更容易被机器处理。推动开发团队统一日志格式(时间戳、级别、traceID、服务名、消息),配合分布式追踪系统(Jaeger、Zipkin)实现全链路排障。
- 学习资源:OpenTelemetry项目 https://opentelemetry.io/
- 实践建议:从一个核心服务开始推JSON格式日志,验证采集和查询链路
日志安全与合规:金融、医疗、政务行业对日志有严格的合规要求(等保2.0、GDPR等)。需要掌握日志脱敏、防篡改、留存策略、审计追踪等技术。
- 学习资源:等保2.0日志审计要求、GDPR数据保护条例
- 实践建议:梳理现有日志中的敏感信息,制定脱敏规则并在rsyslog中实现
6.3 参考资料
- rsyslog官方文档 - 最权威的rsyslog配置参考,RainerScript语法详解
- systemd-journald.service手册 - journald配置参数完整说明
- logrotate手册 - logrotate所有配置指令说明
- rsyslog GitHub仓库 - 源码和issue,排查疑难问题时有用
附录
A. 命令速查表
# === rsyslog相关 ===
rsyslogd -N1 # 验证配置文件语法
rsyslogd -dn # 调试模式启动(前台)
systemctl restart rsyslog # 重启rsyslog
systemctl status rsyslog # 查看rsyslog状态
logger -p local0.info "test message"# 发送测试日志
logger -t myapp "tagged message"# 带标签的测试日志
# === journalctl相关 ===
journalctl -u nginx.service # 查看指定服务日志
journalctl -f # 实时跟踪日志
journalctl -p err # 只看错误及以上
journalctl --since "1 hour ago"# 最近1小时日志
journalctl --since "2024-01-15 10:00"# 指定时间起
journalctl -b -1 # 上次启动的日志
journalctl --disk-usage # 查看日志占用空间
journalctl --vacuum-size=500M # 清理日志到500M以内
journalctl --vacuum-time=7d # 清理7天前的日志
journalctl -o json-pretty -n 5 # JSON格式输出最近5条
journalctl --list-boots # 列出所有启动记录
journalctl _PID=1234 # 按PID查看
journalctl _EXE=/usr/sbin/sshd # 按可执行文件查看
# === logrotate相关 ===
logrotate -d /etc/logrotate.d/nginx # 调试模式(不实际执行)
logrotate -f /etc/logrotate.d/nginx # 强制执行轮转
logrotate -v /etc/logrotate.conf # 详细模式执行
cat /var/lib/logrotate/logrotate.status # 查看轮转状态
# === 日志文件操作 ===
tail -f /var/log/messages # 实时查看系统日志
tail -f /var/log/secure # 实时查看认证日志
> /var/log/xxx.log # 清空日志文件(不删除)
truncate -s 0 /var/log/xxx.log # 清空日志文件
du -sh /var/log/* # 查看各日志文件大小
lsof | grep deleted # 查找已删除但未释放的文件
B. 配置参数详解
rsyslog主要配置参数:
| | | |
|---|
workDirectory | | | |
maxMessageSize | | | |
preserveFQDN | | | |
queue.type | | | 队列类型:Direct/LinkedList/FixedArray/Disk |
queue.size | | | |
queue.maxDiskSpace | | | |
queue.saveOnShutdown | | | |
queue.workerThreads | | | |
queue.dequeueBatchSize | | | |
action.resumeRetryCount | | | |
action.resumeInterval | | | |
asyncWriting | | | |
flushInterval | | | |
ioBufferSize | | | |
dynaFileCacheSize | | | |
journald主要配置参数:
| | |
|---|
Storage | | 存储方式:persistent/volatile/auto/none |
Compress | | |
SystemMaxUse | | |
SystemMaxFileSize | | |
RuntimeMaxUse | | |
MaxRetentionSec | | |
MaxFileSec | | |
ForwardToSyslog | | |
RateLimitIntervalSec | | |
RateLimitBurst | | |
logrotate主要配置指令:
| |
|---|
daily/weekly/monthly | |
rotate N | |
compress | |
delaycompress | |
dateext | |
dateformat | |
maxsize SIZE | |
minsize SIZE | |
missingok | |
notifempty | |
create MODE OWNER GROUP | |
copytruncate | |
sharedscripts | |
postrotate/endscript | |
prerotate/endscript | |
su USER GROUP | |
C. 术语表
| | |
|---|
| | rsyslog中标识日志来源的分类,如kern、auth、cron、local0-7等,共24个 |
| | rsyslog中标识日志严重程度的级别,从emerg(0)到debug(7)共8级 |
| | 定期将当前日志文件归档并创建新文件,防止单个文件无限增长 |
| | journald将日志写入磁盘(/var/log/journal/),重启后不丢失 |
| | journald将日志仅保存在内存中(/run/log/journal/),重启后丢失 |
| | rsyslog中用于缓冲日志消息的机制,分内存队列和磁盘队列 |
| | rsyslog中一组过滤规则和动作的集合,可绑定到特定输入源 |
| | rsyslog根据日志属性(如主机名)动态生成输出文件路径 |
| Reliable Event Logging Protocol | |
| | |
| | 进程打开文件时获得的引用,日志轮转时需要注意fd的处理 |
| | 对日志中的敏感信息(手机号、身份证号等)进行掩码处理 |