如何在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) }) }
关键改进点
- 避免并发属性冲突:复制原始属性切片,防止多个并发请求同时修改同一个切片导致的数据异常。
- 在正确的上下文中获取Span:将业务handler包装后,在其内部使用传入的
r.Context()(已注入Span的上下文)获取有效Span实例。 - 延迟设置属性:在业务逻辑执行完成后统一设置响应体大小属性,无需每次调用
Write都更新,同时确保Span仍处于活跃状态。 - 检查Span记录状态:通过
span.IsRecording()判断Span是否允许修改,避免无效操作。
额外说明
如果需要更灵活的Span处理(比如在Span结束前统一处理属性),也可以自定义SpanProcessor,通过上下文存储响应体大小,在OnEnd阶段注入属性,但上述方案更贴合你现有的代码结构,实现成本更低。
内容的提问来源于stack exchange,提问作者msidler
相关产品推荐
相关产品推荐

