日志能不能查得快、查得准,一半取决于采集端解析。这篇讲 JSON、正则、多行堆栈三种日志的解析配置、时间与时区处理,以及索引字段怎么选才不拖慢写入。
日志接入之后能不能查得快、查得准,一半取决于采集端的解析规则。原始日志只是一串文本,只有抽成结构化字段,才能用类 SQL 语法做过滤、聚合和告警。这篇讲三种最常见日志的解析配置:JSON、正则、多行堆栈,再加上时间与时区处理、索引字段选择。
如果你的应用本来就输出 JSON(比如用 Logback 的 LogstashEncoder、Go 的 zap JSON encoder),采集端要做的第一件事是原样解析,而不是把它压成一个 message 字段。配置里指定 format: json,采集器会把每个 key 自动抽成字段:
parser:
format: json
time_key: timestamp
这样 level、service、trace_id 都直接成为可检索字段。常见错误是应用端又套了一层纯文本,把 JSON 对象当字符串写进 message,等于把结构白白丢掉。改造成本很低:把日志框架的输出格式从 PatternLayout 换成 JSON encoder 即可,其余代码不用动。如果 JSON 里有嵌套对象(比如 request.headers),默认会保留为子对象,检索内层字段要用点号路径;需要平铺的字段可以配 flatten 规则,避免查不到内层内容。
老系统输出往往是固定格式的纯文本,比如:
2026-08-24 10:11:12.345 [order] ERROR PaymentTimeout userId=10086 orderId=778899
这种用命名捕获组拆:
^(?<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[(?<service>\w+)\] (?<level>\w+) (?<msg>.+)$
拆出来的字段还要做类型转换——time 转时间戳、userId 转整数——检索和排序才有意义。正则的坑在性能:捕获组太多会拖慢采集,只在必要字段上用捕获组,其余用非捕获组。写完之后先拿一小批历史日志回放验证,确认匹配率和抽取结果,再上生产。
Java 异常堆栈、Python traceback 会跨多行。如果不做合并,每一行都会被当成独立日志,检索时堆栈被拆碎。配置首行匹配规则,把续行归并到同一条:
parser:
multiline:
firstline: '^\d{4}-\d{2}-\d{2}'
这样一条异常连同堆栈会作为一个整体存储,点开才能看到完整上下文。
日志的时间戳是所有检索和排序的基准。解析时注意三点:一是 time_key 要指到真正的时间字段,别用采集时间冒充业务时间,否则排查延迟问题时会失真;二是时区要显式声明,多时区服务统一转成 UTC 存储、展示时再转本地;三是时间格式写死,自动推断格式在大流量下会拖慢采集,也容易猜错。另外采集端到存储端的时钟偏移要控制在一秒以内,否则跨服务日志按时间排序会出现错乱。
字段命名最好在采集端统一:level 别有的叫 severity、有的叫 log_level,否则检索时要到处猜字段名。可以在解析规则里加 rename,把历史系统的字段名收敛到统一规范。最后列几条最常见的坑:JSON 里嵌套对象没展开导致查不到内层字段;正则没加锚点导致误匹配;多行规则没覆盖所有日志来源。逐条排查就能避免绝大多数"日志进去了但查不到"的问题。
不是所有字段都该建索引。索引字段越多,写入越慢、存储越大。建议只索引高频检索字段:level、service、trace_id、request_id、userId。像 message 这类全文检索字段走倒排索引,其余字段保持可读但不建索引。索引策略定了之后,配合类 SQL 检索,百万级日志也能秒级返回。
最后给一条验收标准:挑一次真实的线上排障,看你能不能全程只用结构化字段(不用纯文本搜索)就定位到问题。能做到,说明解析和索引配置到位了。