go 的 error 接口无法直接携带 context 值,必须提前提取(如 reqid、traceid)并显式注入错误字符串或自定义 error 类型;禁止在 error 实现中延迟调用 ctx.value,避免 panic 或 nil 值。

Context 值无法直接塞进 error 接口里
Go 的 error 是个只带 Error() 方法的接口,不支持携带任意字段。你不能把 context.Context 或它的值(比如 ctx.Value("req_id"))直接嵌入 error 实例中——除非你自己实现 error 类型并显式保存。
常见错误现象是:在中间件或 handler 里用 ctx.Value("trace_id") 打日志没问题,但一旦发生错误、返回 fmt.Errorf("failed to read file"),这个 trace_id 就丢了。
- 必须手动提取 context 值,并拼进错误字符串里(简单但脆弱)
- 更稳妥的是封装一个带 context 元信息的 error 类型,比如
type ctxError struct { msg string; reqID string; traceID string } - 注意:不要在 error 实现里调用
ctx.Value—— 因为 error 可能延迟打印,此时 ctx 已 cancel 或超时,Value返回 nil
用 fmt.Errorf 拼接是最轻量的上下文注入方式
适用于调试期快速关联、日志可读性优先的场景,不追求结构化或下游解析。
关键点在于「提前取值,而非延迟求值」:
立即学习“go语言免费学习笔记(深入)”;
- 在出错前立刻提取你需要的 context 值,例如:
reqID := ctx.Value("req_id").(string) - 再用
fmt.Errorf("read timeout for req %s: %w", reqID, err)包裹原始 error - 避免写成
fmt.Errorf("read timeout: %w", err)后再补 log —— 这样上下文和 error 在不同日志行,排查时容易断链 - 如果
ctx.Value可能为 nil,务必判空,否则 panic;建议用类型断言 + ok 模式:if id, ok := ctx.Value("req_id").(string); ok { ... }
自定义 error 类型才能真正保留结构化上下文
当你需要下游代码根据 req_id 做重试路由、按 trace_id 聚合错误指标,或者要让 errors.As 能识别上下文字段时,必须自己实现 error。
示例结构:
type ContextualError struct {
Msg string
ReqID string
TraceID string
Cause error
}
func (e *ContextualError) Error() string {
return fmt.Sprintf("[%s][%s] %s", e.ReqID, e.TraceID, e.Msg)
}
func (e *ContextualError) Unwrap() error { return e.Cause }
- 别忘了实现
Unwrap(),否则errors.Is/As无法穿透到原始 error - 字段名用小写(如
ReqID)是为了导出,方便外部访问;但别暴露context.Context本身——它不是 error 的一部分 - 构造函数应接收已提取的值,而不是 ctx:
NewContextualError(ctx, "db timeout")是反模式;正确做法是NewContextualError(reqID, traceID, "db timeout", err)
HTTP 中间件里注入上下文错误最容易漏掉 recovery 场景
panic 后 recover 到的 error 默认不含任何 context 信息,这是线上服务最常丢上下文的地方。
- 在
recover()分支里,必须从当前 goroutine 绑定的 context(比如 http.Request.Context())中重新提取值 - 不要依赖全局变量或闭包捕获的 ctx —— panic 可能在任意深度发生,那个 ctx 可能早已失效
- 典型错误:用
log.Printf("panic: %+v", r),但没把req_id一起打进去;应该先取值,再格式化:log.Printf("[%s] panic: %+v", reqID, r) - 如果用了第三方 panic 捕获库(如
gin.Recovery()),检查它是否支持自定义 error 构造逻辑;不支持就替换或 wrap 它










