fmt.printf("%+v", err) 不输出堆栈是因为 errors.new 创建的 error 未记录调用位置;只有 pkg/errors 或 go 1.20+ fmt.errorf(..., %w) 主动调用 runtime.callers 才支持 %+v 展开堆栈。

为什么 fmt.Printf("%+v", err) 没输出堆栈?
因为标准库 errors.New 创建的 error 不记录调用位置,%+v 对它无效——它压根没存堆栈帧。只有像 github.com/pkg/errors 或 Go 1.20+ 的 fmt.Errorf(..., %w) 在构造时主动调用 runtime.Callers,才能让 %+v 展开堆栈。
常见踩坑点:
- 混用
errors.New("xxx")和pkg/errors.Wrap(err, "msg"):前者无堆栈,包装后也只从Wrap调用处开始记,丢失上游位置 - 写成
return errors.New(err.Error()):原始 error 的堆栈被丢弃,新 error 是空堆栈 - 日志里用
log.Print(err)或fmt.Sprintf("%v", err):只触发Error()方法,%+v根本不生效
如何在 HTTP handler 入口统一捕获并打印完整堆栈
不要等错误层层返回再处理,而是在请求入口用 defer/recover + runtime.Stack 捕获 panic,同时对返回的 error 用 pkg/errors.WithStack 包一层。
实操建议:
- handler 开头加
defer func() { if r := recover(); r != nil { buf := make([]byte, 4096); runtime.Stack(buf, false); log.Printf("PANIC: %s\n%s", r, buf) } }() - 所有业务函数返回 error 前,用
return errors.WithStack(err)(注意:仅对非 nil error 调用) - HTTP 错误响应体里避免直接暴露堆栈,但服务端日志必须用
fmt.Printf("err: %+v\n", err) - 若用
zap,传入zap.Error(err)即可自动提取堆栈字段(需配合pkg/errors或自定义 error 类型)
不用第三方库时怎么手动加堆栈
Go 标准库不提供带堆栈的 error 构造器,但可以用 runtime.Caller + 自定义 error 类型实现最小闭环。
关键代码片段:
type stackError struct {
msg string
file string
line int
}
func (e *stackError) Error() string { return e.msg }
func (e *stackError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%s\n%s:%d", e.msg, e.file, e.line)
return
}
fallthrough
case 's':
fmt.Fprint(s, e.msg)
}
}
func NewStackErr(msg string) error {
_, file, line, _ := runtime.Caller(1)
return &stackError{msg: msg, file: file, line: line}
}
使用时:return NewStackErr("timeout"),再用 fmt.Printf("%+v", err) 就能输出文件和行号。但注意:它只记录单层调用,不支持链式包装。
生产环境堆栈日志容易被忽略的细节
堆栈信息在日志中真正起作用的前提,是它能被快速关联到具体请求和时间点。光有堆栈不够,还缺上下文。
必须补上的三点:
- 每个 error 日志必须带 trace ID(如从 HTTP Header 提取或生成),否则一堆
%+v堆栈根本没法归因 -
runtime.Stack(buf, false)只打当前 goroutine,排查死锁或竞态需用runtime.Stack(buf, true)(但性能开销大,慎用) - JSON 日志服务(如 Loki、ES)会丢掉
pkg/errors的私有字段,要么改用github.com/go-errors/errors(自带 JSON 支持),要么在日志写入前手动提取fmt.Sprintf("%+v", err)字符串再塞进 log field
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











