newproduction()是全链路日志起点,因其默认禁用caller与debug、启用紧凑json、纳秒时间戳及内置采样,专为elk/loki解析、高并发低延迟设计;误用newdevelopment()会导致日志膨胀3–5倍、解析失败、颜色符污染json流。

生产环境直接用 zap.NewProduction(),别自己拼 JSON encoder;全链路日志不是靠“加字段”堆出来的,而是靠请求生命周期内复用带上下文的 logger 实例。
为什么 NewProduction() 是全链路日志的起点,而不是 Development
zap.NewProduction() 默认禁用 caller、禁用 debug 级别堆栈、启用紧凑 JSON、使用纳秒时间戳,并内置采样——这些不是“省事开关”,而是为 ELK/Loki 解析、高并发写入、低延迟响应做的硬性约束。你如果用 zap.NewDevelopment() 上线,日志体积会膨胀 3–5 倍,caller 字段在 Kibana 里解析失败率高,且 console encoder 的颜色控制符会污染 JSON 流。
- 开发调试时可临时切到
NewDevelopment(),但压测或上线前必须切回NewProduction() -
NewDevelopmentConfig().Build()和NewDevelopment()底层都是 console encoder,只是多一层配置壳,同样不适用于结构化采集 - 时间戳格式不用改:纳秒级
ts字段对 PromQL 聚合和 trace 关联更友好,强行转成 ISO-8601 反而增加 encoder 开销
HTTP 请求中注入 trace_id 和 request_id 的正确姿势
Zap 不解析 HTTP header,也不绑定 context,所有上下文字段必须显式提取并挂载到单次请求的 logger 实例上。全局 logger 注入会导致并发污染,中间件里每次调用 logger.With() 创建新实例则抬高 allocs/op。
- 中间件中先取 header:
tid := c.GetHeader("X-Trace-ID"),为空则生成uuid.New().String() - 构造子 logger:
reqLogger := logger.With(zap.String("trace_id", tid), zap.String("request_id", c.Request.Header.Get("X-Request-ID"))) - 存入 Gin context:
c.Set("logger", reqLogger),下游用c.MustGet("logger").(*zap.Logger)取出 - 禁止写法:
logger.With(...).Info("msg")—— 每次都新建 logger,字段复制开销不可忽略
文件输出 + 轮转必须绕过 os.OpenFile,改用 lumberjack
lumberjack.Logger 不是“即插即用”的轮转器,它只是个带切割逻辑的 io.WriteCloser。Zap 写进去前必须满足三个硬性条件,否则静默失败、日志卡死。
- 启动前创建目录:
os.MkdirAll("/var/log/myapp", 0755),否则lumberjack不报错也不写日志 -
MaxSize单位是 MB(不是 KB 或字节),设100就是 100MB;MaxBackups: 7和MaxAge: 7 * 24 * time.Hour才有效 - 必须用
zapcore.AddSync封装:zapcore.AddSync(&lumberjack.Logger{...}),不能直接传*os.File - 注意权限:若用 systemd 启动服务,确保
RuntimeDirectoryMode=0755且用户有写入/var/log/myapp的权限
双写控制台 + 文件且格式不同,必须用 NewTee() 而非 NewMultiCore
zapcore.NewMultiCore 是并发写入同一份字段,它不处理格式差异;你要的是“控制台人类可读、文件机器可解析”,这需要两条独立的 encoding pipeline。
- 控制台 Core 用
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) - 文件 Core 用
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) - 组合用
zapcore.NewTee(consoleCore, fileCore),不是NewMultiCore - 别试图用一个 encoder 同时输出两种格式——JSON 里混 color code 会破坏结构化解析,console encoder 输出的 key-value 无法被 Loki 正确提取
真正难的不是加字段,是让每条日志在进入 Zap 前就完成上下文绑定,且不触发额外分配;最容易被忽略的是 lumberjack 目录权限和 MaxSize 单位,这两个点一错,日志就停写,还查不出原因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











