Linux之rsyslog(6)RainerScript

Author:Once Day Date:2025年11月12日

全系列文章请查看专栏: Linux Shell基础_Once_day的博客-CSDN博客

漫漫长路,有人对你微笑过嘛…

参考文档:

1. 介绍

RainerScript 是专为处理网络事件和配置事件处理器设计的脚本语言,也是 rsyslog 的主要配置语言。注意,该语言不可缩写为 rscript(此名称为他人商标)。

版本支持历程:

  • rsyslog 3.12.0 版本起提供有限支持(仅支持表达式功能)。
  • v5 版本新增对 “if … then” 语句的支持。
  • v6 版本实现首个完整功能版本。
1.1 数据类型

RainerScript 是一种无类型语言,但这并不意味着无需关注类型相关逻辑。

例如,表达式 "A" + "B" 无法返回有效结果 —— 字母不能直接用 “+” 运算(字符串拼接需使用连接运算符 &)。不过,当场景需要时,脚本解释器会自动完成类型转换。

1.2 表达式(Expressions)

RainerScript 支持任意复杂的表达式,包含所有常用运算符,运算优先级如下(优先级越高越先执行,例如乘法优先于加法):

  1. 括号内表达式(() 包裹)。
  2. not(逻辑非)、一元减号(-,负号)。
  3. *(乘法)、/(除法)、%(取模,同 C 语言)。
  4. +(加法)、-(减法)、&(字符串拼接)。
  5. ==(等于)、!=(不等于)、<>(不等于,与!=功能一致)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于)、contains(字符串包含)、startswith(字符串以… 开头)、endswith(字符串以… 结尾)。
  6. and(逻辑与)。
  7. or(逻辑或)。

关键注意事项:

  • 字符串专用运算符:containsstartswithendswith 仅适用于字符串类型比较。
  • not 运算符优先级较高:not a == b 会先计算 not a,再将结果与 b 比较,并非 “a 不等于 b”。
  • 推荐写法:判断 “a 不等于 b” 直接用 a != ba <> b(二者完全等价);需组合复杂逻辑时,务必用括号明确优先级(如 not (a == b and c > d))。
1.3 函数(Functions)

RainerScript 函数分为两类:内置函数(built-ins)和模块函数(modules),核心差异在于是否需要手动加载。

内置函数(Built-in Functions):无需额外配置,可直接在配置中调用,始终自动加载。

模块函数(Module Functions):使用前必须先加载对应的模块,配置语法如下:

module(load="<模块名称>")

关键规则:

  1. 函数名冲突处理:多个函数同名时,优先使用先加载模块的函数,会生成错误提示但不终止配置运行。
  2. 内置函数优先级:内置函数始终比所有模块先加载,无法通过模块函数覆盖内置函数(名称已被占用)。
  3. 模块命名规范:函数模块的名称以 fm 开头(如 fmjson)。

RainerScript 函数分为内置函数(直接使用)和模块函数(需加载对应模块),以下是核心功能说明:

(1)内置函数(Built-in Functions),无需加载模块,可直接调用:

  • cnum():将值转换为数值类型。
  • cstr():将值转换为字符串类型。
  • dyn_inc():动态递增计数器(支持自定义名称和初始值)。
  • exec_template():执行指定模板并返回结果。
  • exists():检查属性、变量或函数是否存在,返回布尔值。
  • field():按分隔符提取字符串中的字段(类似属性替换器的字段提取)。
  • format_time():将时间值格式化为指定格式(支持 RFC3339、MySQL 等格式)。
  • get_property():获取指定消息属性或系统属性的值。
  • getenv():获取系统环境变量的值。
  • int2hex():将整数转换为十六进制字符串。
  • num2ipv4() / ipv42num():IPV4 地址与数值的双向转换(如192.168.1.1↔数值)。
  • is_time():检查输入值是否为有效时间格式,返回布尔值。
  • lookup():在预设的键值对集合中查找值(常用于映射表查询)。
  • parse_json():解析 JSON 字符串,将其转换为 rsyslog 变量树(如$!json!field)。
  • parse_time():解析自定义格式的时间字符串,返回时间戳。
  • percentile_observe():记录数值用于百分位计算(如统计延迟的 95 百分位)。
  • previous_action_suspended():检查前一个动作是否被暂停,返回布尔值。
  • prifilt():按 syslog 优先级(PRI)过滤消息,返回匹配结果。
  • random():生成随机数(支持指定范围)。
  • re_extract() / re_extract_i():正则提取字符串内容,_i后缀表示不区分大小写。
  • re_match() / re_match_i():正则匹配字符串,返回布尔值,_i后缀表示不区分大小写。
  • replace():替换字符串中的指定内容(支持普通字符串或正则)。
  • script_error():触发脚本错误,可自定义错误信息。
  • strlen():返回字符串的长度。
  • substring():截取字符串的子串(指定起始和结束位置)。
  • tolower() / toupper():将字符串转换为小写 / 大写。
  • ltrim() / rtrim():去除字符串左侧 / 右侧的空白字符。
  • wrap():按指定长度换行字符串(用于格式化输出)。

(2)模块函数(Module Functions),需先加载对应模块(模块名以fm开头)才能使用:

  • Faup:解析 URL、日志中的网络地址等,提取协议、域名、端口等字段(模块:fmfaup)。
  • HashXX:对输入值进行 HashXX 哈希计算,返回哈希结果(模块:fmhashxx)。
  • HashXXmod:对输入值进行 HashXX 哈希计算并取模,返回指定范围内的结果(模块:fmhashxx)。
  • HTTP-Request:发送 HTTP 请求(GET/POST 等),支持传递参数和设置头信息(模块:fmhttp)。
  • Unflatten:将扁平的键值对(如event.dataset.name)转换为嵌套变量树(模块:fmjsonfmstruct)。
1.4 控制结构(Control Structures)

RainerScript 的控制结构语义与 C、Java、JavaScript、Ruby、Bash 等主流语言相似,以下通过示例说明其用法。

(1)if 语句,用于条件判断,满足条件时执行代码块(可嵌套)。

if ($msg contains "important") then {
   # 嵌套 if:若 $.foo 非空,则拼接 $.bar 和 $.baz 到 $.foo
   if ( $.foo != "" ) then set $.foo = $.bar & $.baz;
   # 满足条件时写入指定日志文件
   action(type="omfile" file="/var/log/important.log" template="outfmt")
}

(2)if/else-if/else 语句,多条件分支判断,按顺序匹配首个满足的条件并执行对应代码块。

if ($msg contains "important") then {
   set $.foo = $.bar & $.baz;
   action(type="omfile" file="/var/log/important.log" template="outfmt")
} else if ($msg startswith "slow-query:") then {
   # 匹配第二个条件时写入慢查询日志
   action(type="omfile" file="/var/log/slow_log.log" template="outfmt")
} else {
   # 所有条件不满足时执行默认逻辑
   set $.foo = $.quux;
   action(type="omfile" file="/var/log/general.log" template="outfmt")
}

(3)foreach 语句,仅用于遍历 JSON 结构(数组或对象),需先通过 mmjsonparse 等模块解析 JSON 数据。

  • 遍历 JSON 数组:按顺序获取元素。
  • 遍历 JSON 对象:随机顺序获取键值对(以 {"key": "键名", "value": "值"} 形式)。

示例 1:遍历 JSON 数组,假设 $.collection 是解析后的 JSON 数组 [1, "2", {"a": "b"}, 4]

foreach ($.i in $.collection) do {
   # 循环中 $.i 依次为 1、"2"、{"a": "b"}、4
   action(type="omfile" file="/var/log/array.log" template="json_elem")
}

示例 2:遍历 JSON 对象,假设 $.collection 是解析后的 JSON 对象 {"a": "b", "c": [1,2,3]}

foreach ($.i in $.collection) do {
   # 循环中 $.i 为 {"key": "a", "value": "b"}、{"key": "c", "value": [1,2,3]}(顺序不定)
   set $!key = $.i!key;    # 获取键名(如 "a"、"c")
   set $!val = $.i!value;  # 获取值(如 "b"、[1,2,3])
   action(type="omfile" file="/var/log/object.log" template="json_kv")
}

示例 3:嵌套 foreach:

foreach ($.quux in $!foo) do {  # 遍历外层 JSON 结构
   action(type="omfile" file="./rsyslog.out.log" template="quux")
   foreach ($.corge in $.quux!bar) do {  # 遍历内层 JSON 结构
      reset $.grault = $.corge;  # 重置变量并赋值
      action(type="omfile" file="./rsyslog.out.log" template="grault")
      # 拼接字符串
      if ($.garply != "") then set $.garply = $.garply & ", ";
      reset $.garply = $.garply & $.grault!baz;
   }
}

注意事项:

  • 限制:仅支持 JSON 解析后的数组 / 对象,不能遍历手动赋值的数组(如 set $.noarr = ["192.168.1.1"] 无法遍历)。

  • 异步动作:若循环体内使用异步动作(如队列),需设置 action.copyMsg="on" 以避免消息被后续迭代修改:

    foreach ($.quux in $!foo) do {
        action(type="omfile" file="./out.log" template="quux"
               queue.type="linkedlist" action.copyMsg="on")
    }
    

(4)call 语句,用于调用自定义的规则集(ruleset),详情参考 rsyslog 官方文档中 “call 语句” 部分。

(5)continue 语句,空操作(NOP),通常用于 if 结构的 then 分支中占位。

1.5 配置对象(Configuration objects)

配置对象的参数不区分大小写。

通用参数(Common Parameters),config.enabled,8.33.0 版本新增,所有配置对象均支持此参数。用于在自动生成的配置中启用或禁用特定配置结构:

  • 设为on或不指定时,配置生效;
  • 设为其他值时,配置被忽略。

可结合反引号(`)功能,通过文件或环境变量动态控制配置片段的启用状态。

示例:通过环境变量LOAD_IMPTCP条件加载模块:

module(load="imptcp"
    config.enabled=`echo $LOAD_IMPTCP`)  # 变量为off时不加载模块

配置对象类型(Objects):

  1. action(),描述 rsyslog 需执行的动作(如写入文件、转发消息等),是定义处理行为的核心对象。
  2. global(),设置全局配置参数(如默认模板、队列模式等),详情参考 rsyslog 全局配置对象文档。
  3. input(),定义输入源(如 TCP/UDP 端口、文件监控等),用于收集待处理的消息。
  4. module(),加载插件模块(如解析器、输出模块等),扩展 rsyslog 功能。
  5. parser(),定义自定义解析器,用于解析特定格式的消息(如非标准日志格式)。
  6. timezone(),配置时区设置,影响日志时间戳的解析和显示。
  7. include(),引入外部配置片段,便于拆分和管理复杂配置。
1.6 Rsyslog 参数字符串常量(Rsyslog Parameter String Constants)

字符串常量的值在 rsyslog 进程启动时解析,且在进程生命周期内保持不变

用途:字符串常量用于比较操作、配置参数值、函数参数等场景。

转义规则:字符串常量中,特殊字符通过前缀反斜杠(\)转义,类似 C 或 PHP 语法。若不确定如何正确转义,可使用 RainerScript 字符串转义在线工具 验证。

类型:rsyslog 提供多种字符串常量类型,借鉴了常见的 shell 语义:

(1)单引号(Single quotes):

值保持原样,仅处理转义序列(如 \' 表示单引号本身)。示例:'Hello \'World\'' 解析为 Hello 'World'

(2)双引号(Double quotes):

与单引号功能等效,但美元符号($)必须转义为 \$。若 $ 未转义,会触发语法错误,导致 rsyslog 启动失败。示例:"Price: \$100" 解析为 Price: $100

(3)反引号(Backticks)

版本支持:8.33.0 版本初步支持(有限子集);8.37.0 版本支持多个环境变量扩展及常量文本拼接;8.2508.0 版本支持花括号风格变量(${VAR})、无括号变量的自动终止(遇到非 [A-Za-z0-9_] 字符时),以及变量与静态文本相邻的场景(如键值对)。

功能:实现 shell 类似行为的安全子集,仅支持以下两种形式:

  • echo ...:解析包含环境变量扩展的简单文本。
  • cat <filename>:包含单个文件的内容,文件不可读时返回空字符串。

其他构造会导致解析错误,且 echo/cat 与后续参数之间必须有且仅有一个空格。

环境变量扩展规则

  • 支持 $VAR${VAR} 两种格式。
  • 无括号 $VAR:变量名在遇到首个非 [A-Za-z0-9_] 字符时终止,该字符原样输出(如 $VAR!! 保留)。
  • 有括号 ${VAR}:扩展在匹配的 } 处终止,自然支持相邻静态文本(如 prefix${VAR}suffix)。
  • 未知变量扩展为空字符串(不报错)。
  • 不支持命令替换及其他 shell 特性(如 $(pwd))。
1.7 示例

简单环境变量扩展(反引号),已知环境变量 SOMEPATH=/var/log/custompath,通过 echo 拼接路径:

# 配置参数中扩展变量,最终值为 /var/log/custompath/myfile
param = `echo $SOMEPATH/myfile`

花括号变量与相邻文本(反引号),已知环境变量 HOSTNAME=myhost,支持无括号变量自动终止(! 为非变量字符,原样保留):

# 最终值为 Log-myhost!
title = `echo Log-${HOSTNAME}!`

键值对配置(模块参数常用,反引号),已知环境变量 KAFKA_PASSWORD=supersecret(要求 rsyslog ≥ 8.2508.0),用于 Kafka 认证配置:

action(
  type="omkafka"
  broker=["kafka.example.com:9093"]
  confParam=[
    "security.protocol=SASL_SSL",
    "sasl.mechanism=SCRAM-SHA-512",
    "sasl.username=myuser",
    `echo sasl.password=${KAFKA_PASSWORD}`  # 扩展为 sasl.password=supersecret
  ]
)

旧版本兼容方案(rsyslog < 8.2508.0),先在外部脚本中拼接完整键值对,再传入 rsyslog:

# Bash/初始化脚本中预拼接
export KAFKA_PASSWORD='supersecret'
export SASL_PWDPARAM="sasl.password=${KAFKA_PASSWORD}"
# rsyslog 配置中直接扩展预拼接变量
action(
  type="omkafka"
  broker=["kafka.example.com:9093"]
  confParam=[
    "security.protocol=SASL_SSL",
    "sasl.mechanism=SCRAM-SHA-512",
    "sasl.username=myuser",
    `echo $SASL_PWDPARAM`  # 扩展为 sasl.password=supersecret
  ]
)

基于环境变量的配置对象条件启用(反引号 + config.enabled),通过环境变量 LOAD_IMTCP 控制 imtcp 模块和 TCP 输入的启用状态,仅当 LOAD_IMTCP=on 时生效。

步骤 1:设置环境变量(systemd 系统),创建 systemd 扩展配置文件,注入环境变量:

sudo mkdir -p /etc/systemd/system/rsyslog.service.d
sudo tee /etc/systemd/system/rsyslog.service.d/10-imtcp.conf >/dev/null <<'EOF'
[Service]
Environment=LOAD_IMTCP=on  # 启用模块,设为 off 则禁用
EOF

# 重载配置并重启 rsyslog
sudo systemctl daemon-reload
sudo systemctl restart rsyslog

步骤 2:RainerScript 配置:

# 条件加载 imtcp 模块(LOAD_IMTCP 非 on 则不加载)
module(
  load="imtcp"
  config.enabled=`echo ${LOAD_IMTCP}`
)

# 条件启用 TCP 输入(端口 514,关联规则集 in_tcp)
input(
  type="imtcp"
  port="514"
  ruleset="in_tcp"
  config.enabled=`echo ${LOAD_IMTCP}`
)

# 规则集:接收 TCP 日志并写入文件
ruleset(name="in_tcp") {
  action(type="omfile" file="/var/log/remote/tcp.log")
}

容器化部署场景,直接通过环境变量传递开关,无需修改配置文件:

docker run --rm \
  -e LOAD_IMTCP=on \  # 启用 TCP 输入
  -v /path/to/rsyslog.conf:/etc/rsyslog.conf:ro \
  my-rsyslog-image

引入文件内容(反引号 + cat),读取文件中的令牌(如证书、密钥)作为配置参数:

# 读取 /etc/rsyslog.d/token.txt 的内容作为 token 值,文件不可读则返回空字符串
token = `cat /etc/rsyslog.d/token.txt`
1.8 变量属性类型

rsyslog 中的变量(属性)主要分为两类:消息 JSON 属性和本地变量,均支持通过 set/unset/reset 语句操作(仅这两类可修改,其他消息属性不可改)。

  1. 消息 JSON 属性(Message JSON Properties):
  • 前缀标识:$!(感叹号为根节点),路径分隔符统一使用 !
  • 作用域:与当前消息绑定,属于消息的 JSON 结构部分(如 CEE/Lumberjack 格式消息)。
  • 示例:$!event!log!message(三级嵌套的消息 JSON 属性)。
  1. 本地变量(Local Variables):
  • 前缀标识:$.(点号为根节点),路径分隔符统一使用 !
  • 作用域:仅当前消息上下文,不属于消息属性(不会包含在 $! JSON 结构中)。
  • 示例:$.temp!count(二级嵌套的本地变量)。

注意:所有变量操作语句末尾必须加英文分号(;),否则会触发配置加载语法错误。

set 语句:设置或合并变量,根据变量现有值的类型,行为不同:

  • 现有值和新值均为对象:合并新值到根节点(非指定键下合并)。

    set $.x!one = "val_1";  # $. = { "x": { "one": "val_1" } }
    set $.z!var = $.y;       # 新值 $.y 是对象,合并后 $. 根节点新增 "two": "val_2"
    
  • 现有值是对象、新值非对象(字符串 / 数字等):忽略新值。

    set $.x!one = "val_1";  # $.x 是对象
    set $.x = "quux";       # 新值是字符串,被忽略,$.x 仍为 { "one": "val_1" }
    
  • 现有值非对象:覆盖原有值。

    set $.x!val = "val_1";  # $.x!val 是字符串
    set $.x!val = "quux";   # 覆盖为 "quux",结果 $.x = { "val": "quux" }
    

unset 语句:删除变量键,移除指定路径的变量键,父级结构保留为空对象。

set $.x!val = "val_1";  # $. = { "x": { "val": "val_1" } }
unset $.x!val;          # 删除 "val" 键,结果 $. = { "x": { } }

reset 语句:强制覆盖变量,无论变量原有类型或是否存在,均强制设置为新值。

set $.z!var = $.x;      # $.z!var = { "one": "val_1" }
reset $.z!var = $.y;    # 强制覆盖,$.z!var = { "two": "val_2" }
reset $.x = "quux";     # 强制覆盖对象 $.x 为字符串,结果 $.x = "quux"
1.9 “call” 语句

“call” 语句用于关联多个规则集(ruleset),模仿主流编程语言的 “调用” 语法,可将规则集视为子程序(本质就是子程序)。

支持调用任意类型的规则集,执行方式由规则集的队列配置决定:

  • 规则集已分配队列:消息投递至该队列异步处理,调用语句立即返回。
  • 无队列规则集:同步执行规则集,执行完成后才返回原流程。

替代废弃的 omruleset 模块,功能一致但效率更高:omruleset 需复制消息(占用内存、增加分配 / 释放开销),而同步调用的 call 语句无额外开销。

关键注意事项:

  • “stop” 语句的影响:异步执行时,规则集中的 stop 语句不会影响原始消息的后续处理;同步执行时则会终止原始消息流程。
  • 兼容性:8.2110.0 版本前存在 bug—— 规则集显式设置 queue="direct" 会被当作真实队列处理,可能导致异常行为;8.2110.0 及以上版本已修复,行为一致。若需兼容旧版本或遇到异常,可给规则集添加小型数组队列。
call rulesetname
  • rulesetname:需在配置中其他位置已定义的规则集名称。
  • 执行方式(同步 / 异步)由规则集自身参数决定,call 语句无法覆盖。
# 定义被调用的规则集(无队列,同步执行)
ruleset(name="process_debug") {
  if ($syslogseverity-text == "debug") then {
    action(type="omfile" file="/var/log/debug.log")
    stop  # 同步执行时,终止当前消息后续处理
  }
}

# 主规则集:调用 process_debug 规则集
ruleset(name="main") {
  call process_debug  # 同步执行,处理完 debug 日志后返回
  action(type="omfile" file="/var/log/all.log")  # debug 消息不会执行此动作(因 stop)
}
1.10 “call_indirect” 语句

“call_indirect” 语句与 “call” 语句功能等效,核心差异是被调用的规则集名称并非固定常量,而是可在运行时计算的表达式。

核心特性:

  • 规则集名称动态计算:通过表达式(如消息变量、字符串拼接)生成规则集名称,灵活适配动态场景。
  • 未找到规则集的处理:若表达式计算出的规则集名称不存在,会输出错误日志并忽略该语句,后续执行不受影响。
  • 语法要求:语句末尾必须加英文分号(;),否则会触发配置加载语法错误。
call_indirect expression;
  • expression:任意有效的 RainerScript 表达式(如变量、字符串拼接等),详情参考表达式文档。

(1)基于消息变量调用规则集:假设规则集名称与 syslog 标签(syslogtag)一致,直接通过变量动态调用:

# 运行时根据 $syslogtag 的值调用对应规则集(如 $syslogtag=sshd 则调用 sshd 规则集)
call_indirect $syslogtag;

(2)带前缀的规则集调用(安全防护):为避免恶意注入无效标签,给规则集名称添加唯一前缀(如 changeme-),通过字符串拼接生成名称:

# 规则集名称格式为 "changeme-+syslogtag"(如 changeme-sshd),降低注入风险
call_indirect "changeme-" & $syslogtag;

(3)常量名称调用(不推荐):虽支持通过表达式传递常量名称,但性能不及 call 语句,建议优先使用 call

# 等效于 call my_ruleset; 但性能更差,不推荐
call_indirect "my_ruleset";

之所以区分 “call” 和 “call_indirect” 两个语句,是因为 “call” 语句早于 “call_indirect” 存在。若扩展 “call” 支持表达式,会破坏现有配置(如 call ruleset 需改为 call "ruleset"),因此新增 “call_indirect” 专门处理动态规则集调用场景。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐