基本概念
Trace标准
OpenTracing是当前主流,其中golang中OpenTracing的实现有jaeger
google下一代可观测标准是OpenTelemetry「整合日志、监控告警、APM」
jaeger 初始化
package jaeger
import (
"context"
"fmt"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
"io"
"net/http"
)
const (
LocalAgentHostPort = "x:6831"
TraceIDKey = "Hermes-Trace-ID"
)
func GetDefaultConfig() *config.Configuration {
cfg := &config.Configuration{
Sampler: &config.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: LocalAgentHostPort,
},
}
return cfg
}
func init() {
jaegerConfig := GetDefaultConfig()
InitJaeger("go-framework-demo", jaegerConfig)
}
/*
*
初始化
*/
func InitJaeger(service string, cfg *config.Configuration) (opentracing.Tracer, io.Closer) {
cfg.ServiceName = service
tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("Error: connot init Jaeger: %v\n", err))
}
opentracing.SetGlobalTracer(tracer)
return tracer, closer
}
func StartSpan(tracer opentracing.Tracer, name string) opentracing.Span {
//设置顶级span
span := tracer.StartSpan(name)
return span
}
func WithSpan(ctx context.Context, name string) (opentracing.Span, context.Context) {
span, ctx := opentracing.StartSpanFromContext(ctx, name)
return span, ctx
}
// 透传上下文
func GetCarrier(span opentracing.Span) (opentracing.HTTPHeadersCarrier, error) {
carrier := opentracing.HTTPHeadersCarrier{}
err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier)
if err != nil {
return nil, err
}
return carrier, nil
}
func GetParentSpan(spanName string, traceId string, header http.Header) (opentracing.Span, error) {
carrier := opentracing.HTTPHeadersCarrier{}
carrier.Set(TraceIDKey, traceId)
tracer := opentracing.GlobalTracer()
wireContext, err := tracer.Extract(
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(header),
)
parentSpan := opentracing.StartSpan(
spanName,
ext.RPCServerOption(wireContext))
if err != nil {
return nil, err
}
return parentSpan, err
}
Gin中间件
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"x.com/infra/hermes/jaeger"
j "github.com/uber/jaeger-client-go"
)
var (
SpanCTX = "span-ctx"
)
func TraceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
traceId := c.GetHeader(TraceIDKey)
var span opentracing.Span
if traceId != "" {
var err error
span, err = jaeger.GetParentSpan(c.FullPath(), traceId, c.Request.Header)
if err != nil {
return
}
} else {
span = jaeger.StartSpan(opentracing.GlobalTracer(), c.FullPath())
}
defer span.Finish()
c.Set(SpanCTX, opentracing.ContextWithSpan(c, span))
c.Set(TraceIDKey,span.Context().(j.SpanContext).TraceID().String())
c.Next()
}
}
routergroup注册中间件
apiCostV3 := r.Group("/api/cost/v3",middleware.GenCacheQueryKeyMiddleware(),middleware.TraceMiddleware(),)
bill.RegisterCostComparisonRouter(apiCostV3)
Handler处理
func CostComparisonHandlerParameter(c *gin.Context) (*dto.AliyunProductPretaxAmountInCycleRequest, error) {
...
` 重点 `
if value, exists := c.Get(SpanCTX); exists {
ppa.OpenTracingContextWithSpan = value.(context.Context)
}
...
return &ppa, nil
}
封装创建Span
func CreateChildSpanContext(spanCtxInterface context.Context, spanName string) {
var spanCtx context.Context
spanCtx = spanCtxInterface.(context.Context)
//创建子span
span, _ := jaeger.WithSpan(spanCtx, spanName)
defer span.Finish() //结束后调用完成
调用Span创建
注入接口一「子Span」
import (
rhandler "acmp-service/handler/response"
)
func (_c *CostService) GetAYProductPretaxAmountInCycle(ppa *dto.AYProductPretaxAmountInCycleRequest) (any, error) {
rhandler.CreateChildSpanContext(ppa.OpenTracingContextWithSpan, "GetAYroductPretaxAmountInCycle")
注入接口二「子Span」
确保传递给CreateChildSpanContext类型为
中间件中Key:SpanCTX、Value:context.Context
func Page(overview Res, c *gin.Context) Res {
span,_ := c.Get(SpanCTX)
CreateChildSpanContext(span.(context.Context), "Page")
}
Gin返回携带TraceID
返回TraceID
type PageResponse struct {
Result int `json:"result"`
Message string `json:"message"`
Success bool `json:"success"`
TotalCount int `json:"totalCount"`
Data interface{} `json:"data"`
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
TraceID string `json:"traceID"`
}
func (g *HGin) traceID() string {
traceID, e := g.C.Get(TraceIDKey)
if e && traceID != nil {
return traceID.(string)
}
return ""
}
func (g *HGin) PageOK(data any, count int, pageNum int, PageSize int) {
g.C.JSON(http.StatusOK, PageResponse{
Result: http.StatusOK,
Message: "success",
Success: true,
TotalCount: count,
Data: data,
PageNum: pageNum,
PageSize: PageSize,
TraceID: g.traceID(),
})
return
}
效果
若前端提交TraceID,则以前端为准;
若未提交,后端自动返回TraceID
注入接口一
注入接口二
Q&A
封装ChildSpan V2
封装ChildSpan V2
func CreateChildSpanContext(spanCtxInterface context.Context, spanName string) opentracing.Span{
var spanCtx context.Context
spanCtx = spanCtxInterface.(context.Context)
//创建子span
span, _ := jaeger.WithSpan(spanCtx, spanName)
return span
}
调用ChildSpan V2
span := response.CreateChildSpanContext(c, "getCertificates")
span.SetTag("domainName", domainName)
defer span.Finish()
效果
链接
developer.aliyun.com/article/110…