You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在OpenTelemetry Go SDK父Span中设置http.response.body.size属性

解决OpenTelemetry Go SDK中设置http.response.body.size时Span为空的问题

问题根源

你遇到的Span为空的原因是:otelhttp.NewHandler在处理请求时,会创建新的请求上下文并将Span注入其中,然后传递给下游的业务handler。而你在onWrite回调中使用的是原始请求的上下文(r.Context()),这个上下文还未关联上OpenTelemetry创建的Span,因此无法获取到有效Span实例。

解决方案

调整代码结构,确保在包含Span的上下文环境中设置响应体大小属性。以下是基于你现有代码的修改版本:

type responseWriterWrapper struct {
    http.ResponseWriter
    contentLength int
}

func (rw *responseWriterWrapper) Write(b []byte) (int, error) {
    n, err := rw.ResponseWriter.Write(b)
    rw.contentLength += n
    return n, err
}

// 可选:处理WriteHeader,兼容仅返回状态码无响应体的场景
func (rw *responseWriterWrapper) WriteHeader(statusCode int) {
    rw.ResponseWriter.WriteHeader(statusCode)
}

func NewHTTPHandler(name string, handler http.Handler, tp Provider, attr ...Attribute) http.Handler {
    opts := []otelhttp.Option{
        otelhttp.WithSpanNameFormatter(httpSpanNameFormatter),
    }

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 复制原始属性切片,避免并发请求间的切片修改冲突
        reqAttrs := make([]Attribute, len(attr))
        copy(reqAttrs, attr)
        reqAttrs = append(reqAttrs, NewAttribute("http.request.body.size", r.ContentLength))

        // 构建独立的handler选项,避免跨请求污染
        handlerOpts := append([]otelhttp.Option{}, opts...)
        handlerOpts = append(handlerOpts, otelhttp.WithSpanOptions(
            trace.WithAttributes(reqAttrs...),
        ))

        rwWrapper := &responseWriterWrapper{ResponseWriter: w}

        // 包装业务handler,确保在注入Span的上下文中操作
        wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 执行业务逻辑
            handler.ServeHTTP(w, r)

            // 业务执行完成后,Span仍处于活跃状态,此时获取有效Span
            span := trace.SpanFromContext(r.Context())
            if span.IsRecording() {
                span.SetAttributes(NewAttribute("http.response.body.size", int64(rwWrapper.contentLength)))
            }
        })

        // 让otel handler处理包装后的业务逻辑
        otelhttp.NewHandler(wrappedHandler, name, handlerOpts...).ServeHTTP(rwWrapper, r)
    })
}

关键改进点

  1. 避免并发属性冲突:复制原始属性切片,防止多个并发请求同时修改同一个切片导致的数据异常。
  2. 在正确的上下文中获取Span:将业务handler包装后,在其内部使用传入的r.Context()(已注入Span的上下文)获取有效Span实例。
  3. 延迟设置属性:在业务逻辑执行完成后统一设置响应体大小属性,无需每次调用Write都更新,同时确保Span仍处于活跃状态。
  4. 检查Span记录状态:通过span.IsRecording()判断Span是否允许修改,避免无效操作。

额外说明

如果需要更灵活的Span处理(比如在Span结束前统一处理属性),也可以自定义SpanProcessor,通过上下文存储响应体大小,在OnEnd阶段注入属性,但上述方案更贴合你现有的代码结构,实现成本更低。

内容的提问来源于stack exchange,提问作者msidler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:32:11