├── .gitignore ├── LICENSE ├── README.md ├── client.go ├── example_test.go ├── glide.lock ├── glide.yaml ├── main.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .idea/ 27 | opentracing-go-nethttp-demo 28 | opentracing-go-nethttp-demo.iml 29 | vendor/ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yuri Shkuro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opentracing-go-nethttp-demo 2 | 3 | Demo code for blog post [Tracing HTTP request latency in Go with OpenTracing](https://medium.com/@YuriShkuro/tracing-http-request-latency-in-go-with-opentracing-7cc1282a100a#.r6h4w2n9o) 4 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/opentracing-contrib/go-stdlib/nethttp" 10 | "github.com/opentracing/opentracing-go" 11 | "github.com/opentracing/opentracing-go/ext" 12 | otlog "github.com/opentracing/opentracing-go/log" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func runClient(tracer opentracing.Tracer) { 17 | // nethttp.Transport from go-stdlib will do the tracing 18 | c := &http.Client{Transport: &nethttp.Transport{}} 19 | 20 | // create a top-level span to represent full work of the client 21 | span := tracer.StartSpan(client) 22 | span.SetTag(string(ext.Component), client) 23 | defer span.Finish() 24 | ctx := opentracing.ContextWithSpan(context.Background(), span) 25 | 26 | req, err := http.NewRequest( 27 | "GET", 28 | fmt.Sprintf("http://localhost:%s/", *serverPort), 29 | nil, 30 | ) 31 | if err != nil { 32 | onError(span, err) 33 | return 34 | } 35 | 36 | req = req.WithContext(ctx) 37 | // wrap the request in nethttp.TraceRequest 38 | req, ht := nethttp.TraceRequest(tracer, req) 39 | defer ht.Finish() 40 | 41 | res, err := c.Do(req) 42 | if err != nil { 43 | onError(span, err) 44 | return 45 | } 46 | defer res.Body.Close() 47 | body, err := ioutil.ReadAll(res.Body) 48 | if err != nil { 49 | onError(span, err) 50 | return 51 | } 52 | fmt.Printf("Received result: %s\n", string(body)) 53 | } 54 | 55 | func onError(span opentracing.Span, err error) { 56 | // handle errors by recording them in the span 57 | span.SetTag(string(ext.Error), true) 58 | span.LogKV(otlog.Error(err)) 59 | log.Print(err) 60 | } 61 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptrace" 6 | 7 | "github.com/opentracing/opentracing-go" 8 | "github.com/opentracing/opentracing-go/log" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | func ExampleAskGoogle() { 13 | AskGoogle(context.Background()) 14 | } 15 | 16 | // We will talk about this later 17 | var tracer opentracing.Tracer 18 | 19 | func AskGoogle(ctx context.Context) error { 20 | // retrieve current Span from Context 21 | var parentCtx opentracing.SpanContext 22 | parentSpan := opentracing.SpanFromContext(ctx) 23 | if parentSpan != nil { 24 | parentCtx = parentSpan.Context() 25 | } 26 | 27 | // start a new Span to wrap HTTP request 28 | span := tracer.StartSpan( 29 | "ask google", 30 | opentracing.ChildOf(parentCtx), 31 | ) 32 | 33 | // make sure the Span is finished once we're done 34 | defer span.Finish() 35 | 36 | // make the Span current in the context 37 | ctx = opentracing.ContextWithSpan(ctx, span) 38 | 39 | // now prepare the request 40 | req, err := http.NewRequest("GET", "http://google.com", nil) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // attach client trace to the Context, and Context to request 46 | trace := NewClientTrace(span) 47 | ctx = httptrace.WithClientTrace(ctx, trace) 48 | req = req.WithContext(ctx) 49 | 50 | // execute the request 51 | res, err := http.DefaultClient.Do(req) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | // Google home page is not too exciting, so ignore the result 57 | res.Body.Close() 58 | return nil 59 | } 60 | 61 | func NewClientTrace(span opentracing.Span) *httptrace.ClientTrace { 62 | trace := &clientTrace{span: span} 63 | return &httptrace.ClientTrace{ 64 | DNSStart: trace.dnsStart, 65 | DNSDone: trace.dnsDone, 66 | } 67 | } 68 | 69 | // clientTrace holds a reference to the Span and 70 | // provides methods used as ClientTrace callbacks 71 | type clientTrace struct { 72 | span opentracing.Span 73 | } 74 | 75 | func (h *clientTrace) dnsStart(info httptrace.DNSStartInfo) { 76 | h.span.LogKV( 77 | log.String("event", "DNS start"), 78 | log.Object("host", info.Host), 79 | ) 80 | } 81 | 82 | func (h *clientTrace) dnsDone(httptrace.DNSDoneInfo) { 83 | h.span.LogKV(log.String("event", "DNS done")) 84 | } 85 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: f582e7e1988a1c15932213aaec3df483b5718e1609d1ce4ad6a8bdd537e8cc48 2 | updated: 2016-10-06T01:32:36.891852533-04:00 3 | imports: 4 | - name: github.com/apache/thrift 5 | version: 23d6746079d7b5fdb38214387c63f987e68a6d8f 6 | subpackages: 7 | - lib/go/thrift 8 | - name: github.com/opentracing-contrib/go-stdlib 9 | version: 75e37ae3e209c490d2aa67d8a31fb302fdc38cba 10 | subpackages: 11 | - nethttp 12 | - name: github.com/opentracing/opentracing-go 13 | version: 0c3154a3c2ce79d3271985848659870599dfb77c 14 | subpackages: 15 | - ext 16 | - log 17 | - name: github.com/openzipkin/zipkin-go-opentracing 18 | version: 9f204102ca9d3bd81b488fc077b75509f550dcdc 19 | - name: github.com/stretchr/testify 20 | version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 21 | subpackages: 22 | - assert 23 | - require 24 | - suite 25 | - name: github.com/uber/jaeger-client-go 26 | version: 56e83ddb7942b939086cb0c53cd895c211446e28 27 | subpackages: 28 | - internal/spanlog 29 | - thrift-gen/agent 30 | - thrift-gen/sampling 31 | - thrift-gen/zipkincore 32 | - transport 33 | - transport/zipkin 34 | - utils 35 | - name: golang.org/x/net 36 | version: 3b993948b6f0e651ffb58ba135d8538a68b1cddf 37 | subpackages: 38 | - context 39 | testImports: [] 40 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/yurishkuro/opentracing-go-nethttp-demo 2 | import: 3 | - package: github.com/openzipkin/zipkin-go-opentracing 4 | - package: github.com/opentracing-contrib/go-stdlib 5 | - package: github.com/uber/jaeger-client-go 6 | - package: github.com/opentracing/opentracing-go 7 | version: ^1 8 | subpackages: 9 | - ext 10 | - package: github.com/stretchr/testify 11 | version: ^1.1.3 12 | subpackages: 13 | - assert 14 | - require 15 | - suite 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/uber/jaeger-client-go" 8 | "github.com/uber/jaeger-client-go/transport/zipkin" 9 | ) 10 | 11 | var ( 12 | zipkinURL = flag.String("url", 13 | "http://localhost:9411/api/v1/spans", "Zipkin server URL") 14 | serverPort = flag.String("port", "8000", "server port") 15 | actorKind = flag.String("actor", "server", "server or client") 16 | ) 17 | 18 | const ( 19 | server = "server" 20 | client = "client" 21 | ) 22 | 23 | func main() { 24 | flag.Parse() 25 | 26 | if *actorKind != server && *actorKind != client { 27 | log.Fatal("Please specify '-actor server' or '-actor client'") 28 | } 29 | 30 | // Jaeger tracer can be initialized with a transport that will 31 | // report tracing Spans to a Zipkin backend 32 | transport, err := zipkin.NewHTTPTransport( 33 | *zipkinURL, 34 | zipkin.HTTPBatchSize(1), 35 | zipkin.HTTPLogger(jaeger.StdLogger), 36 | ) 37 | if err != nil { 38 | log.Fatalf("Cannot initialize HTTP transport: %v", err) 39 | } 40 | // create Jaeger tracer 41 | tracer, closer := jaeger.NewTracer( 42 | *actorKind, 43 | jaeger.NewConstSampler(true), // sample all traces 44 | jaeger.NewRemoteReporter(transport, nil), 45 | ) 46 | 47 | if *actorKind == server { 48 | runServer(tracer) 49 | return 50 | } 51 | 52 | runClient(tracer) 53 | 54 | // Close the tracer to guarantee that all spans that could 55 | // be still buffered in memory are sent to the tracing backend 56 | closer.Close() 57 | } 58 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/opentracing-contrib/go-stdlib/nethttp" 11 | "github.com/opentracing/opentracing-go" 12 | ) 13 | 14 | func getTime(w http.ResponseWriter, r *http.Request) { 15 | log.Print("Received getTime request") 16 | t := time.Now() 17 | ts := t.Format("Mon Jan _2 15:04:05 2006") 18 | io.WriteString(w, fmt.Sprintf("The time is %s", ts)) 19 | } 20 | 21 | func redirect(w http.ResponseWriter, r *http.Request) { 22 | http.Redirect(w, r, 23 | fmt.Sprintf("http://localhost:%s/gettime", *serverPort), 301) 24 | } 25 | 26 | func runServer(tracer opentracing.Tracer) { 27 | http.HandleFunc("/gettime", getTime) 28 | http.HandleFunc("/", redirect) 29 | log.Printf("Starting server on port %s", *serverPort) 30 | err := http.ListenAndServe( 31 | fmt.Sprintf(":%s", *serverPort), 32 | // use nethttp.Middleware to enable OpenTracing for server 33 | nethttp.Middleware(tracer, http.DefaultServeMux)) 34 | if err != nil { 35 | log.Fatalf("Cannot start server: %s", err) 36 | } 37 | } 38 | --------------------------------------------------------------------------------