go 的 log 包默认无级别区分,log.fatal 会终止进程,web 服务中应避免使用;推荐用 zap 等结构化日志库,按错误影响程度选择 info/warn/error/fatal 级别,并统一处理 panic 与退出逻辑。

Go 的 log 包默认不区分级别,别直接用 log.Printf
Go 标准库的 log 包本身没有 Error、Warn、Fatal 级别概念,log.Printf 和 log.Fatal 只是输出+退出,不是语义分级。真要区分,得自己封装或换库。
常见错误是:在 HTTP handler 里写 log.Fatal("db failed") —— 服务直接崩了,而不是返回 500 并继续跑。
-
log.Fatal会调用os.Exit(1),整个进程退出,不适合 Web 服务或长期运行程序 -
log.Print/log.Printf没有上下文标识,日志聚合时无法过滤“错误类事件” - 如果只是想加前缀(比如
[ERROR]),可以自定义log.SetPrefix,但无法动态控制输出目标或采样
用 zap 替代标准 log:初始化时选对构造函数
zap 是 Go 生产环境最常用的结构化日志库,它的级别区分靠构造出的 logger 实例类型,不是方法名。
别用 zap.NewExample() 做线上日志 —— 它只输出到 os.Stdout 且无格式、无级别字段;也别在热路径上反复调用 zap.NewDevelopment() 或 zap.NewProduction(),它们开销不小。
立即学习“go语言免费学习笔记(深入)”;
- 开发期用
zap.NewDevelopment():带颜色、行号、时间戳,适合本地调试 - 生产环境用
zap.NewProduction():JSON 输出、自动采样、支持写文件,但需手动配置zap.AddCaller()才能保留调用位置 - 如果只需要轻量级分级(比如没引入
zap),可用log.New配合不同io.Writer(如os.Stderrfor error,ioutil.Discardfor debug)
示例:
logger, _ := zap.NewProduction(zap.AddCaller())<br>logger.Error("db query failed", zap.String("query", q), zap.Error(err))
error 类型本身不带级别,日志级别必须由你决定何时记录
Go 的 error 是值,不是事件。收到一个 err != nil,不代表必须记 Error 级别日志 —— 它可能只是用户输错 ID,该记 Warn;也可能底层磁盘满,该立刻 Fatal 并告警。
关键判断点在于:这个错误是否影响服务可用性?是否需要人工介入?是否可恢复?
- 网络超时、重试后成功的错误 →
Warn,加retry_count字段 - 配置加载失败、监听端口被占 →
Fatal,因为服务无法启动 - 用户传了非法 JSON →
Info或不记录,属于预期边界检查 - 不要把
fmt.Errorf("failed to %s: %w", op, err)直接塞进Error(),先判断原始err是否值得上报
混用 log.Fatal 和 panic 是最隐蔽的运维坑
两者都会终止 goroutine,但行为完全不同:log.Fatal 调用 os.Exit,不走 defer;panic 可被 recover 捕获,且会执行当前 goroutine 的 defer。
在 HTTP handler 或 goroutine 中误用 log.Fatal,会导致连接未关闭、资源未释放、监控指标中断 —— 日志里只看到一行 “exit status 1”,查不出哪条请求触发的。
- Web handler 出错:用
logger.Error+ 返回 HTTP 状态码,绝不用log.Fatal - 主函数启动失败(如 DB 连不上):可用
log.Fatal,但建议统一用logger.Fatal(zap提供)并确保它调用了os.Exit - 第三方库内部 panic?加顶层
recover+logger.Error("panic recovered", zap.String("stack", string(debug.Stack())))
真正难调试的,是那些没打日志就静默 os.Exit 的地方 —— 它们不会出现在任何 trace 里,只能靠进程退出码和系统日志反推。










