深入 Logstash 02 — event 模型:@timestamp、@metadata 与字段引用
上一篇确立了 JRuby/JVM/多 pipeline 的运行形态。这一篇进入 event 的内部结构。event 容易被误解成"就是一个 JSON 对象"。更准确的说法是:event 是 Logstash 在管道内部流通的核心数据单元,由 Java 对象实现,包含业务字段、系统保留字段(@timestamp、@version)和管道内部暂存区(@metadata),三者在语义上截然不同。本文只抓一个问题:event 的字段空间如何分层,以及 @metadata 为什么不进最终输出。
数据流全景
1 | |
@timestamp:第一等公民
@timestamp 是 event 进入 pipeline 的时间标记,类型为 LogStash::Timestamp,底层对应 java.time.Instant。
自 Logstash 8.0 起,@timestamp 支持纳秒精度(由 pipeline.ecs_compatibility 配置控制,默认 v8)。在 ECS v8 兼容模式下,@timestamp 序列化为 ISO-8601 字符串,精确到纳秒,例如:
1 | |
行为要点:
- input 插件在 decode 阶段(codec 调用
LogStash::Event.new)自动赋值为当前时间;如果原始数据中已有时间字段,需要显式用datefilter 解析并覆盖@timestamp,否则它记录的是到达时间而非事件发生时间。 @timestamp不可删除。调用event.remove("[@timestamp]")在当前 8.x 版本中会抛出异常或被静默忽略(具体行为视版本而定),应通过datefilter 修改而非删除。- Elasticsearch output 默认将
@timestamp映射为_source.@timestamp,同时用于索引名称模板(如logstash-%{+YYYY.MM.dd})。
@metadata:管道内部暂存区
@metadata 是一个 map 型字段,在 filter 阶段可以自由读写,但在 output 的 codec encode 阶段被剥离,不出现在最终序列化结果中。
典型用途:
- 携带路由决策信息(
[@metadata][target_index]),在 output 中引用但不写入 Elasticsearch。 - 记录重试次数或处理标记,避免污染业务字段。
- Beats 输入将部分协议元数据(如
[@metadata][beat]、[@metadata][version])放入此空间,filter 阶段可读取但不会意外输出到下游。
1 | |
字段引用语法
Logstash 使用方括号语法引用字段,与 JSON path 类似但有自己的规则:
| 写法 | 含义 |
|---|---|
[message] |
顶层字段 message |
[host][hostname] |
嵌套字段,等价于 host.hostname |
[@timestamp] |
保留字段(@前缀) |
[@metadata][key] |
@metadata 子字段 |
%{[field]} |
sprintf 格式,用于字符串插值 |
%{+YYYY.MM.dd} |
Joda-Time 格式,仅用于 @timestamp 的日期格式化 |
字段引用在 if 条件、mutate 插件、output 的 index/document_id 参数中普遍使用。
1 | |
filter 插件的 event API
filter 插件通过 event 对象操作字段:
| 方法 | 说明 |
|---|---|
event.get("[field]") |
读取字段值,不存在时返回 nil |
event.set("[field]", value) |
写入字段值 |
event.remove("[field]") |
删除字段 |
event.tag("_error") |
向 tags 数组追加标签 |
event.cancel |
取消 event,阻止其进入 output |
event.to_hash |
获取所有业务字段(不含 @metadata) |
在 ruby filter 中可直接调用这些方法:
1 | |
类型与强制转换
所有从文本来源(stdin、file input 的单行读取)进入 pipeline 的字段,初始类型均为字符串。数值比较或数值运算前需要显式转换:
1 | |
Beats input 在传输层使用 JSON 或 Protobuf 编码,字段类型在到达 Logstash 时已保留,无需额外转换。
可运行实验
目标:观察 @metadata 在不同 codec 下的可见性差异,验证它不进入序列化输出。
1 | |
1 | |
rubydebug 输出(含 metadata => true 选项):
1 | |
查看 /tmp/logstash_out.json(json_lines 输出):
1 | |
@metadata 不出现在文件输出中,与预期一致。
再演示 sprintf 字段引用:
1 | |
关键对象映射
| event 概念 | 对应 Java/JRuby 类 | 说明 |
|---|---|---|
| event 本体 | org.logstash.Event |
内部用 ConvertedMap 存储字段 |
| @timestamp | org.logstash.Timestamp |
封装 java.time.Instant |
| @metadata | ConvertedMap 子映射 |
encode 时不序列化 |
| 字段引用解析 | FieldReference |
将 [a][b] 解析为路径数组 |
| tag 操作 | tags 字段(字符串数组) |
event.tag(x) 追加 |
模式提炼
event 的字段空间分三层:业务字段(随 event 输出)、系统保留字段(@timestamp、@version,始终存在)、管道暂存区(@metadata,处理完成后剥离)。这种分层使 pipeline 内部的路由信息和处理状态不会污染最终的数据输出,是"关注点分离"在数据结构级别的体现。
@timestamp 的语义是"事件的时间维度锚点",date filter 的职责是将原始日志中的时间字符串解析后写入 @timestamp,使其从"到达时间"变为"发生时间"。这一步在日志分析中是精确时序的前提。
工程迁移表
| Logstash event 概念 | Kafka Record 对应 | Flink StreamRecord 对应 | 结构化日志对应 |
|---|---|---|---|
| 业务字段 | value(反序列化后的 Map) | value | log fields (level/msg/…) |
| @timestamp | timestamp(毫秒 epoch) | timestamp + watermark | timestamp 字段 |
| @metadata | headers(producer/consumer 元数据) | 无直接对应,常用 SideOutput | MDC / context map(不输出) |
| @version | 无直接对应 | 无直接对应 | schema version 字段 |
| event.cancel | filter 中 drop()或不 forward | filter 中不调用 collector.collect | 过滤条件 |
| tags 数组 | 无直接对应,常用 header | 无直接对应 | labels / tags map |
常见误解
误解一:“@metadata 是 Logstash 自动加的,不用管。”
实际情况:Logstash 确实会自动填充 [@metadata][pipeline] 等少量内置字段,但 @metadata 主要是供用户在 filter 阶段存放中间状态,是主动设计的扩展点,而非被动元数据。
误解二:“@timestamp 就是 Logstash 收到日志的时间,直接用就行。”
实际情况:不经过 date filter 处理时,@timestamp 是 event 进入 input codec 的时刻,而非日志的原始发生时间。对于有历史回溯需求或时序分析的场景,必须用 date filter 从 message 中解析真实时间并覆盖 @timestamp。
误解三:“字段引用 [host] 和 [host][hostname] 是一样的。”
实际情况:前者引用整个 host 对象(可能是 map),后者引用嵌套字段 host.hostname(字符串)。在条件表达式中混用会导致类型不匹配。
误解四:“rubydebug 输出就是最终写入 Elasticsearch 的内容。”
实际情况:rubydebug 默认包含 @metadata(加 metadata => true 选项时),而 Elasticsearch output 的 codec encode 会剥离 @metadata。两者内容不完全一致。
练习
-
编写一条 pipeline,从 stdin 读取 Apache access log 格式的文本(如
127.0.0.1 - - [06/Aug/2026:13:20:00 +0800] "GET / HTTP/1.1" 200 1234),用grokfilter 提取字段,再用datefilter 将提取的时间覆盖@timestamp,最后用 rubydebug 输出,对比处理前后@timestamp的值。 -
在
@metadata中记录 filter 链经过的每个处理阶段名称(每经过一个mutate就追加一个标记),然后在 output 中用if [@metadata][stages]做条件分流,验证@metadata参与路由但不进入输出。 -
用
rubyfilter 调用event.to_hash打印所有业务字段的 key,观察@metadata是否出现在to_hash结果中。
系列导航
- 上一篇:深入 Logstash 01 — 架构:JRuby、JVM 与 pipeline 的运行形态
- 下一篇:深入 Logstash 03 — codec:字节流与 event 的边界转换
参考资料
- Elastic 官方文档 — Event API
- Elastic 官方文档 — Field References Deep Dive
- Elastic 官方文档 — Accessing Event Data and Fields in the Configuration
- Elastic 官方文档 — @metadata field
- Elastic 官方文档 — date filter plugin
