3、日志采集与聚合:日志规范与ELK/EFK技术栈搭建

日志这东西,说简单也简单,说复杂真能坑死人。我在早期做交易系统的时候,吃过不少日志的亏。有一次线上出了个诡异的问题,交易订单莫名其妙丢失了几笔,排查了整整一个通宵,最后发现——日志格式不统一,采集端直接把关键字段截断了。嗯,从那以后,我对日志规范这件事,就特别较真。

3.1 日志规范:结构化日志与日志级别

先聊聊日志规范。很多团队觉得日志就是随便打打,出问题再看。我告诉你,这种想法在电子交易系统里是致命的。交易系统每秒处理成千上万笔订单,日志量巨大,如果没有规范,你根本没法快速定位问题。

3.1.1 结构化日志

什么叫结构化日志?说白了,就是每条日志都是一个结构化的数据对象,而不是一段随意拼接的字符串。我个人习惯用JSON格式,因为解析方便,字段清晰。

举个例子,非结构化的日志可能是这样的:

2024-01-15 10:30:22 ERROR 订单处理失败,订单号: ORD20240115001,原因: 余额不足

而结构化的日志应该是这样的:

{
  "timestamp": "2024-01-15T10:30:22.123Z",
  "level": "ERROR",
  "service": "order-service",
  "trace_id": "abc123def456",
  "order_id": "ORD20240115001",
  "error_code": "INSUFFICIENT_BALANCE",
  "message": "订单处理失败",
  "duration_ms": 235
}

你想想看,第二种格式,Elasticsearch直接就能索引,Kibana里随便搜。第一种格式,你还得写正则去解析,费时费力还容易出错。

核心字段建议:

  • timestamp:ISO 8601格式,带时区
  • level:日志级别
  • service:服务名称
  • trace_id:链路追踪ID
  • error_code:错误码(如果有)
  • duration_ms:耗时(毫秒)

3.1.2 日志级别规范

日志级别这块,我见过太多乱用的。有人把所有信息都打INFO,有人把业务异常打WARN,真正出问题的时候根本分不清轻重。

我的建议是这样的:

级别 使用场景 示例
FATAL 系统级故障,无法自动恢复 数据库连接池耗尽、磁盘空间不足
ERROR 业务处理失败,需要人工介入 订单扣款失败、风控拒绝
WARN 异常情况但系统可自动恢复 重试第3次成功、接口响应超时
INFO 关键业务流程节点 订单创建成功、支付回调到达
DEBUG 开发调试用,生产环境关闭 SQL语句、中间变量值

注意:生产环境千万不要开DEBUG级别,否则日志量会暴涨,磁盘很快写满。我曾经有个项目,上线前忘了关DEBUG,半小时就把100GB的磁盘写爆了……

3.2 ELK/EFK技术栈搭建

ELK大家都熟,Elasticsearch、Logstash、Kibana。EFK就是把Logstash换成了Fluentd。我个人更倾向于EFK,因为Fluentd资源占用更小,配置也更灵活。不过今天咱们重点讲ELK,毕竟它更通用。

3.2.1 架构图

先画个图,让大家对整体架构有个直观认识:

应用服务器 Java/Python/Go Filebeat Logstash 解析/过滤/转换 Elasticsearch 存储/索引/搜索 Kibana ELK技术栈架构图

3.2.2 搭建步骤

搭建ELK其实不复杂,但有几个坑要注意。我直接给出一套经过验证的部署方案。

第一步:安装Elasticsearch

# 使用Docker Compose部署
version: '3'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms4g -Xmx4g
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data

volumes:
  es_data:

小提示:生产环境建议至少3个节点,并且给ES分配的内存不要超过物理内存的50%,否则容易触发JVM的GC问题。

第二步:安装Logstash

logstash:
  image: docker.elastic.co/logstash/logstash:8.11.0
  ports:
    - "5044:5044"
  volumes:
    - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf

第三步:安装Kibana

kibana:
  image: docker.elastic.co/kibana/kibana:8.11.0
  ports:
    - "5601:5601"
  environment:
    - ELASTICSEARCH_HOSTS=http://elasticsearch:9200

3.3 日志采集工具Filebeat/Logstash配置实战

这里我重点讲Filebeat,因为它是轻量级的采集器,部署在每台应用服务器上,几乎不占资源。Logstash则负责集中处理。

3.3.1 Filebeat配置

# filebeat.yml
filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /var/log/app/*.log
  multiline:
    pattern: '^\d{4}-\d{2}-\d{2}'
    negate: true
    match: after
  fields:
    service: order-service
    env: production

output.logstash:
  hosts: ["logstash:5044"]

这里有个关键点——multiline配置。Java的异常栈是跨多行的,如果不做多行合并,一条异常会被拆成几十条日志,根本没法看。我当初就踩过这个坑,排查问题时发现异常栈被拆得七零八落,气得我直接加了这个配置。

3.3.2 Logstash配置

# logstash.conf
input {
  beats {
    port => 5044
  }
}

filter {
  # 解析JSON日志
  json {
    source => "message"
    target => "parsed"
  }
  
  # 添加时间戳
  date {
    match => ["parsed.timestamp", "ISO8601"]
    target => "@timestamp"
  }
  
  # 移除原始message
  mutate {
    remove_field => ["message"]
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "app-logs-%{+YYYY.MM.dd}"
  }
}

注意:如果日志不是JSON格式,记得先用grok插件解析。但说实话,我强烈建议你直接用结构化日志,省去解析的麻烦。

3.4 日志索引生命周期管理

日志索引生命周期管理(ILM),说白了就是让ES自动管理索引的创建、滚动和删除。交易系统的日志量很大,一天可能几十GB,如果不做ILM,磁盘很快会爆。

3.4.1 ILM策略配置

PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "1d"
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "shrink": {
            "number_of_shards": 1
          },
          "forcemerge": {
            "max_num_segments": 1
          }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "freeze": {}
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

这个策略的意思是:

  • Hot阶段:当前写入的索引,超过50GB或1天就滚动
  • Warm阶段:7天后的索引,合并段、减少分片,节省存储
  • Cold阶段:30天后的索引,冻结,几乎不占资源
  • Delete阶段:90天后的索引,直接删除

嗯,这里要注意,交易系统的日志保留时间,建议至少90天,因为监管审计需要。但如果你磁盘有限,可以缩短到30天,不过要做好备份。

3.4.2 索引模板配置

PUT _index_template/logs_template
{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs_policy",
      "index.lifecycle.rollover_alias": "app-logs"
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "level": { "type": "keyword" },
        "service": { "type": "keyword" },
        "trace_id": { "type": "keyword" },
        "message": { "type": "text" }
      }
    }
  }
}

总结一下:日志规范是基础,ELK是工具,ILM是保障。这三者缺一不可。我见过太多团队只搭了ELK,没做规范也没做ILM,结果三个月后磁盘爆了,日志全丢。别让这种事发生在你身上。

好了,这一章的内容就到这里。日志采集与聚合这块,说白了就是三个字——规范、采集、管理。把这三件事做好,你的监控体系就有了最坚实的基础。


公众号:蓝海资料掘金营,微信deep3321