package otel import ( "time" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) // Middleware 返回 Gin 中间件,用于自动创建 span func Middleware(serviceName string) gin.HandlerFunc { return func(c *gin.Context) { // 从请求头提取 trace context ctx := c.Request.Context() propagator := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) ctx = propagator.Extract(ctx, propagation.HeaderCarrier(c.Request.Header)) // 创建 span spanName := c.Request.Method + " " + c.FullPath() if c.FullPath() == "" { spanName = c.Request.Method + " " + c.Request.URL.Path } ctx, span := Tracer().Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes( semconv.HTTPRequestMethodKey.String(c.Request.Method), semconv.URLFull(c.Request.URL.String()), semconv.HTTPRoute(c.FullPath()), semconv.ServerAddress(c.Request.Host), attribute.String("http.client_ip", c.ClientIP()), ), ) defer span.End() // 将新的 context 放入请求 c.Request = c.Request.WithContext(ctx) // 记录开始时间 start := time.Now() // 执行后续处理 c.Next() // 记录响应信息 duration := time.Since(start) statusCode := c.Writer.Status() span.SetAttributes( semconv.HTTPResponseStatusCode(statusCode), attribute.Int64("http.response_size", int64(c.Writer.Size())), attribute.Float64("http.duration_ms", float64(duration.Milliseconds())), ) // 如果有错误,记录错误信息 if len(c.Errors) > 0 { span.SetAttributes(attribute.String("error.message", c.Errors.String())) } } }