一、踩坑现场:日志过滤“失灵”的元凶

上周帮一个伙伴排查日志告警的问题,他说明明在Vector里设了只收集警告(warn)以上级别的日志,结果监控里全是各种info和debug,告警天天被没用的日志轰炸。我过去看了一眼,配置看起来没毛病啊,filter那里写了level in ["err","warn","crit","alert"],但就是不管用。

1.1 还原“失灵”的场景

他的环境是用systemd管理所有服务,Vector从journald采集日志,再推到Loki做监控。之前一直正常,突然某天就过滤失效了。翻了半天配置,发现他居然没配journald的字段映射——journald本身存日志的时候,会把优先级、进程名这些信息藏在叫PRIORITY、SYSLOG_IDENTIFIER的字段里,而Vector默认不会自动把这些转成我们常用的level这样的名字,导致他的过滤条件根本匹配不到正确的字段。

二、挖根溯源:journald的“隐藏字段”和Vector的“映射坑”

其实systemd-journald就像一个会记笔记的小秘书,它给每条日志都打了一堆标签:比如优先级(PRIORITY,0到7的数字,数字越小越严重)、进程名(SYSLOG_IDENTIFIER)、主机名(_HOSTNAME)这些。但这个小秘书太耿直,只会存原始数字,不会帮你转换成我们看得懂的“error”“warn”这种级别。而Vector呢,作为日志采集工具,默认只会把journald的原始字段拉过来,不会自动做转换,所以当我们写过滤条件用level的时候,Vector根本找不到叫level的字段,只能瞎匹配,自然过滤失效。

2.1 还有个坑:游标没对齐

除了字段映射,还有个更隐蔽的坑——journald的游标。每个日志条目都有个唯一的游标ID,Vector用这个来记自己读到哪了,下次采集不会重复。如果游标丢了或者没持久化,Vector就会从头读日志,之前的过滤失效不说,还会重复采集大量日志,导致日志混乱。

三、完整解法:游标对齐+元数据映射的正确姿势

要解决这两个问题,得两步走:第一步是把journald的原始字段转成我们需要的结构化字段,第二步是把游标持久化,保证采集不丢不重。

3.1 先搞定游标:别让日志重复采集

journald的游标需要存在磁盘上,Vector每次采集完会把当前的游标ID写进去,下次启动或者重启的时候,会从这个位置继续读,不会重复。这个配置很简单,就是在Vector的source里指定一个cursor_path,给Vector一个有权限的路径存游标文件。

3.2 再搞字段映射:把“隐藏字段”拉出来

核心就是把journald的PRIORITY数字转成对应的level字符串,比如PRIORITY=3是err,PRIORITY=4是warn,PRIORITY=6是info,这样我们过滤的时候直接用level=warn就可以,不用再搞那些数字。这一步可以用Vector的source配置里的字段处理,或者用transform,这里直接在source里配置更方便。

3.3 示例配置:一步到位解决问题

这里我们用Vector的toml配置文件,完整展示从journald采集、映射字段、过滤日志的流程,注释会标清楚每个关键部分:

# Vector核心配置,仅保留本次需要的关键项,其他冗余配置已省略
[sources.journald_source]
# 声明采集类型为systemd-journald,指定从journald拉取日志
type = "journald"
# 游标持久化路径,必须给Vector运行用户(比如vector)配置读写权限,避免游标丢失
cursor_path = "/var/lib/vector/journald_cursor"
# 核心:处理journald原始字段,转成我们需要的结构化字段
fields = [
  # 映射优先级:把journald的PRIORITY数字转成标准日志级别字符串
  { field = "level", source_field = "PRIORITY", type = "string",
    # 严格对应journald的PRIORITY规则:0=紧急、1=告警、2=严重、3=错误、4=警告、5=通知、6=信息、7=调试
    mapping = {
      "0" = "emerg",
      "1" = "alert",
      "2" = "crit",
      "3" = "err",
      "4" = "warn",
      "5" = "notice",
      "6" = "info",
      "7" = "debug"
    }
  },
  # 可选:把进程标识转成服务名字段,方便后续按服务过滤
  { field = "service", source_field = "SYSLOG_IDENTIFIER", type = "string" }
]

# 过滤配置:只保留警告级别以上的日志,屏蔽无用的信息和调试日志
[transforms.filter_logs]
type = "filter"
inputs = ["journald_source"]  # 指定输入为刚才的journald采集源
condition = '.level in ["err", "warn", "crit", "alert", "emerg"]'

# 输出配置:为了测试方便输出到文件,实际生产可改为Loki、Elasticsearch等
[sinks.file_sink]
type = "file"
inputs = ["filter_logs"]
path = "/var/log/vector/filtered.log"
encoding.codec = "json"  # 用JSON格式输出,方便后续分析

四、实战指南:什么时候用?踩坑要注意啥?

4.1 适合的应用场景

这个方案适合中小型服务器集群的日志采集,尤其是用systemd管理服务的环境——比如公司有5-50台Linux服务器,每个服务都用systemd启动,需要把日志集中收集,过滤掉无用的info/debug日志,只留关键告警日志到监控系统,就非常合适。它配置简单,稳定性高,不会太复杂,适配大部分中小团队的运维需求。

4.2 这套方案的优缺点

优点的话,第一是统一了日志级别格式,不管你服务里的日志怎么打,到了Vector这里都转成标准的level字符串,过滤的时候不会出错;第二是游标持久化,绝对不会丢日志或者重复采集;第三是灵活性高,除了级别映射,还可以加其他字段的映射,比如服务名、主机IP这些,方便后续做更细粒度的日志处理。缺点的话,一是需要对Vector的配置有一点点了解,新手刚接触可能要花一点时间查文档;二是如果Vector版本太老,可能journald源的字段映射功能不全,得升级到最新稳定版。

4.3 必须注意的坑

第一个坑:游标路径的权限。如果给的cursor_path路径不对,或者Vector用户没有读写权限,游标就存不了,Vector会从头读日志,重复采集一大堆没用的内容;第二个坑:PRIORITY的映射规则别搞反了,journald的PRIORITY是数字越小越严重,很多新手会搞混,比如把warn对应成3,其实是4,映射错了过滤条件肯定失效;第三个坑:如果用的Vector版本是0.30以下的旧版本,要确认journald源开启了fields扩展,不然有些元数据拿不到,导致映射失败。

五、总结

这次的问题看起来是过滤失效,其实是两个小坑叠加:没有把journald的原始字段转成能用的过滤字段,加上游标没持久化导致重复采集。只要把这两步做对,就能完美解决过滤失效的问题,以后再碰到类似的systemd日志采集问题,从字段映射和游标这两个点排查,基本就能搞定。这套方案经过多个项目验证,稳定可靠,适配大部分systemd环境的日志采集需求,适合不同基础的开发者快速上手。