3. 日志格式设计:结构化日志 vs 非结构化日志
日志格式这事儿,我见过太多团队踩坑了。刚开始做量化系统时,大家图省事,直接 print 一堆字符串完事。等到系统出问题,几十个 G 的日志文件摆在那,grep 都救不了你。说白了,日志格式选错了,后面监控、告警、排障全得跟着遭殃。
3.1 非结构化日志:简单但致命
非结构化日志,就是纯文本字符串。比如:
2024-01-15 10:30:45 ERROR 订单执行失败,订单ID: ord_123456,原因: 资金不足
这种日志看着挺清楚对吧?但你想过没有,如果我要统计今天所有「资金不足」的错误有多少条,怎么办?用 grep 硬搜?那如果错误信息里带了换行符呢?如果时间格式变了呢?
我在项目中遇到过最头疼的事:某天凌晨系统报警,说订单延迟严重。我打开日志一看,好家伙,同一个字段在不同模块里格式都不一样——有的用「订单ID:」,有的用「order_id=」,还有的直接写「ord_123456」。排查问题花了整整两个小时,最后发现是日志格式不统一导致的。
3.2 结构化日志:量化系统的标配
结构化日志,说白了就是把日志变成机器能读懂的结构。最常用的就是 JSON 格式。你想想看,如果日志长这样:
{
"timestamp": "2024-01-15T10:30:45.123+08:00",
"level": "ERROR",
"logger": "OrderExecutionService",
"thread": "executor-3",
"message": "订单执行失败",
"order_id": "ord_123456",
"reason": "资金不足",
"account_id": "acc_789012",
"duration_ms": 2345,
"extra": {
"symbol": "BTC/USDT",
"side": "BUY",
"price": 42000.50,
"quantity": 0.1
}
}
看到区别了吗?每个字段都是独立的 key-value。你想查什么,直接按字段过滤就行。Elasticsearch、ClickHouse 这些工具天生就支持 JSON 查询,效率比 grep 高几个数量级。
3.3 JSON 格式日志设计实战
我个人习惯把日志字段分成三层:
| 层级 | 字段 | 说明 | 示例 |
|---|---|---|---|
| 基础层 | timestamp, level, logger, thread, message | 所有日志必须包含 | 用于基础过滤和排序 |
| 业务层 | order_id, account_id, symbol, side 等 | 按业务场景添加 | 用于业务链路追踪 |
| 性能层 | duration_ms, cpu_usage, memory_mb | 性能监控专用 | 用于性能分析 |
这里有个细节要注意:message 字段只放人类可读的摘要信息,所有结构化数据都放到 extra 或 context 字段里。为什么?因为日志采集系统(比如 Filebeat、Logstash)在处理 JSON 时,如果 message 里嵌套了 JSON 字符串,解析起来会非常麻烦。
// 错误做法:把结构化数据塞进 message
"message": "订单执行失败, order_id=ord_123456, reason=资金不足"
// 正确做法:message 只放摘要,详情放 extra
"message": "订单执行失败",
"extra": {
"order_id": "ord_123456",
"reason": "资金不足"
}
3.4 时间戳与时区处理:最容易翻车的地方
时间戳处理,我敢说这是量化系统日志里最容易出 bug 的地方。为什么?因为量化系统通常跨时区运行——服务器可能在香港(UTC+8),数据库在美国(UTC-5),交易所在伦敦(UTC+0)。
我曾经遇到过一个经典问题:某天凌晨 2 点,系统突然报错说订单超时。我查日志发现,订单创建时间是 2024-01-15 02:00:00,超时时间是 2024-01-15 02:00:05。看起来没问题啊?后来才发现,创建时间用的是 UTC+8,超时时间用的是 UTC+0,两个时间差了 8 个小时!
推荐的时间戳格式:
// ISO 8601 标准格式,带毫秒和时区
2024-01-15T02:30:45.123Z // UTC 时间,Z 表示零时区
2024-01-15T10:30:45.123+08:00 // 东八区时间
// 或者用 Unix 时间戳(毫秒级)
1705271445123 // 从 1970-01-01 UTC 开始的毫秒数
我个人更推荐 ISO 8601 格式。为什么?因为人类可读,而且各种日志系统都原生支持。Unix 时间戳虽然计算方便,但排查问题时你总得先转换一下才能看懂。
3.5 时区处理的实战方案
在代码里怎么处理?我建议统一用 UTC 时间生成日志,然后在展示层做时区转换。比如:
// Python 示例
import datetime
import pytz
def get_log_timestamp():
"""统一使用 UTC 时间"""
utc_now = datetime.datetime.now(pytz.UTC)
return utc_now.isoformat() # 输出: 2024-01-15T02:30:45.123456+00:00
// Java 示例(使用 Logback)
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<timeZone>UTC</timeZone>
<includeContext>false</includeContext>
</encoder>
</appender>
3.6 知识体系总览
下面这张图总结了日志格式设计的核心要点。你可以把它当作一个检查清单,每次设计日志格式时对照着看一遍:
嗯,到这里日志格式设计这部分就差不多了。记住一句话:日志是量化系统的「黑匣子」,格式设计得越好,出问题时恢复得越快。别等到系统崩了才后悔当初没好好设计日志格式。
公众号:蓝海资料掘金营,微信deep3321