github完整代碼地址 我的博客html
jaeger提供一個all in one 的docker鏡像,能夠快速搭建實驗環境git
docker run -d --name jaeger
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411
-p 5775:5775/udp
-p 6831:6831/udp
-p 6832:6832/udp
-p 5778:5778
-p 16686:16686
-p 14268:14268
-p 9411:9411
jaegertracing/all-in-one:1.6
複製代碼
OpenTracing經過提供平臺無關、廠商無關的API,使得開發人員可以方便的添加(或更換)追蹤系統的實現。 OpenTracing提供了用於運營支撐系統的和針對特定平臺的輔助程序庫。 jaeger兼容OpenTracing API,因此咱們使用OpenTracing的程序庫能夠方便的替換追蹤工具。 OpenTracing中文文檔github
封住一下jaeger的初始化操做方便使用,詳細用法能夠查看 jaeger-client-gogolang
// lib/tracer
// NewTracer 建立一個jaeger Tracer
func NewTracer(servicename string, addr string) (opentracing.Tracer, io.Closer, error) {
cfg := jaegercfg.Configuration{
ServiceName: servicename,
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
BufferFlushInterval: 1 * time.Second,
},
}
sender, err := jaeger.NewUDPTransport(addr, 0)
if err != nil {
return nil, nil, err
}
reporter := jaeger.NewRemoteReporter(sender)
// Initialize tracer with a logger and a metrics factory
tracer, closer, err := cfg.NewTracer(
jaegercfg.Reporter(reporter),
)
return tracer, closer, err
}
func main() {
t, io, err := tracer.NewTracer("tracer", "")
if err != nil {
log.Fatal(err)
}
defer io.Close()
opentracing.SetGlobalTracer(t)
}
複製代碼
opentracing.SetGlobalTracer(t) 方法執行會將jaeger tracer註冊到全局,接下來只須要使用opentracing 的標準API即可以了。 若是不想使用jaeger了,想替換成其餘分佈式追蹤工具,只須要工具支持opentracing標準,並將main函數的SetGlobalTracer操做替換便可,其餘文件都不須要更改。docker
在micro自帶的插件中已經有opentracing的插件了,包含server,client等,不過這個插件只能go-micro構建的微服務(api,srv)中使用。由於micro網關有一個獨立的插件系統,可是並無提供opentracing相關的插件。api
micro/go-plugins/wrapper/trace/opentracing/opentracing.gobash
咱們能夠在構建服務的時候直接使用,只須要在服務初始化時增長一行函數就能夠了。app
service := micro.NewService(
micro.Name(name),
micro.Version("latest"),
micro.WrapHandler(ocplugin.NewHandlerWrapper(opentracing.GlobalTracer())),
)
複製代碼
srv/user/main.go 目錄下的user 服務是一個完整的使用實例。分佈式
// micro opentracing插件中wHandlerWrappe
// NewHandlerWrapper accepts an opentracing Tracer and returns a Handler Wrapper
func NewHandlerWrapper(ot opentracing.Tracer) server.HandlerWrapper {
return func(h server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
ctx, span, err := traceIntoContext(ctx, ot, name)
if err != nil {
return err
}
defer span.Finish()
return h(ctx, req, rsp)
}
}
}
複製代碼
lib/wrapper/tracer/opentracing/stdhttp/stdhttp.go函數
和實現JWT鑑權插件同樣,實現一個HTTP中間件經過mciro的插件機制全局註冊就能夠實現攔截每次請求並處理。
// TracerWrapper tracer wrapper
func TracerWrapper(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
sp := opentracing.GlobalTracer().StartSpan(r.URL.Path, opentracing.ChildOf(spanCtx))
defer sp.Finish()
if err := opentracing.GlobalTracer().Inject(
sp.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(r.Header)); err != nil {
log.Println(err)
}
sct := &status_code.StatusCodeTracker{ResponseWriter: w, Status: http.StatusOK}
h.ServeHTTP(sct.WrappedResponseWriter(), r)
ext.HTTPMethod.Set(sp, r.Method)
ext.HTTPUrl.Set(sp, r.URL.EscapedPath())
ext.HTTPStatusCode.Set(sp, uint16(sct.Status))
if sct.Status >= http.StatusInternalServerError {
ext.Error.Set(sp, true)
}
})
}
複製代碼
Tracer相關的概念能夠查看這個文檔
lib/wrapper/tracer/opentracing/gin2micro/gin2micro.go
// TracerWrapper tracer 中間件
func TracerWrapper(c *gin.Context) {
md := make(map[string]string)
spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
sp := opentracing.GlobalTracer().StartSpan(c.Request.URL.Path, opentracing.ChildOf(spanCtx))
defer sp.Finish()
if err := opentracing.GlobalTracer().Inject(sp.Context(),
opentracing.TextMap,
opentracing.TextMapCarrier(md)); err != nil {
log.Log(err)
}
ctx := context.TODO()
ctx = opentracing.ContextWithSpan(ctx, sp)
ctx = metadata.NewContext(ctx, md)
c.Set(contextTracerKey, ctx)
c.Next()
statusCode := c.Writer.Status()
ext.HTTPStatusCode.Set(sp, uint16(statusCode))
ext.HTTPMethod.Set(sp, c.Request.Method)
ext.HTTPUrl.Set(sp, c.Request.URL.EscapedPath())
if statusCode >= http.StatusInternalServerError {
ext.Error.Set(sp, true)
}
}
// ContextWithSpan 返回context
func ContextWithSpan(c *gin.Context) (ctx context.Context, ok bool) {
v, exist := c.Get(contextTracerKey)
if exist == false {
ok = false
return
}
ctx, ok = v.(context.Context)
return
}
複製代碼
基本操做流程和給micro編寫的插件相同,可是有兩點不一樣。其一,由於我使用gin開發API服務,因此基於gin的API。其二,由於micro內部提供經過golang context傳遞spanContexts的機制,因此將這邊會將child spanContexts注入到gin 的context,在API服務經過micro提供RPC接口(生成的XX.micro.go文件中調用函數第一個參數都是context)調用其餘服務時傳入提取的context,以下:
...
ctx, ok := gin2micro.ContextWithSpan(c)
if ok == false {
log.Log("get context err")
}
res, err := s.helloC.Call(ctx, &helloS.Request{Name: "xuxu"})
...
複製代碼
完整的實現細節能夠查看,github倉庫中 lib/wrapper/tracer/opentracing, 這裏。