How to implement distributed opentracing using Go?

Shreya Singh
4 min readJun 26, 2018

This is an implementation of distributed open-tracing using Golang and Jaeger UI.

In micro services architectures of modern application development, distributed tracing instrumentation is used by developers to trace requests across the different endpoints of a system. With the higher-level distribution of services that takes place in a cloud-based environment, tracing will become a key part of the cloud infrastructure supporting those services.

In OpenTracing, a trace tells the story of a transaction or workflow as it propagates through a distributed system. The concept of the trace borrows a tool from the scientific community called a directed acyclic graph (DAG), which stages the parts of a process from a clear start to a clear end.

The github repository of the codes is available at this link:

https://github.com/singh-shreya6/opentracing.go.git

Here I have provided the code for opentracing in a distributed environment where a user is making a request on a browser at some port.The request to server 1 prints out Hello ! time is . After completing this operation, server 1 makes a request to server 2 on another port,by sending the name in request, the second server takes the name in its response body and displays Hello !

So overall the following operations takes place:

  1. A browser makes a request to http://localhost:8082/name
  2. The browser displays Hello name! time is Thu Jun 7 17:18:54 2018
  3. Server 1 sends a POST request to Sever 2 with the name at http://localhost:8081
  4. Server 2 captures this request in response body and displays Hello name!

Code for Server-1:

package main

import (
“fmt”
“net/http”
“time”
“io/ioutil”
“strings”
“context”
“go-opentracing”
“log”
)

func main() {
go_opentracing.Init(“server-1”)
s:=&http.Server{
Addr: “:8082”,
Handler: go_opentracing.HttpMiddleware(“server-1”, http.HandlerFunc(handle)),
}
log.Fatal(s.ListenAndServe())

}

func handle(w http.ResponseWriter, r *http.Request) {
t:=time.Now()
name:=r.URL.Path[1:]
time.Sleep(250 * time.Millisecond)
makeRequest(r.Context(), “http://localhost:8081",name)
fmt.Fprintf(w, “Hello %s! time is %s”, name,t.Format(“Mon Jan _2 15:04:05 2006”))
}

func makeRequest(ctx context.Context, serverAddr string, name string){
span, ctx:=go_opentracing.Introduce_span(ctx,”server-1_to_server-2")
defer span.Finish()

req, err := http.NewRequest(http.MethodPost, “http://localhost:8081", strings.NewReader(name))
if err != nil {
fmt.Printf(“http.NewRequest() error: %v\n”, err)
return
}

go_opentracing.Serialise(ctx,req)

c := &http.Client{}
resp, err := c.Do(req)
if err != nil {
fmt.Printf(“http.Do() error: %v\n”, err)
return
}
defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf(“ioutil.ReadAll() error: %v\n”, err)
return
}
fmt.Printf(“\n%s”, string(data))
}

Code for Server-2:

package main

import (
“net/http”
“io/ioutil”
“fmt”
“log”
“go-opentracing”
“time”
)

func handle(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
serverSpan,r:= go_opentracing.Deserialize(r,”server-2")
defer serverSpan.Finish()

reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf(“ioutil.ReadAll() error: %v\n”, err)
w.WriteHeader(http.StatusBadRequest)
return
}

name := string(reqBody)
time.Sleep(500 * time.Millisecond)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, “Hello %s!”, name)
}

func main() {
go_opentracing.Init(“server-2”)
s := &http.Server{
Addr: “:8081”,
Handler: go_opentracing.HttpMiddleware(“server-2”, http.HandlerFunc(handle)),
}
log.Fatal(s.ListenAndServe())
}

Go Opentracing code:

package go_opentracing

import (
“github.com/opentracing/opentracing-go”
“github.com/uber/jaeger-client-go”
“net/http”
“context”
“github.com/opentracing/opentracing-go/ext”
“log”
“time”
)

func Init(s string) {
// init open tracing
udpTransport, _ := jaeger.NewUDPTransport(“localhost:5775”, 0)
reporter:= jaeger.NewRemoteReporter(udpTransport)
sampler := jaeger.NewConstSampler(true)
tracer, _ := jaeger.NewTracer(s, sampler, reporter)
opentracing.SetGlobalTracer(tracer)
}

func Introduce_span(ctx context.Context,spanName string)(opentracing.Span,context.Context) {
span,ctx:=opentracing.StartSpanFromContext(ctx, spanName)
return span,ctx
}

func Serialise(ctx context.Context,req *http.Request){
req = req.WithContext(ctx)
if span := opentracing.SpanFromContext(ctx); span != nil {
opentracing.GlobalTracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header))
}
}

func Deserialize(r *http.Request,spanName string) (opentracing.Span,*http.Request){
time.Sleep(250 * time.Millisecond)
var serverSpan opentracing.Span
appSpecificOperationName := spanName
wireContext, err := opentracing.GlobalTracer().Extract(
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(r.Header))
if err != nil {
log.Fatal(“Error”)
}
serverSpan = opentracing.StartSpan(
appSpecificOperationName,
ext.RPCServerOption(wireContext))
ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
r=r.WithContext(ctx)
return serverSpan,r
}

func HttpMiddleware(serverName string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
span, newCtx := opentracing.StartSpanFromContext(r.Context(), serverName)
defer func() {
span.Finish()
}()
r = r.WithContext(newCtx)
h.ServeHTTP(w, r)
})
}

Thus in all we get 3 spans, 1st span is of server-1, 2nd span is from server-1 to server-2 and 3rd span of server 2. The opentracing package used is Jaeger.

Spans

Jaeger is run via Docker using the following command:

docker run -d -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 -p16686:16686 -p14268:14268 -p9411:9411 jaegertracing/all-in-one:latest

or it can also be run using:

docker run -d -p 5775:5775/udp -p 16686:16686 jaegertracing/all-in-one:latest

Jaeger-UI

In Linux/Ubuntu, do remember to prefix sudo with these commands. Now the Jaeger UI runs at http://localhost:16686 All the codes in Golang.

--

--

Shreya Singh

Software Development Engineer at Amazon, Google APAC Women Techmaker Scholar, C.S.E. Graduate @ NIT Patna