日志采集与结构化:日志级别设计、JSON结构化日志、日志采集工具接入

日志这东西,说简单也简单,说复杂真能坑死人。我在报价路由系统上线初期,就吃过日志混乱的亏——线上出问题,翻半天日志找不到关键信息,全是些没用的调试信息。后来我痛定思痛,把日志体系彻底重构了一遍。今天就跟大家聊聊,一个靠谱的日志系统该怎么搭。

日志级别设计:别让日志变成噪音

日志级别,说白了就是给日志贴个标签,告诉运维人员「这条日志有多重要」。我见过不少团队,日志级别设计得跟没有一样——要么全是INFO,要么DEBUG满天飞。

我个人习惯,报价路由系统至少分五个级别:

级别 含义 典型场景
FATAL 致命错误,系统无法继续运行 数据库连接池耗尽、核心线程崩溃
ERROR 错误,但系统可继续运行 报价请求超时、外部接口返回异常
WARN 警告,可能存在问题 报价延迟超过阈值、重试次数过多
INFO 正常业务信息 报价请求接收、路由决策完成
DEBUG 调试信息,仅开发环境使用 详细参数、中间计算结果
注意:生产环境千万别开DEBUG级别。我曾经见过一个团队,生产环境开了DEBUG,结果一天产生200GB日志,磁盘直接打满,系统挂了。

这里有个小技巧:动态调整日志级别。线上出问题时,可以通过管理接口临时把某个模块的日志级别降到DEBUG,问题排查完再调回去。这样既不影响性能,又能快速定位问题。

JSON结构化日志:让机器能读懂

传统日志长什么样?大概是这样:

2024-01-15 10:30:22.456 INFO 报价请求处理成功,订单号: ORD123456,金额: 1000.00

这种日志,人眼看看还行,但你想用ELK做分析?想用Grafana做可视化?门都没有。因为机器没法解析这种非结构化的文本。

所以,我强烈建议用JSON结构化日志。同样的信息,写成JSON:

{
  "timestamp": "2024-01-15T10:30:22.456+08:00",
  "level": "INFO",
  "logger": "com.example.quote.QuoteService",
  "message": "报价请求处理成功",
  "orderId": "ORD123456",
  "amount": 1000.00,
  "currency": "CNY",
  "duration": 45,
  "sourceIp": "192.168.1.100"
}

你看,这样一搞,每个字段都是独立的key-value。Elasticsearch可以直接索引,Kibana可以随意聚合查询。想查某个订单的所有日志?orderId: ORD123456 一搜就出来了。

我的经验:JSON日志里一定要包含traceIdspanId。这样你才能把一次报价请求的完整链路串起来——从网关到路由引擎,再到下游银行接口,全链路追踪。

实现JSON日志,在Java里用Logback或Log4j2,配置一个JSON Layout就行。Python的话,用python-json-logger。代码示例:

// Logback配置示例
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.JsonEncoder"/>
</appender>

日志采集工具:Filebeat vs Fluentd

日志写好了,怎么收集到中央存储?这就轮到采集工具上场了。市面上主流的两款:FilebeatFluentd

Filebeat:轻量级选手

Filebeat是Elastic生态的采集器。特点就一个字:。它用Go写的,内存占用极小,适合部署在每台机器上。

我一般在报价路由系统的每个节点上都部署一个Filebeat,配置很简单:

filebeat.inputs:
- type: log
  paths:
    - /var/log/quote-routing/*.json
  json.keys_under_root: true
  json.add_error_key: true

output.elasticsearch:
  hosts: ["http://elasticsearch:9200"]
  index: "quote-routing-%{+yyyy.MM.dd}"

你看,几行配置就搞定了。Filebeat会自动监听日志文件变化,新日志一产生就推送到Elasticsearch。

Fluentd:全能型选手

Fluentd用Ruby写的,功能更强大,但资源消耗也大一些。它最大的优势是插件生态丰富——输入、输出、过滤,各种插件应有尽有。

举个例子,你想在采集时对日志做点处理:

<source>
  @type tail
  path /var/log/quote-routing/*.json
  format json
  tag quote.routing
</source>

<filter quote.routing>
  @type record_transformer
  <record>
    hostname "#{Socket.gethostname}"
    env "production"
  </record>
</filter>

<match quote.routing>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
  logstash_prefix quote-routing
</match>

这个配置里,我加了一个filter,自动给每条日志打上主机名和环境标签。这样在Kibana里就能按主机筛选,排查问题特别方便。

怎么选?

我个人建议:

  • 场景简单(就是采集日志送到ES)→ 用Filebeat,省资源
  • 场景复杂(需要数据转换、路由、缓冲)→ 用Fluentd,灵活
  • 混合使用:Filebeat采集 + Fluentd做聚合处理,这也是我目前在用的方案
避坑指南:我曾经遇到过Filebeat采集JSON日志时,因为日志里某个字段包含特殊字符,导致解析失败。解决办法是在Filebeat配置里加上json.add_error_key: true,这样解析失败的日志会被标记,方便排查。

知识体系总览

下面这张图,把日志采集与结构化的核心逻辑串起来了:

日志采集与结构化核心流程 应用服务 JSON结构化日志 日志采集器 Filebeat / Fluentd Elasticsearch 索引存储 日志级别: FATAL ERROR WARN INFO DEBUG 关键字段: traceId | spanId | orderId | duration | sourceIp 告警联动 ERROR级别日志 → 触发告警 → 通知值班SRE

从这张图能看出来,整个流程就是:应用生成JSON日志 → 采集器收集 → 存储到ES → 触发告警。每一步都环环相扣,缺一不可。

最后说两句

日志这件事,前期设计得好,后期运维能省80%的精力。我见过太多团队,日志随便写写,出问题的时候抓瞎。你想想看,线上报价延迟了,你连是哪个环节慢都不知道,怎么排查?

所以,别嫌麻烦。把日志级别定好,用JSON结构化输出,再配上Filebeat或Fluentd采集。这套体系搭好了,以后排查问题就跟查数据库一样简单。

核心要点回顾:
  • 日志级别分五档,生产环境别开DEBUG
  • JSON结构化日志,必须包含traceId做链路追踪
  • Filebeat轻量,Fluentd灵活,按需选择
  • 采集时做好字段清洗,避免脏数据进ES

专注资料整理