必须调用 span.recorderror(err) 并设置 status,不可仅记录日志或设为属性;自定义 error 应封装业务字段并分开记录;http handler 需统一捕获 panic 和错误,对齐 otel 与 http 状态码。

Go 的 error 怎么塞进 OpenTelemetry 的 span?
不能直接往 span 里塞原生 error,OpenTelemetry Go SDK 要求你显式调用 span.RecordError() 或设置 status,否则错误不会出现在追踪系统里(比如 Jaeger、OTLP 后端)。
常见错误是只写 log.Printf("failed: %v", err) 或把 err 当普通字段塞进 span.SetAttributes() —— 这样只会当字符串存,丢失类型、堆栈、业务上下文。
-
span.RecordError(err)是必须的,它会自动提取err.Error()和(如果实现了StackTrace())堆栈,同时标记 span 状态为Error - 若想带业务字段(如订单 ID、用户 ID),别改
err本身,而是用span.SetAttributes()单独加attribute.String("order_id", orderID) - 不要在 defer 中调用
RecordError(),除非你能确保err在 defer 执行时还有效(闭包捕获或传参)
怎么让自定义 error 携带 traceID 和业务字段?
Go 原生 error 接口太简单,没法自动透传 traceID 或附加结构化数据。你需要自己封装,但别用 fmt.Errorf("%w", err) 直接套娃 —— 它丢掉所有额外字段,只保留底层 Error() 字符串。
推荐用 errors.Join()(Go 1.20+)或社区方案如 github.com/pkg/errors,但更轻量的做法是实现自己的 error 类型:
立即学习“go语言免费学习笔记(深入)”;
type BizError struct {
Code string
Message string
OrderID string
UserID string
Err error // 底层原始 error
}
func (e *BizError) Error() string { return e.Message }
func (e *BizError) Unwrap() error { return e.Err }
这样既能用 errors.Is()/errors.As() 判断,又能在记录时手动提取字段:
- 调用
span.RecordError(bizErr.Err)记录原始错误 - 再用
span.SetAttributes(attribute.String("biz.code", bizErr.Code), attribute.String("order_id", bizErr.OrderID)) - 注意:
traceID不需要手动塞 —— 只要 span 是当前 context 的,导出时会自动关联
context.WithValue() 能不能用来传 error 上下文?
不能。Go 的 context.Context 是只读传递控制流和截止时间的,不是错误载体。往 context 里塞 error 或业务对象,属于反模式:
- 下游可能忽略它,导致错误静默丢失
- 违反 OpenTelemetry “显式记录” 原则 —— tracing 系统看不到 context 里的值
- 容易和 cancel/timeout 逻辑耦合,增加调试难度
真正该用 context 传的是 trace propagation 信息(比如 propagators.TraceContext{}.Extract()),不是业务错误。错误始终由 handler 层或 RPC 调用点决定是否记录、如何丰富。
HTTP handler 里怎么统一处理 error 并上报?
别在每个 if err != nil 后手写 span.RecordError() —— 容易漏、重复、参数不一致。用中间件或统一返回封装:
func httpHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
span := trace.SpanFromContext(ctx)
defer func() {
if r := recover(); r != nil {
span.RecordError(fmt.Errorf("panic: %v", r))
span.SetStatus(codes.Error, "panic")
}
}()
// ...业务逻辑
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
// 注意:这里 status code 要和 HTTP 状态码一致,否则后端解析错乱
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
关键点:
- panic 捕获必须有,否则崩溃类错误完全不上报
-
span.SetStatus()的第二个参数建议用简短标识(如"not_found"),而不是长错误消息,避免日志膨胀 - HTTP 状态码和 OTel status code 要对齐:
codes.NotFound对应 404,codes.Internal对应 500
链路里 error 不是“有没有”的问题,是“在哪记、记什么、谁负责丰富”的问题。最常漏的是 panic 捕获和 status code 对齐,这两处一错,整条 trace 的错误语义就断了。










