2. 日志格式规范:通用格式、JSON结构化与日志级别

日志格式这事,看着简单,但坑特别多。我见过太多团队一开始随便写几行日志,等到要排查线上问题时,才发现格式乱七八糟,grep 都救不了你。说白了,日志格式就是你和系统之间的「共同语言」,语言不通,啥都查不出来。

这一节我们聊聊三种主流格式:Apache/Nginx 的通用日志格式、JSON 结构化日志,以及日志级别怎么定。嗯,都是实战中绕不开的东西。

2.1 通用日志格式(Apache/Nginx)

先说说最经典的通用日志格式。Apache 和 Nginx 默认的日志格式,业内叫 Combined Log Format(CLF)。你肯定见过:

192.168.1.10 - - [10/Oct/2023:13:55:36 +0800] "GET /api/users HTTP/1.1" 200 2326 "https://example.com" "Mozilla/5.0"

这个格式包含:客户端 IP、时间戳、请求方法+路径+协议、状态码、响应大小、Referer、User-Agent。我在项目中遇到过,直接用 Nginx 默认格式上线,结果发现时间戳没有时区信息,跨地域排查时对时间对到崩溃。

我的建议:Nginx 里用 log_format 自定义一下,把时区、响应时间、上游地址都加进去。别偷懒用默认。

Nginx 自定义示例:

log_format extended '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_addr';

access_log /var/log/nginx/access.log extended;

通用格式的优点是人眼可读,但缺点也很明显——机器解析效率低。你想想看,用 awk 切字段时,如果某个字段里多了个空格,整行就废了。所以,我一般只在开发环境或临时调试时用这种格式。

2.2 JSON 结构化日志

到了生产环境,我个人强烈推荐 JSON 结构化日志。为什么?因为机器解析友好,字段可扩展,而且跟日志收集系统(ELK、Loki)天然契合。

JSON 日志长这样:

{
  "timestamp": "2023-10-10T13:55:36.123+08:00",
  "level": "INFO",
  "logger": "com.example.UserController",
  "thread": "http-nio-8080-exec-3",
  "message": "用户查询成功",
  "context": {
    "userId": "10086",
    "requestId": "a1b2c3d4",
    "duration": 45
  }
}

你看,每个字段都有明确的含义,而且可以嵌套。我曾经接手过一个老项目,日志全是字符串拼接,查一次接口调用链要翻 5 个文件。后来改成 JSON 格式,配合 Elasticsearch,点几下就定位到问题。

关键字段建议:
  • timestamp:ISO 8601 格式,带时区
  • level:日志级别
  • logger:来源类或模块名
  • traceId:链路追踪 ID(后面章节会细讲)
  • message:人类可读的描述
  • context:业务上下文(用户ID、订单号等)

Nginx 输出 JSON 日志也不难:

log_format json_combined escape=json
  '{'
    '"timestamp":"$time_iso8601",'
    '"remote_addr":"$remote_addr",'
    '"request":"$request",'
    '"status":$status,'
    '"body_bytes_sent":$body_bytes_sent,'
    '"request_time":$request_time,'
    '"upstream_addr":"$upstream_addr",'
    '"referer":"$http_referer",'
    '"user_agent":"$http_user_agent"'
  '}';

access_log /var/log/nginx/access.log json_combined;
注意:JSON 日志里不要出现换行符。如果 message 字段包含多行文本,记得转义或替换。否则你的日志收集器会直接报错。

2.3 日志级别定义

日志级别这事,看起来就是 TRACE、DEBUG、INFO、WARN、ERROR 五个词,但实际用起来,每个团队的理解都不一样。我见过有人把 INFO 当 DEBUG 用,一天刷几个 G 的日志;也见过有人只在 ERROR 里打日志,出了问题啥线索都没有。

下面是我个人在项目中沉淀下来的级别定义,供你参考:

级别 含义 使用场景 生产环境是否开启
TRACE 细粒度追踪 调试特定代码路径、方法入参出参 否(除非临时排查)
DEBUG 调试信息 开发环境排查逻辑、变量值 否
INFO 关键业务事件 请求开始/结束、用户操作、定时任务执行 是
WARN 潜在问题 配置降级、重试、限流触发、非关键异常 是
ERROR 错误事件 数据库连接失败、接口返回 500、业务异常 是

这里有个容易踩的坑:INFO 和 DEBUG 的边界。我曾经在一个项目中,把每次 SQL 查询都打成了 INFO,结果每天日志量 50GB,磁盘直接爆了。后来我定了个规则:INFO 只记录业务层面的「事件」,比如「用户下单成功」;DEBUG 才记录技术细节,比如「SQL 执行耗时 23ms」。

避坑指南:我曾经在线上临时开启 DEBUG 级别排查问题,结果忘了改回去,第二天运维找我喝茶。建议在代码里加个动态开关,通过配置中心控制日志级别,别硬编码。

2.4 本章知识体系图

下面这张图把日志格式规范的核心脉络梳理了一下,你可以对照着理解:

日志格式规范知识体系 日志格式规范 通用日志格式 JSON结构化日志 日志级别定义 Apache CLF Nginx 自定义 字段定义 Nginx JSON TRACE/DEBUG/INFO/WARN/ERROR 核心原则:生产环境用 JSON 结构化日志,INFO 及以上级别开启 通用格式仅用于开发调试,避免直接用于生产日志收集

2.5 小结

日志格式这事,选对了事半功倍,选错了天天填坑。我个人建议:

  • 开发环境:用通用格式,方便人眼看
  • 测试/预发环境:用 JSON 格式,开始对接日志系统
  • 生产环境:必须 JSON 结构化,级别控制在 INFO 及以上