日志标准化原则
做日志采集这些年,我最深的体会就一句话:没有标准化的日志,就是个半成品。你想想看,团队十几个人,每个人按自己喜好写日志——有人用下划线,有人用驼峰;时间戳有的写「2024-01-15 14:30」有的写「1705302600」;级别更是五花八门,有的把调试信息标成ERROR……那后期做分析、做告警,简直是灾难。
我刚开始带项目时也踩过这种坑。当时一个微服务架构的系统,二十几个服务,日志格式全凭开发者心情。结果线上出故障,排查链路花了整整两天——因为压根儿没法把同一个请求的日志串起来。那次之后,我在团队里立了条规矩:日志标准化,不进则退。
这套原则说到底就五个核心点。咱们一个一个来看。
字段命名规范
字段名是日志的「脸面」。命名乱,后续所有处理都得跟着绕路。
核心原则就是一致。我个人的习惯是统一用小驼峰(camelCase)或者下划线(snake_case),选一个之后全团队锁死。举个例子:请求耗时字段如果写成request_time,就别再出现reqTime或者requestDuration。
另外注意避免歧义。比如 time 到底是请求时间还是响应时间?干脆写成 request_time 和 response_time。我见过有人把用户ID写成 uid、userId、user_id 三种变体混在同一套系统里,后来做关联分析时脚本写了半天兼容逻辑。
时间格式统一
时间戳乱,是所有日志问题的「万恶之源」。
我的铁律:一律采用 ISO 8601 格式,精确到毫秒,带时区。也就是长这样:
2024-01-15T14:30:22.123+08:00
为什么是 ISO 8601?因为它是人类可读的,又能被机器直接排序、比较,时区信息也嵌在里面,不会出现「北京时间早上8点,但服务器时间是UTC零点」这种乌龙。
我之前接手过一个遗留系统,日志里时间格式竟然有三种:Unix 时间戳(秒级)、YYYY-MM-DD HH:mm:ss(无时区)、还有 MM/DD/YYYY HH:mm:ss 美式写法。做时间轴分析时,我还得写个转换脚本,每次跑都提心吊胆,生怕某个服务的时间戳被认错。所以,统一、带时区、毫秒级,三个缺一不可。
2024-01-15 14:30:22,结果美国机房和上海机房的时间差了 13 个小时,查问题查到怀疑人生。后来我们强制所有日志输出时都调用一个公共时间工具,统一加上时区后缀。
日志级别定义
级别是日志的「红绿灯」,告诉你当前信息的紧急程度。但很多团队对级别的理解是模糊的。
标准化的做法:遵循业界通行的五个级别,给每个级别下明确的定义:
| 级别 | 含义 | 典型场景 | 处理建议 |
|---|---|---|---|
| DEBUG | 调试信息,开发阶段使用 | 变量值、函数入口出口 | 生产环境默认关闭 |
| INFO | 常规运行信息 | 请求开始/结束、服务启动 | 正常情况下保留 |
| WARN | 潜在问题,不影响当前流程 | 配置降级、重试次数超阈值 | 需要关注,但不必立即响应 |
| ERROR | 错误,但服务仍可继续 | 数据库连接失败、外部接口超时 | 应触发告警,排查原因 |
| FATAL | 致命错误,服务无法恢复 | 内存溢出、关键进程崩溃 | 立即触发告警,并拉起告警群 |
分清级别。我见过有些开发把「参数校验失败」这种业务异常也打 ERROR,结果告警平台每天几千条,大家直接免疫了。真正的 ERROR 应该是那些你不知道为什么会发生、发生后需要人工介入的情况。
我自己的经验:最开始先定一个简单的映射表,把每个业务场景对应到哪个级别写出来。比如「数据库操作失败并重试3次仍失败」→ ERROR,「重试第1次失败」→ WARN。这样大家有据可依,就不会乱打级别了。
唯一标识符
分布式系统里,一个用户请求可能要穿过几十个微服务。如果没有一个全局 ID 把它们串起来,排查链路就跟大海捞针一样。
唯一标识符(通常叫 trace_id、request_id 或 correlation_id)就是为了解决这个问题的。我建议每个服务在接收到请求的最外层生成一个全局唯一的字符串(比如 UUID 或 Snowflake ID),然后通过 HTTP 头或消息元数据向下游传递,同时写入每条日志中。这样一来,你只要拿着这个 ID,就能把所有关联的日志全部拉出来。
另外还要注意容错。如果上游服务没有传 ID 过来怎么办?我建议在本地生成一个 fallback ID,同时用额外字段标记 trace_id_source: "local_generated",避免链路断掉。我在一个老系统里就遇到过上游服务升级滞后,没有透传 ID,后悔当初没加这个 fallback 逻辑。
结构化与半结构化
日志的结构决定了你能多「快」、多「准」地提取信息。
结构化日志指的是将日志内容组织成固定的字段结构,比如 JSON、Key-Value 对或者 Protobuf。与之相对的,是纯文本日志(比如那行字「用户张三登录成功」)。
我的原则:能结构化就结构化,实在不行才用半结构化。
- JSON 格式:最推荐。可读性好,解析方便,大多数日志采集工具(如 Filebeat、Fluentd)原生支持。每条日志一个 JSON 对象,字段清晰。
- Key-Value 对:比如
key1=value1 key2=value2。适合简单日志,但字段值如果包含空格需要用引号包裹,容易踩坑。 - Syslog 格式:历史遗产,按固定结构(优先级、时间戳、主机名、消息)排列,扩展性较差,适合老系统迁移过渡。
- 纯文本:最差选择,除非是极小的玩具项目,否则不建议用。因为后期你要写一堆正则去解析,维护成本极高。
我自己在项目里一直用 JSON。举个例子:
{
"timestamp": "2024-01-15T14:30:22.123+08:00",
"level": "ERROR",
"trace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"service": "order-service",
"method": "createOrder",
"message": "库存扣减失败,商品ID=1024,可用库存=5,请求数量=10",
"duration_ms": 234
}
你看,所有关键信息都在一个结构体里,采集端直接解析 JSON,根本不用自己写解析器。更重要的是,你可以在 ES 里直接按字段做聚合、过滤、排序,非常灵活。
如果你因为历史原因必须以半结构化方式输出(比如老系统只支持一行文本),那就尽量用固定的分隔符和固定的字段顺序,配合简单的正则也能处理。但说真的,能上结构化就上结构化,别犹豫。
最后,把五个原则串起来,你就可以得到一份标准化的日志了。下面这张图可以帮你快速回顾它们之间的关系:
好了,日志标准化的五个核心原则就说到这里。你可以在实际项目里一条一条对照着做,先把规范定下来,再去落地工具和流程。相信我,把这些基础打牢了,后面做采集、清洗、分析都会顺畅很多。