使用Golang otelgin中间件无法创建子Span,仅能观测中间件Span
问题描述
使用Golang的otelgin中间件为Go服务器的每个请求生成Span,但尝试创建子Span实现更细粒度链路追踪时失败,目前仅能观测到中间件生成的Span。
相关代码
main.go
package main import ( "context" "log" "net/http" "net/url" "os" "strconv" "time" "github.com/gin-gonic/gin" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "go.opentelemetry.io/contrib/propagators/aws/xray" sampler "go.opentelemetry.io/contrib/samplers/aws/xray" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" ) var svcName = os.Getenv("OTEL_SERVICE_NAME") func main() { err := start_xray() if err != nil { log.Fatalf("Failed to start XRay: %v", err) return } r := gin.Default() r.Use(otelgin.Middleware(svcName)) r.GET("/", func(ctx *gin.Context) { tracer := otel.Tracer(svcName) _, span := tracer.Start(ctx.Request.Context(), "sample-trace", oteltrace.WithSpanKind(oteltrace.SpanKindServer), oteltrace.WithAttributes([]attribute.KeyValue{ {Key: "http.method", Value: attribute.StringValue(ctx.Request.Method)}, }...)) defer span.End() time.Sleep(1 * time.Second) ctx.JSON(http.StatusOK, gin.H{ "healthy": true, }) span.SetAttributes(attribute.KeyValue{Key: "http.status", Value: attribute.StringValue(strconv.Itoa(http.StatusOK))}) }) if err := r.Run(":8080"); err != nil { log.Fatal(err) } } func start_xray() error { ctx := context.Background() exporterEndpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") if exporterEndpoint == "" { exporterEndpoint = "localhost:4317" } log.Println("Creating new OTLP trace exporter...") traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(exporterEndpoint), otlptracegrpc.WithDialOption(grpc.WithBlock())) if err != nil { log.Fatalf("Failed to create new OTLP trace exporter: %v", err) return err } idg := xray.NewIDGenerator() samplerEndpoint := os.Getenv("XRAY_ENDPOINT") if samplerEndpoint == "" { samplerEndpoint = "http://localhost:2000" } endpointUrl, err := url.Parse(samplerEndpoint) res, err := sampler.NewRemoteSampler(ctx, svcName, "ecs", sampler.WithEndpoint(*endpointUrl), sampler.WithSamplingRulesPollingInterval(10*time.Second)) if err != nil { log.Fatalf("Failed to create new XRay Remote Sampler: %v", err) return err } // attach remote sampler to tracer provider tp := trace.NewTracerProvider( trace.WithSampler(res), trace.WithBatcher(traceExporter), trace.WithIDGenerator(idg), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(xray.Propagator{}) return nil }
docker-compose.yml
services: app: build: context: ./ depends_on: - otel environment: - LISTEN_ADDRESS=0.0.0.0:8080 - AWS_ACCESS_KEY_ID=<key> - AWS_SECRET_ACCESS_KEY=<secret> - OTEL_RESOURCE_ATTRIBUTES=service.name=sample-service - OTEL_SERVICE_NAME=sample-service - OTEL_EXPORTER_OTLP_ENDPOINT=otel:4317 - XRAY_ENDPOINT=http://otel:2000 - OTEL_JAVAAGENT_DEBUG=true - OTEL_METRICS_EXPORTER=otlp volumes: - /tmp/awscreds:/tmp/awscreds ports: - '8080:8080' otel: image: amazon/aws-otel-collector:latest command: --config /etc/ecs/ecs-default-config.yaml environment: - AWS_ACCESS_KEY_ID=<key> - AWS_SECRET_ACCESS_KEY=<secret> - AWS_REGION=us-east-1 volumes: - .:/config - /tmp/awscreds:/tmp/awscreds ports: - '4317:4317' - '2000:2000'
Dockerfile
## ## STEP 1 - BUILD ## FROM golang:1.21 AS build WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY ./ ./ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /bootstrap ./cmd/server ## ## STEP 2 - DEPLOY ## FROM alpine:latest WORKDIR / COPY --from=build /bootstrap /bootstrap RUN apk update && apk add curl EXPOSE 8080 ENTRYPOINT ["/bootstrap"]
解决方案
问题出在创建子Span时使用的上下文不对:otelgin中间件会将生成的请求Span存储在Gin的Context中,而你当前用的ctx.Request.Context()并没有携带这个父Span的上下文,导致新创建的Span无法关联为子Span。
修改方法是使用otelgin.GetSpan(ctx).Context()来获取包含父Span的上下文,替换原代码中的ctx.Request.Context():
r.GET("/", func(ctx *gin.Context) { tracer := otel.Tracer(svcName) // 替换上下文获取方式 parentCtx := otelgin.GetSpan(ctx).Context() _, span := tracer.Start(parentCtx, "sample-trace", oteltrace.WithSpanKind(oteltrace.SpanKindInternal), // 子Span建议用Internal类型 oteltrace.WithAttributes([]attribute.KeyValue{ {Key: "http.method", Value: attribute.StringValue(ctx.Request.Method)}, }...)) defer span.End() time.Sleep(1 * time.Second) ctx.JSON(http.StatusOK, gin.H{ "healthy": true, }) span.SetAttributes(attribute.KeyValue{Key: "http.status", Value: attribute.StringValue(strconv.Itoa(http.StatusOK))}) })
另外,子Span的SpanKind建议设置为oteltrace.SpanKindInternal,因为这是服务内部的操作,而非对外的服务器端请求,这样链路追踪的语义更准确。
内容的提问来源于stack exchange,提问作者Javier Perez
相关产品推荐
相关产品推荐

