第4章 日志管理:日志规范设计、日志轮转策略、集中式日志收集(ELK/Loki)
日志这东西,平时没人搭理它,一出事它就是唯一的救命稻草。我在做市系统里摸爬滚打这些年,见过太多因为日志没管好,排查问题花了一整夜的惨案。说白了,日志管理不是写写代码就完事,它是一套从规范到存储再到检索的完整体系。
4.1 日志规范设计:别让日志变成噪音
我刚开始带团队时,大家写日志全凭心情。有人用中文,有人用英文,有人干脆不写。结果呢?系统崩了,日志里全是“error”、“failed”,连个上下文都没有。嗯,这里要注意,日志规范必须从第一天就定死。
4.1.1 日志分级:什么级别该打什么日志
我个人习惯用这五个级别,少一个都不行:
| 级别 | 含义 | 使用场景 | 示例 |
|---|---|---|---|
| FATAL | 致命错误 | 系统无法继续运行,必须人工介入 | 数据库连接池耗尽、内存溢出 |
| ERROR | 错误 | 业务逻辑异常,但系统还能跑 | 订单撮合失败、风控校验不通过 |
| WARN | 警告 | 潜在风险,不影响当前功能 | 网络延迟超过阈值、重试次数过多 |
| INFO | 信息 | 关键业务流程节点 | 订单提交成功、持仓更新完成 |
| DEBUG | 调试 | 开发阶段或线上临时排查 | 变量值、函数调用栈 |
4.1.2 日志格式:统一模板,机器可读
你想想看,如果每行日志格式都不一样,ELK怎么解析?正则写到崩溃。我建议用JSON格式,结构清晰,解析方便。
{
"timestamp": "2025-03-15T10:30:00.123+08:00",
"level": "INFO",
"logger": "com.mm.order.OrderService",
"thread": "order-pool-7",
"message": "订单提交成功",
"context": {
"order_id": "ORD202503150001",
"symbol": "BTC/USDT",
"side": "BUY",
"price": 65000.12,
"quantity": 0.5
},
"trace_id": "a1b2c3d4e5f6"
}
这里面有个关键字段——trace_id。做市系统里一个订单可能经过网关、风控、撮合、清算好几个服务,没有trace_id,你根本串不起来。我个人习惯用OpenTelemetry自动生成,省心。
4.2 日志轮转策略:别让日志撑爆磁盘
我记得刚入行时,有一次凌晨三点被电话叫醒,说系统挂了。上去一看,磁盘100%,日志文件已经50GB了。从那以后,日志轮转成了我每次部署必检查的项目。
4.2.1 基于大小的轮转
最常用的策略。每个日志文件达到指定大小就切分,保留最近N个文件。
# logback.xml 配置示例
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/mm/mm.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/mm/mm.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>500MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
</appender>
4.2.2 基于时间的轮转
按天或按小时切分。适合日志量比较稳定的场景。我个人更推荐“大小+时间”组合策略,双保险。
4.2.3 压缩与清理
轮转后的旧日志,建议立即压缩。gzip压缩率通常在10:1左右,能省不少空间。同时要设置自动清理策略,别让压缩文件也堆成山。
# 压缩策略示例
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>500MB</maxFileSize>
</triggeringPolicy>
<!-- 压缩旧日志 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
4.3 集中式日志收集:ELK vs Loki
单机看日志还行,一旦服务超过3个,你就得用集中式方案了。目前主流就两套:ELK和Loki。我两个都用过,各有千秋。
4.3.1 ELK:老牌劲旅,功能全面
ELK = Elasticsearch + Logstash + Kibana。这套组合拳打了十年,生态成熟,文档丰富。
- Elasticsearch:存储+检索,全文搜索能力极强
- Logstash:数据采集+解析,支持各种输入源
- Kibana:可视化+告警,Dashboard做得漂亮
我在项目中遇到过一个问题:Logstash吃内存太狠,动不动就OOM。后来我换成Filebeat采集,Logstash只做解析,才稳住。
# Filebeat 配置示例
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/mm/*.log
json.keys_under_root: true
json.overwrite_keys: true
output.logstash:
hosts: ["logstash.mm.com:5044"]
4.3.2 Loki:轻量新秀,成本更低
Loki是Grafana家的产品,设计理念跟ELK完全不同。它不全文索引,只索引标签,存储成本低得多。
- Loki:存储,基于标签索引,压缩率高
- Promtail:采集端,类似Filebeat
- Grafana:可视化,跟监控面板统一
4.3.3 架构对比
我画了一张图,帮你快速理解两者的核心差异:
4.4 实战:搭建一套日志收集流水线
光说不练假把式。我拿Loki举例,因为它在做市系统里更实用——你想想看,做市系统最怕的就是延迟,Loki资源占用低,不会跟交易进程抢CPU。
4.4.1 部署Promtail
每台机器上部署一个Promtail,采集日志并打上标签。
# promtail.yml
scrape_configs:
- job_name: mm-system
static_configs:
- targets: [localhost]
labels:
job: mm
env: production
__path__: /var/log/mm/*.log
pipeline_stages:
- json:
expressions:
level: level
message: message
trace_id: trace_id
4.4.2 配置Loki存储
Loki支持本地存储和对象存储。生产环境我建议用S3或MinIO,成本低,扩容方便。
# loki.yml
storage_config:
boltdb_shipper:
active_index_directory: /data/loki/index
cache_location: /data/loki/cache
shared_store: s3
aws:
s3: s3://bucket-name/loki/
region: us-east-1
4.4.3 在Grafana中查询
部署完成后,在Grafana里添加Loki数据源,就能用LogQL查询了。
# 查询某个trace_id的所有日志
{job="mm", env="production"} |= "trace_id=a1b2c3d4e5f6"
# 查询最近1小时ERROR级别的日志
{job="mm"} |= "ERROR" | logfmt | __timestamp__ > now() - 1h
4.5 避坑指南:日志管理常见问题
- 日志打太多:我曾经见过一个开发在循环里打DEBUG日志,每秒输出10万行。记住,循环里别打日志,真要打就用采样。
- 时区不统一:所有服务强制使用UTC时间,前端展示时再转本地时间。否则跨时区排查问题,你会疯掉。
- 敏感信息泄露:日志里别打密码、密钥、用户手机号。我见过有人把数据库密码直接打在ERROR日志里,然后日志被ELK索引了...后果你懂的。
- 日志采集延迟:Promtail默认10秒采集一次,如果系统出问题,这10秒可能就是生死线。建议改成1秒。
日志管理这件事,前期多花一小时设计规范,后期能省一百小时排查问题。别等到磁盘爆了、问题查不到了,才想起日志的重要性。