diff --git a/README.md b/README.md index d0ee929..b26632a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ -# http-opentracing -用于http调用链增加opentracing协议 +# opentracing net/http + + + +[OpenTracing](http://opentracing.io/) instrumentation for [net/http] + +## Usage + +implements http RoundTrip by `NewTraceTracesport(rt http.RoundTripper, activeSpanKey string,peerService string, extraTags ...opentracing.Tag)` . + +Example : + +```go +tracertan := httpinvoke.NewTraceTracesport(http.DefaultTransport,"","",opentracing.Tag{Key:"ab",Value:"b"}) + + client := &http.Client{ + Transport:tracertan, + } +``` +Example for rpcx : + +```go + tracertan := httpinvoke.NewTraceTracesport(http.DefaultTransport,share.OpentracingSpanServerKey,"",opentracing.Tag{Key:"ab",Value:"b"}) + + client := &http.Client{ + Transport:tracertan, + } +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..58d9881 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module . + +go 1.12 + +require github.com/opentracing/opentracing-go v1.1.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..71fd021 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= diff --git a/httptracer.go b/httptracer.go new file mode 100644 index 0000000..9f6cb15 --- /dev/null +++ b/httptracer.go @@ -0,0 +1,92 @@ +package httpinvoke + +import ( + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + "github.com/opentracing/opentracing-go/log" + "net/http" +) + +const _defaultComponentName = "net/http" + + +// TraceTransport wraps a RoundTripper. If a request is being traced with +// Tracer, Transport will inject the current span into the headers, +// and set HTTP related tags on the span. +type TraceTransport struct { + //spankey from parentTrace + activeSpanKey string + //peerService addr + peerService string + //extraTags to be set + extraTags []opentracing.Tag + // The actual RoundTripper to use for the request. A nil + // RoundTripper defaults to http.DefaultTransport. + http.RoundTripper +} + +// NewTraceTracesport NewTraceTracesport +func NewTraceTracesport(rt http.RoundTripper, activeSpanKey string,peerService string, extraTags ...opentracing.Tag) *TraceTransport { + return &TraceTransport{RoundTripper: rt,activeSpanKey:activeSpanKey, peerService: peerService, extraTags: extraTags} +} + +// RoundTrip implements the RoundTripper interface +func (t *TraceTransport) RoundTrip(req *http.Request) (*http.Response, error) { + rt := t.RoundTripper + if rt == nil { + rt = http.DefaultTransport + } + var tr opentracing.Span + if t.activeSpanKey != "" { + acvspans := req.Context().Value(t.activeSpanKey) + if acvspans == nil{ + return rt.RoundTrip(req) + } + tr = opentracing.SpanFromContext(opentracing.ContextWithSpan(req.Context(), acvspans.(opentracing.Span))) + } else { + tr = opentracing.SpanFromContext(req.Context()) + } + + if tr == nil { + return rt.RoundTrip(req) + } + operationName := "HTTP:" + req.Method + //get start new tracer + tracer := tr.Tracer() + span := tracer.StartSpan(operationName, opentracing.ChildOf(tr.Context())) + + ext.DBType.Set(span, "http") + //ext.PeerAddress.Set(span, req.URL.String()) + ext.HTTPMethod.Set(span,req.Method) + ext.HTTPUrl.Set(span, req.URL.String()) + /*ext.SpanKind.Set(span, ext.SpanKindRPCClientEnum)*/ + ext.Component.Set(span,_defaultComponentName) + //end + if t.peerService != "" { + ext.PeerService.Set(span,t.peerService) + } + for _, v := range t.extraTags { + span.SetTag(v.Key, v.Value) + } + // inject trace to http header + tracer.Inject(span.Context(),opentracing.HTTPHeaders,req.Header) + + // ct := clientTracer{tr: tr} + // req = req.WithContext(httptrace.WithClientTrace(req.Context(), ct.clientTrace())) + resp, err := rt.RoundTrip(req) + + if err != nil { + ext.Error.Set(span, true) + span.LogFields(log.String("event", "error"), log.String("message", err.Error())) + span.Finish() + return resp, err + } + + ext.HTTPStatusCode.Set(span,uint16(resp.StatusCode)) + if resp.StatusCode >= 400 { + ext.Error.Set(span, true) + } + span.Finish() + return resp, err +} + diff --git a/httptracer_test.go b/httptracer_test.go new file mode 100644 index 0000000..521a722 --- /dev/null +++ b/httptracer_test.go @@ -0,0 +1,107 @@ +package httpinvoke + +import ( + "context" + "fmt" + "github.com/opentracing/opentracing-go" + zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" + "github.com/openzipkin/zipkin-go" + "github.com/openzipkin/zipkin-go/reporter" + zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" + "log" + "net/http" + "testing" + "time" +) + +func TestHttpTrace(t *testing.T) { + + initZipkinV2() + span := opentracing.StartSpan("test-case") + span.Finish() + ctx := opentracing.ContextWithSpan(context.Background(),span) + mux := http.NewServeMux() + wait := make(chan bool,1) + mux.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + keys := []string{"x-b3-traceid","x-b3-spanid","x-b3-parentspanid","x-b3-sampled"} + for _, key := range keys { + fmt.Println(r.Header.Get(key)) + if r.Header.Get(key) == "" { + t.Errorf("empty key: %s", key) + } + } + wait <- true + })) + + server := &http.Server{ + Addr: ":4400", + WriteTimeout: 4 * time.Second, + Handler: mux, + } + + defer server.Close() + //创建http服务监听 + go func(){ + err := server.ListenAndServe() + if err != nil { + if err == http.ErrServerClosed { + log.Print("Server closed under requeset!!") + } else { + log.Fatal("Server closed unexpecteed!!") + } + + } + }() + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1"+server.Addr, nil) + if err != nil { + t.Fatal(err) + } + req = req.WithContext(ctx) + tracertan := NewTraceTracesport(http.DefaultTransport,"","",opentracing.Tag{Key:"ab",Value:"b"}) + client := &http.Client{Transport: tracertan} + if _, err = client.Do(req); err != nil { + t.Fatal(err) + } + <-wait + +} + +/** + *init zipkin tracer + */ +func initZipkinV2() reporter.Reporter { + msg := "init_zipkin-v2_err" + + // set up a span reporter + reporter := zipkinhttp.NewReporter("") + if reporter == nil { + + panic(msg) + } + + // create our local service endpoint + endpoint, err := zipkin.NewEndpoint("test", "127.0.0.1:4400") + if err != nil || endpoint == nil { + msg := fmt.Sprintf("%v\t%v", msg, err) + panic(msg) + } + + // initialize our tracer + nativeTracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint)) + if err != nil || nativeTracer == nil { + msg := fmt.Sprintf("%v\t%v", msg, err) + panic(msg) + } + + // use zipkin-go-opentracing to wrap our tracer + tracer := zipkinot.Wrap(nativeTracer) + if tracer == nil { + panic(msg) + } + + // optionally set as Global OpenTracing tracer instance + opentracing.SetGlobalTracer(tracer) + + return reporter +} \ No newline at end of file