WPL 分隔符使用指南
概述
分隔符(Separator)是 WPL 中用于分割日志字段的关键语法元素。通过灵活使用分隔符,可以解析各种格式的结构化日志数据。
快速开始
基本语法
| take(field_name) separator
分隔符写在字段定义之后,用于指示该字段在何处结束。
简单示例
# 使用空格分隔
| take(ip) \s
| take(method) \s
| take(path)
# 使用逗号分隔
| take(name) ,
| take(age) ,
| take(city)
内置分隔符
1. 空格分隔符 \s
匹配单个空格字符。
# 输入: "192.168.1.1 GET /api/users"
rule parse_log {
| take(ip) \s
| take(method) \s
| take(path)
}
适用场景:
- Apache/Nginx 访问日志
- 空格分隔的简单日志
- 标准格式日志文件
2. 制表符分隔符 \t
匹配单个制表符(Tab)字符。
# 输入: "user001\t25\tBeijing"
rule parse_tsv {
| take(user_id) \t
| take(age) \t
| take(city)
}
适用场景:
- TSV(Tab-Separated Values)文件
- 数据库导出文件
- 制表符对齐的日志
3. 通用空白分隔符 \S
匹配空格或制表符(二选一)。
# 输入: "field1 field2\tfield3"(混合空格和制表符)
rule parse_flexible {
| take(col1) \S
| take(col2) \S
| take(col3)
}
适用场景:
- 格式不统一的日志(混合空格和 Tab)
- 手工编辑过的配置文件
- 宽松的数据解析
行为说明:
- 遇到空格或制表符都会停止
- 灵活处理格式不一致的数据源
4. 行尾分隔符 \0
读取到行尾(换行符或字符串结束)。
# 输入: "prefix_value some remaining text until end"
rule parse_to_end {
| take(prefix) _
| take(remaining) \0
}
适用场景:
- 解析最后一个字段
- 读取消息正文
- 获取剩余所有内容
别名:
\0和0等价
5. 自定义字符分隔符
使用任意单个字符作为分隔符。
# 逗号分隔
| take(name) ,
| take(age) ,
# 竖线分隔
| take(id) |
| take(status) |
# 分号分隔
| take(key) ;
| take(value) ;
支持的字符:
- 逗号
, - 竖线
| - 分号
; - 冒号
: - 等号
= - 斜杠
/ - 等任意单字符
6. 自定义字符串分隔符
使用多字符字符串作为分隔符。
# 使用 " | " 分隔
| take(field1) " | "
| take(field2) " | "
# 使用 " :: " 分隔
| take(module) " :: "
| take(function) " :: "
适用场景:
- 格式化输出的日志
- 特定格式协议
- 需要明确边界的数据
实际应用场景
场景 1:解析 Nginx 访问日志
# 日志格式: 192.168.1.1 - - [29/Jan/2024:10:30:45 +0800] "GET /api/users HTTP/1.1" 200 1234
rule nginx_access_log {
| take(client_ip) \s
| take(identity) \s
| take(user) \s
| take(timestamp) \s
| take(request) \s
| take(status_code) \s
| take(bytes_sent) \0
}
场景 2:解析 TSV 数据
# 输入: "2024-01-29\t10:30:45\tERROR\tDatabase connection failed"
rule tsv_log {
| take(date) \t
| take(time) \t
| take(level) \t
| take(message) \0
}
场景 3:解析 CSV 数据
# 输入: "John Smith,30,New York,Engineer"
rule csv_parser {
| take(name) ,
| take(age) ,
| take(city) ,
| take(job) \0
}
场景 4:解析结构化日志
# 输入: "level=error | module=database | msg=Connection timeout"
rule structured_log {
| take(level_prefix) =
| take(level_value) " | "
| take(module_prefix) =
| take(module_value) " | "
| take(msg_prefix) =
| take(message) \0
}
场景 5:处理混合空白的日志
# 输入: "192.168.1.1 \tGET\t /api/data"(混合空格和制表符)
rule flexible_whitespace {
| take(ip) \S
| take(method) \S
| take(path) \0
}
场景 6:解析 Syslog 格式
# 输入: "Jan 29 10:30:45 hostname app[1234]: Error message here"
rule syslog {
| take(month) \s
| take(day) \s
| take(time) \s
| take(hostname) \s
| take(app_tag) ": "
| take(message) \0
}
场景 7:解析键值对日志
# 输入: "user=admin;action=login;ip=192.168.1.1;status=success"
rule kv_log {
| take(user_key) =
| take(user_value) ;
| take(action_key) =
| take(action_value) ;
| take(ip_key) =
| take(ip_value) ;
| take(status_key) =
| take(status_value) \0
}
分隔符优先级
WPL 支持三个级别的分隔符设置:
1. 字段级分隔符(优先级 3,最高)
| take(field1) , # 该字段使用逗号
| take(field2) \s # 该字段使用空格
2. 组级分隔符(优先级 2)
group {
sep = \t # 组内所有字段默认使用制表符
| take(field1)
| take(field2)
}
3. 继承分隔符(优先级 1,最低)
从上游规则继承的默认分隔符。
优先级规则
字段级 > 组级 > 继承级
group {
sep = \t # 组级:制表符
| take(f1) # 使用 \t
| take(f2) , # 使用 ,(字段级覆盖组级)
| take(f3) # 使用 \t
}
分隔符行为
全局替换 vs 单次匹配
分隔符只在当前字段结束位置匹配一次:
# 输入: "hello,world,test"
| take(first) , # 读取 "hello",消费第一个逗号
| take(second) , # 读取 "world",消费第二个逗号
| take(third) \0 # 读取 "test"
分隔符消费行为
默认情况下,分隔符会被消费(从输入中移除):
# 输入: "a,b,c"
| take(x) , # 读取 "a",消费 ",",剩余 "b,c"
| take(y) , # 读取 "b",消费 ",",剩余 "c"
分隔符不存在的情况
如果到达字符串末尾仍未找到分隔符,读取到末尾:
# 输入: "field1 field2"
| take(f1) , # 未找到逗号,读取全部 "field1 field2"
高级用法
组合使用多种分隔符
# 输入: "192.168.1.1:8080/api/users?id=123"
rule url_parse {
| take(ip) :
| take(port) /
| take(api_path) /
| take(resource) ?
| take(query_string) \0
}
处理可选字段
# 输入可能是: "user,30,city" 或 "user,,city"(age 为空)
rule optional_fields {
| take(name) ,
| take(age) , # 可能为空字符串
| take(city) \0
}
跳过不需要的字段
# 只提取第 1 和第 3 个字段
rule skip_fields {
| take(field1) ,
| take(_skip) , # 临时变量,不保存
| take(field3) \0
}
使用限制
1. 分隔符不支持正则表达式
# ❌ 不支持正则
| take(field) [0-9]+
# ✅ 使用固定字符串
| take(field) \s
2. 分隔符区分大小写
# "ABC" 和 "abc" 是不同的分隔符
| take(field1) ABC
| take(field2) abc
3. 空字符串不能作为分隔符
# ❌ 不支持
| take(field) ""
# ✅ 使用 \0 读取到末尾
| take(field) \0
4. 转义字符限制
当前支持的转义字符:
\s- 空格\t- 制表符\S- 空格或制表符\0- 行尾
其他转义字符(如 \n、\r)需要使用实际字符。
性能说明
单字符分隔符
性能最优,推荐优先使用:
| take(f1) ,
| take(f2) \s
| take(f3) \t
- 时间复杂度:O(n)
- 扫描速度:约 500 MB/s
多字符分隔符
性能略低,但仍然高效:
| take(f1) " | "
| take(f2) " :: "
- 时间复杂度:O(n × m),m 为分隔符长度
- 扫描速度:约 300-400 MB/s
通用空白分隔符 \S
需要逐字符检查,性能介于两者之间:
| take(f1) \S
- 时间复杂度:O(n)
- 扫描速度:约 400 MB/s
错误处理
常见错误
1. 分隔符未找到
错误: 未找到分隔符 ','
原因: 输入字符串中不包含指定的分隔符
解决: 检查输入格式或使用 \0 读取到末尾
2. 分隔符语法错误
错误: invalid separator
原因: 使用了不支持的分隔符语法
解决: 参考本文档使用正确的分隔符格式
3. 字段顺序错误
错误: 字段解析失败
原因: 字段定义顺序与实际数据不匹配
解决: 调整字段顺序以匹配输入格式
最佳实践
1. 优先使用内置分隔符
# ✅ 推荐
| take(f1) \s
| take(f2) \t
# ⚠️ 避免(除非必要)
| take(f1) " "
| take(f2) "\t"
2. 明确指定最后字段的分隔符
# ✅ 推荐(明确到行尾)
| take(message) \0
# ⚠️ 不清晰
| take(message) # 依赖默认行为
3. 使用 \S 处理不规范数据
# ✅ 推荐(兼容性好)
| take(field1) \S
| take(field2) \S
# ⚠️ 可能失败(如果混合了空格和制表符)
| take(field1) \s
| take(field2) \s
4. 复杂格式使用多字符分隔符
# ✅ 清晰准确
| take(level) " | "
| take(message) " | "
# ⚠️ 可能误匹配
| take(level) |
| take(message) |
5. 组合使用字段级和组级分隔符
# ✅ 推荐(减少重复)
group {
sep = ,
| take(f1)
| take(f2)
| take(f3) \0 # 最后字段使用不同分隔符
}
调试技巧
1. 逐字段验证
# 先解析第一个字段
| take(field1) ,
# 确认成功后添加第二个
| take(field1) ,
| take(field2) ,
# 依次添加...
2. 使用临时字段查看中间结果
| take(field1) ,
| take(_debug) , # 临时字段,查看剩余内容
| take(field2) \0
3. 打印分隔符位置
在测试环境中,使用调试模式查看分隔符匹配情况:
wp-motor --debug rule.wpl < test.log
4. 验证分隔符字符
对于不可见字符(如制表符),使用十六进制查看器确认:
# 查看文件中的制表符
cat -A test.log
# 或
hexdump -C test.log | head
常见问题 (FAQ)
Q1: \s 和 \S 有什么区别?
\s:只匹配空格 (space)\S:匹配空格或制表符 (space OR tab)
# 输入: "a b"
| take(x) \s # ✅ 匹配成功
# 输入: "a\tb"
| take(x) \s # ❌ 匹配失败(这是制表符,不是空格)
| take(x) \S # ✅ 匹配成功
Q2: 如何处理连续的分隔符?
WPL 会将连续分隔符视为多个空字段:
# 输入: "a,,c"
| take(f1) , # 读取 "a"
| take(f2) , # 读取 ""(空字符串)
| take(f3) \0 # 读取 "c"
Q3: 分隔符会影响性能吗?
单字符分隔符性能最优,多字符分隔符略慢,但对于大多数场景影响可忽略。
Q4: 如何解析嵌套结构?
使用多级分隔符:
# 输入: "k1=v1;k2=v2|k3=v3;k4=v4"
rule nested {
| take(group1) |
| take(group2) \0
}
# 然后在每个 group 内再用 ; 和 = 解析
Q5: 分隔符可以是中文吗?
可以,支持 Unicode 字符:
# 使用中文逗号分隔
| take(field1) ,
| take(field2) ,
Q6: \0 和省略分隔符有区别吗?
建议显式使用 \0,语义更清晰:
# ✅ 推荐(明确)
| take(message) \0
# ⚠️ 可以但不明确
| take(message)
Q7: 如何处理引号内的分隔符?
对于包含引号的复杂格式,建议使用专门的解析器(如 JSON、KV 解析器):
# 复杂 CSV(带引号)
# 输入: "field1","field with , comma","field3"
# 建议使用 CSV 解析器而非手动分隔符
更多资源
- WPL 语法参考:
docs/guide/wpl_syntax.md - 解析器开发指南:
docs/guide/wpl_field_func_development_guide.md - chars_replace 使用指南:
docs/usage/wpl/chars_replace.md - 源代码:
crates/wp-lang/src/ast/syntax/wpl_sep.rs
版本历史
-
1.11.0 (2026-01-29)
- 新增
\t制表符分隔符支持 - 新增
\S通用空白分隔符(空格或制表符) - 优化 Whitespace 分隔符性能
- 添加完整的测试覆盖
- 新增
-
1.10.x 及更早版本
- 支持
\s(空格)和\0(行尾) - 支持自定义字符和字符串分隔符
- 支持
提示: 分隔符是 WPL 解析的核心,选择合适的分隔符可以大大简化日志解析规则。