在RHEL系统中,几乎所有的系统日志都由rsyslog负责接收、处理和落盘。很多人装完系统就默认使用它的配置,却很少注意到/etc/rsyslog.conf和/etc/rsyslog.d/目录下那些以template开头的语句到底做了什么。事实上,模板是rsyslog灵活性来源的核心:它决定了日志以什么格式写入磁盘、写到哪个目录、转发到远端时带上什么标签。掌握模板定制,等于掌握了日志系统的排版权和路由权。

一、理解rsyslog模板的两种基础语法
rsyslog的模板本质上是一个字符串生成器,它在日志消息到达后被调用,把消息中的各个字段拼接成最终输出。模板有两种写法,旧的list格式和新的string格式。string格式使用双百分号包裹属性名,例如%timegenerated%表示日志被本地接收的时间,%hostname%表示来源主机名,%msg%表示消息正文。一条最简单的模板如下:
# 传统list格式,兼容旧版本 $template myTpl,"%timegenerated% %hostname% %syslogtag% %msg%\n" # 新版本推荐使用template()对象语法 template(name="myTpl" type="string" string="%timegenerated% %hostname% %syslogtag% %msg%\n")
两种写法功能等价,但RHEL 7之后的版本更推荐使用template()对象语法,因为它结构清晰,且支持在属性名后追加选项。例如%timegenerated:::date-rfc3339%会把时间格式化为RFC3339标准,%msg:1:32%表示截取消息正文的前32个字符。属性选项的语法是在属性名后用冒号分隔,格式为属性名:起始位置:结束位置:选项,这种字段裁剪能力在做脱敏或限长处理时非常实用。
需要特别区分的是%timegenerated%和%timereported%。前者是本地rsyslog接收到消息的时间,后者是消息源头产生日志时自带的时间戳。两者在本地日志场景下几乎一致,但一旦涉及远程转发或队列积压,可能出现明显差异。做日志审计和排序时,选错属性会导致时间线错乱,这是初学者最容易踩的坑之一。
二、默认日志格式拆解与自定义格式实战
RHEL默认使用名为RSYSLOG_TraditionalFileFormat的内置模板,它输出的就是大家熟悉的/var/log/messages里的格式。如果想换成更利于日志平台解析的格式,比如ISO8601精确时间戳,可以自定义一个模板并挂载到动作上:
# 定义精确时间格式模板
template(name="precise" type="list") {
property(name="timereported" dateFormat="rfc3339")
constant(value=" ")
property(name="hostname")
constant(value=" ")
property(name="syslogtag")
property(value=" ")
property(name="msg" spifno1stsp="on")
property(name="msg" droplastlf="on")
constant(value="\n")
}
# 所有info级别日志使用新模板写入
*.*;auth,authpriv.none -/var/log/messages;precise
上面的spifno1stsp选项用于避免消息首字符空格重复,droplastlf用于去掉消息末尾自带的换行符,这两个小选项能让输出格式保持干净。自定义格式后,建议先在测试机验证,用logger -p local0.info "test message"发送一条测试日志,再查看文件内容确认格式符合预期。
除了普通文本,rsyslog还支持直接输出JSON结构化日志,方便接入ELK等日志分析平台。使用jsonf类型的属性输出,可以直接生成JSON格式的消息:
template(name="jsonfmt" type="list") {
constant(value="{")
property(name="timereported" outname="@timestamp" format="jsonf" dateFormat="rfc3339")
constant(value=",")
property(name="hostname" outname="host" format="jsonf")
constant(value=",")
property(name="syslogtag" outname="tag" format="jsonf")
constant(value=",")
property(name="msg" outname="message" format="jsonf")
constant(value="}\n")
}
# 转发到远端时使用JSON模板
*.* @@192.168.0.1:514;jsonfmt
三、动态文件路径:按主机和日期自动分目录存储
模板不仅能定义内容格式,还能作为动态文件名。这是集中式日志服务器最常用的玩法:把所有客户端的日志按照来源主机名自动分目录,再按日期分文件。配置如下:
# 按主机名和日期动态生成文件路径
template(name="remoteStore" type="string"
string="/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")
# 接收TCP 514端口日志并使用动态模板存储
module(load="imtcp")
input(type="imtcp" port="514")
if $fromhost-ip != "127.0.0.1" then {
action(type="omfile" dynaFile="remoteStore")
}
注意这里属性用的是大写形式%HOSTNAME%和系统属性%$YEAR%、%$MONTH%、%$DAY%,前者的$前缀表示引用的是系统级变量而非消息字段。动态文件路径默认会自动创建不存在的目录,但如果SELinux处于Enforcing状态,新目录可能因为安全上下文问题导致写入失败,需要用semanage fcontext和restorecon处理:
semanage fcontext -a -t var_log_t "/var/log/remote(/.*)?" restorecon -Rv /var/log/remote
除了按主机分目录,还可以按程序名分文件,用%programname%属性即可。例如把所有sshd的日志单独存放,配合if $programname == 'sshd' then的条件判断,可以实现精细化的日志分类。这种按需拆分的策略比把所有内容都塞进messages文件更利于排查问题和控制单文件体积。
四、常见配置错误与排查方法
模板定制中最常见的错误是语法位置错误:模板必须定义在被使用之前,如果模板写在了ruleset或action后面,rsyslog启动时会直接报错。修改配置后务必执行rsyslogd -N1做配置校验,它会在不启动服务的情况下检查语法,能提前发现大部分问题。
第二个常见问题是转义字符。模板中的\n在string格式中有效,但在某些插件场景下需要写成\\n。如果日志文件中出现字面的反斜杠加n,多半是转义层级出了问题。第三个问题是动态文件不落盘,除了前面提到的SELinux,还要检查磁盘空间、文件描述符数量,以及模板中引用的属性是否为空,空属性会生成类似/var/log/remote//2025-01-01.log的异常路径。
排查时可以开启调试模式:先停止服务,再用rsyslogd -dn > /tmp/rsyslog.debug 2>&1前台运行,调试日志会详细输出模板的解析和渲染过程,包括每个属性的取值。结合logger命令发送可控的测试消息,基本可以定位绝大多数模板相关的问题。掌握这些方法后,无论是格式调整还是存储规划,rsyslog的模板体系都能应对自如。