bindbox-game/internal/pkg/otel/middleware.go

67 lines
1.8 KiB
Go

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()))
}
}
}