日志格式化最佳实践:九条规则让日志机器可解、人亦可读

日志格式化决定了日志能否被高效检索与分析。本文讲解非结构化/半结构化/结构化三种日志格式的区别,以及九条生产环境日志格式化实践:JSON 输出、ISO-8601 时间戳、字符串级别、来源信息、版本号、堆栈跟踪、上下文字段规范、关联 ID 与对象字段控制。

最佳实践
日志格式化最佳实践:九条规则让日志机器可解、人亦可读技术指南封面

日志格式化(Log Formatting)是指对日志条目的结构、编码、上下文字段组织方式和分隔规则的标准化设计。 格式差的日志如同大海捞针;格式好的日志则能被自动检索、聚合与告警,是整个日志体系效率的前提。

三种日志格式

1. 非结构化日志:自由文本,开发时好读,规模上来后难以检索:

1687927940843: 应用在 3000 端口启动
数据库备份时发生意外错误

2. 半结构化日志:时间戳、级别、进程号等头部标准化,但消息体仍是自由文本:

2026-08-25 19:09:48 I [969609:60] MyApp -- 应用在 3000 端口启动

3. 结构化日志:整条日志是格式统一的数据对象(通常是 JSON),字段明确、机器可解析:

{"time": "2026-08-25T17:20:19+08:00", "level": "info", "service": "MyApp", "msg": "应用在 3000 端口启动"}

生产环境应统一到结构化;开发环境可切换为彩色易读格式,多数框架支持按环境切换。

核心要点速览

  • 生产日志统一用 JSON 结构化输出
  • 级别用字符串error 而非 50),时间戳用 ISO-8601/UTC
  • 每条日志带来源信息(文件、行号、主机)与构建版本(commit hash);
  • 错误日志必须带堆栈跟踪,最好是结构化堆栈;
  • 上下文字段全公司统一命名,用 trace_id 串联请求;
  • 对自定义对象实现选择性输出,防止敏感字段被意外记录。

九条实践详解

1. 使用结构化 JSON 日志

采用原生支持 JSON 的日志框架(如 Go slog 的 JSONHandler)。中间件也要开:Nginx 可用 log_format ... escape=json 把访问日志从 Combined 格式改为字段清晰的 JSON;PostgreSQL 15+ 设置 log_destination = 'jsonlog'

2. 级别统一用字符串表示

框架内部常用整数表示级别(如 Pino 里 60 表示 fatal),但不同框架的整数约定不同,跨语言环境极易混淆。统一输出字符串级别(infoerror),消除歧义。

观测云落地:Pipeline 会把级别映射为标准 status 字段,字符串命名统一后,查看器的状态着色与按级别筛选才能正常工作 。

3. 时间戳用 ISO-8601 并统一时区

ISO-8601(或其严格子集 RFC 3339)人类可读、精度可达纳秒、可携带时区:

2026-08-25T12:34:56.123456789+08:00

建议全链路统一为 UTC,避免跨服务器对时间线时的时区混乱。

4. 记录日志来源信息

每条日志应能回答"它从哪来":源文件、行号、函数名(多数框架可自动注入);分布式环境下还应包含主机名、容器 ID:

{"level": "DEBUG", "source": {"function": "main.main", "file": "app/main.go", "line": 30}, "msg": "调试信息"}

5. 带上构建版本或 commit hash

代码会重构,函数名和行号会变化——半年前的错误日志可能对不上现在的代码。在日志中加入应用版本号或 Git commit hash(必要时加上运行时版本),就能精确回溯到日志产生时的代码状态:

{"level": "ERROR", "msg": "意外错误", "build": {"version": "v2.3.1", "commit": "9b0695e1"}}

6. 错误日志必须包含堆栈跟踪

堆栈跟踪是定位问题源头的最短路径。优先输出结构化堆栈(错误类型、消息、逐帧文件与行号作为字段),让平台可以按帧聚合分析;退而求其次,完整的字符串堆栈也好过没有。

7. 标准化上下文字段

不要把上下文拼接进消息字符串("用户 " + id + " 登录"),而要以键值对输出:

slog.Info("用户登录", slog.Int("user_id", 42))

框架的字段绑定能力(如 slog 的 With())可以让一组日志自动携带相同字段。全公司统一字段命名规范——用户 ID 固定叫 user_id,不要出现 user/uid/userID 混用;数值字段名带单位(duration_msresponse_bytes)。

观测云落地:统一命名后,查看器中一次字段筛选即可横跨所有服务;名称混乱时则必须为每个变体写 OR 条件 。

8. 用关联 ID 串联请求日志

一个请求会产生多条日志。在请求入口生成唯一 ID 并全程透传,每条日志携带它,就能一键捞出该请求的完整日志链。推荐直接使用 trace_id,在观测云中可进一步实现日志与 APM 链路的互相跳转 。

9. 对自定义对象做选择性输出

为自定义结构体实现日志输出控制方法(如 Go slog 的 LogValuer 接口),明确指定哪些字段允许进入日志:

func (u *User) LogValue() slog.Value {
    return slog.StringValue(u.ID) // 只输出 ID,email/password 永远不会进日志
}

这样即使未来有人给结构体加了敏感字段,也不会被意外泄露——防御性设计的价值在于"默认安全"。

总结

九条规则可归为三类:格式层面(JSON、字符串级别、ISO-8601)、溯源层面(来源、版本、堆栈)、关联层面(字段规范、关联 ID、字段控制)。它们共同决定了日志进入观测云后是"资产"还是"噪音"。

常见问题(FAQ)

Q:开发环境也必须输出 JSON 吗?
不必。开发环境可用彩色易读格式提升阅读体验,生产环境切回 JSON——主流框架都支持按环境变量切换输出格式。

Q:老系统输出的是非结构化日志怎么办?
不需要改代码。DataKit 采集原始文本后由 Pipeline 按规则切割提取字段,存量系统也能融入结构化体系 。

Q:ISO-8601 和 Unix 时间戳选哪个?
日志记录用 ISO-8601(可读、带时区);系统间传输的 metric 可用 Unix 时间戳。日志场景以人和机器的读取便利性为先。


系列阅读:日志最佳实践十二条什么是结构化日志

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台