log.Printf 不显示调用栈因不自动捕获堆栈,需用 runtime.Caller 或 zap.Error();zap.Error() 支持错误展开与 nil 安全,优于直接 err.Error();log.Fatal 会终止进程,HTTP handler 中禁用;敏感字段必须脱敏,日志重在可追溯而非全量输出。

用 log.Printf 记录错误时,为什么看不到调用栈?
因为 log.Printf 只做格式化输出,不自动捕获堆栈。你传入的 err 本身(比如 errors.New("xxx"))通常不含调用信息。要看到哪一行出的错,得手动加 runtime.Caller,或者换支持 trace 的日志库。
临时调试可以这样补:
import "runtime"
func logError(err error) {
_, file, line, _ := runtime.Caller(1)
log.Printf("ERROR [%s:%d] %v", file, line, err)
}
- 别在生产环境高频调用
runtime.Caller,有性能开销 -
Caller(1)表示跳过当前函数,取调用方的位置 -
标准库
log没有内置 error 字段结构化能力,err.Error()是唯一能安全取的字符串
用 zap 记录错误时,zap.Error() 和直接 zap.String("error", err.Error()) 有什么区别?
zap.Error() 不只是把 err.Error() 存成字符串——它会尝试展开实现了 fmt.Formatter 或 stackTracer 接口的错误(比如 github.com/pkg/errors 包 wrap 的错误),并保留原始类型信息,方便后期结构化过滤或高亮显示。
推荐写法:
立即学习“go语言免费学习笔记(深入)”;
漂亮的企业网站。NET2.0出来了, 本次升级修改如下: 1、优化了3层结构。 2、优化了后台管理代码,增强了安全性能。 3、增加了系统名称及关键字管理。 4、增加了系统错误日志记录,自动生成Systemlog.log日志文件。 备注:本系统采用ASP.NET 2.O+ACCESS开发,请调试的朋友安装.NET2.0运行环境! 网站内容 网站栏目包括 首页|企业简介|新闻中心|产品展示|公司展示|
logger.Error("db query failed",
zap.String("query", sql),
zap.Error(err), // ← 这里传 err 本体,不是 err.Error()
zap.String("user_id", userID),
)
- 如果
err是nil,zap.Error()会自动写成"error": null,不会 panic - 自己用
zap.String("error", err.Error())会丢失堆栈、丢掉类型语义,且err为nil时会 panic - 注意:原生
errors.New和fmt.Errorf(Go 1.13+)不带堆栈,需用fmt.Errorf("xxx: %w", err)配合errors.Is/As才能链式追踪
为什么 log.Fatal 不适合记录 HTTP handler 中的错误?
log.Fatal 底层调用 os.Exit(1),会导致整个进程退出。在 HTTP server 里一旦某个请求出错就 kill 掉服务,显然不可接受。
- handler 中该用
log.Printf或结构化 logger 记录,然后返回http.Error或自定义响应 -
log.Panic同样危险:触发 panic 后若没被recover,照样崩进程 - 真正该用
log.Fatal的场景极少,一般是 main 函数里初始化失败(如无法监听端口、加载配置失败)
错误日志中要不要打印敏感字段(如密码、token、用户手机号)?
不要。哪怕是在 debug 环境,也不该让明文敏感数据落到磁盘日志文件里。zap 提供 zap.String("password", "[redacted]") 这种显式脱敏,但更稳妥的是从源头控制:
- 记录前用
strings.ReplaceAll或正则擦除已知敏感键值(如"auth_token"、"card_number") - HTTP 请求体、数据库查询参数等,统一走中间件或 wrapper 做字段过滤,而不是靠日志函数临时判断
- 使用
zap.Object+ 自定义LogObjectMarshaler接口,对 struct 字段做选择性序列化
日志不是 dump,是线索。留够定位信息就行,多一条密码反而增加泄露风险。









