Go, or like many people also call it Golang, is a general-purpose language created and backed by Google, but developed in the open. Started back in 2009, Go reached version 1.0 in 2012 and is available in version 1.12.1 as of the day of writing.
Being used in quite a few bigger systems like Docker and Kubernetes, Go is the prime language to implement microservices, as well as gateway or proxy implementations. That said, programs written in Go become a major contributor to today’s highly distributed and extremely dynamic application infrastructures. It also means that monitoring those integral parts of the systems is critically important too.
Instana provides simple and straightforward monitoring for applications written in Go. In comparison to systems running on virtual machines (like Java, .NET, Ruby, …) though, Go is a natively compiled language. Full automatic instrumentation of Go processes is therefore neither implemented in Instana, nor recommended, as changes require deep changes in the programs method lookup table. We also understand, that Go developer – like all developers using natively compiled languages – prefer to have more control. Therefore Instana makes integration a simple, one-shot operation for development teams, by still providing full control over operations or requests being traced or not.
The Instana Go Sensor
The Instana Go Sensor provides a quick and easy API, using middleware functionalities built into the Go standard library.
Based on the OpenTracing API, Instana users always have the possibility to create their own traces using the API offered by OpenTracing. To ease the integration and handle the distributed specifics of Instana, a small helper library available from Github is provided by Instana. It can be used just as any other known Go lib, by just adding it as a dependency.
import "github.com/instana/golang-sensor"
The API is meant to be as simple as possible and supports the most common use cases like request handling and http client requests.
To see how to integrate Instana into Go applications, we will create a small proxy application that uses the Instana Go Sensor API to trace incoming and outgoing http calls.
To begin with we need to create an instana.Sensor instance. It is recommended to create one per application and store it into a public free variable.
var sensor = instana.NewSensor("go-proxy-demo")
The instana.Sensor instance itself already provides the basic information about the Go process into Instana’s Dashboard, which include metrics like Garbage Collection and memory or CPU usage.
However Instana can do more. The real benefit for developers are comprehensive, end-to-end traces throughout all the systems infrastructure.
That said, now that we have a instana.Sensor instance, we are able to build what makes a proxy a proxy, request handling and forwarding.
But before we get into the Go Sensor specifics, we need a bit of preparation to make our application behave like a simple proxy (not a real reverse proxy though). To make things easier we use a single Go application to handle the original and the proxied request using two different request handlers.
That said, we need a http.Client instance and one small helper method, which copies the original http.Request.
var client = http.Client{} func adaptRequest(req *http.Request, uri string) (*http.Request, error) { copy := new(http.Request) *copy = *req u, err := url.Parse(uri) if err != nil { return nil, err } copy.URL = u copy.RequestURI = "" return copy, nil }
Done. Now we can create a method to handle the proxied request. The handler does nothing else than answering any request with a plain Hello World.
func proxiedRequestHandler(w http.ResponseWriter, req *http.Request) { http.ServeContent(w, req, "index", time.Now(), strings.NewReader("Hello World"), ) }
The actual request handler doesn’t have to be modified from standard Go handler functions. To integrate the request handler into Instana’s distributed tracing capabilities, we now register the handler, by wrapping it into an Instana TracingHandler provided by the instana.Sensor instance.
func main() { http.HandleFunc("/bar", sensor.TracingHandler("bar", proxiedRequestHandler), ) http.ListenAndServe(":8080", nil) }
The above code creates a tracing request handler, managing all Instana specific behavior and passing along the request to the user provided, wrapped request handler. Finally, we’re listening for requests. That’s all to make sure that proxied requests get into Instana.
And, for your own convenience, you can even simplify it slightly more and squeeze initialization and registration into a single command.
func main() { http.HandleFunc( sensor.TraceHandler("bar", "/bar", proxiedRequestHandler), ) http.ListenAndServe(":8080", nil) }
The actual request proxying is as easy as the basic request tracing, it just is a twofold step, trace the incoming and tell Instana about the outgoing request.
func proxyRequestHandler(w http.ResponseWriter, origReq *http.Request) { proxyReq, _ := adaptRequest(origReq, "http://localhost:8080/bar") resp, _ := sensor.TracingHttpRequest("proxy", origReq, proxyReq, client) resp.Write(w) w.Write([]byte("nAdded by the Proxy")) }
That time we use a little bit more code, however only a few lines are really special here. We already know about the adaptRequest function, we created before. It is used to copy and adjust our request for sending it onwards.
The really interesting bit is the sensor.TracingHttpRequest which takes multiple parameters. The first parameter defines the name for the trace in the Instana Dashboard, then the original request as it includes all tracing headers to read, the new request which will be patched to include the tracing headers, and the http.Client we created before. The client is used to execute the http.Request. In our case we just wrapped the existing request and adjusted the URL.
All the necessary pieces to make this an external call appear in Instana are handled by the underlying sensor implementation.
The last step is to add the proxyRequestHandler to our already existing http server.
http.HandleFunc( sensor.TraceHandler("foo", "/foo", proxyRequestHandler), )
Starting the application and creating the instana.Sensor instance makes the Go application connect to the locally running Instana agent.
Calling http://localhost:8080/foo will pass the request onwards to http://localhost:8080/bar via the http request we created and executed. The traces are now visible from the Instana Dashboard.
We’ve seen that the integration of Instana into Go applications, even though not fully automatic, is quick and easy. The minimal amount of code that has to be added, as well as few changes necessary to existing applications are the most pleasant experience to add monitoring today.
The project code can be found in the examples of the Instana Go Sensor.