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