Monitoring Golang Web App with Application Insights

Continue from the previous topic

Application Insights is available on Azure Portal as a way for developers to monitor their live web applications and to detect performance anomalies. It has a dashboard with charts to help developers diagnose issues and understand user behaviors on the applications. It works for apps written on multiple programming languages other than .NET too.

Setup of Application Insights on Azure

It is straightforward to setup Application Insights on Azure Portal. If we have already setup a default CI/CD for simple Golang web app, an Application Insights account will be created automatically.

Creating new Application Insights account. The golab002 is automatically created when we setup a new Golang DevOps project on Azure Portal.

Once the Application Insights account is created, we need to get its Instrument Key which is required before any telemetry can be sent via the SDK.

Simplicity in ASP .NET Core

In ASP .NET Core projects, we can easily include Application Insights by including the Nuget package Microsoft.ApplicationInsights.AspNetCore and adding the following highlighted code in Program.cs.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup()
.UseApplicationInsights();

Setup of Application Insights Telemetry in Golang

So, what if we want to monitor our Golang applications which are hosted on Azure App Service? Luckily, Microsoft officially published an Application Insights SDK for Golang which is also open sourced on GitHub.

Since June 2015, Luke Kim, the Principal Group Software Engineering Manger at Microsoft, and other Microsoft engineers have been working on this open source project.

Introducing Application Insights to Golang application is not as straightforward as doing the same in ASP .NET Core project described above. Here, I will cover only how to use Telemetry.

First of all, we need to download and install the relevant package with the following go get command.

go get github.com/Microsoft/ApplicationInsights-Go/appinsights

Tracing Errors

Previously, we already have a centralized checkError function to handle errors returned from different sources in our code. So, we will have the following code added in the function to send traces back to Application Insights when an error occurs.

func checkError(err error) {
    if err != nil {
        client := appinsights.NewTelemetryClient(os.Getenv("APPINSIGHTS_INSTRUMENTATIONKEY"))

        trace := appinsights.NewTraceTelemetry(err.Error(), appinsights.Error)
        trace.Timestamp = time.Now()

        client.Track(trace)

        panic(err)    
}
}

So, when there is an error on our application, we will receive a trace record as such on the Metrics of Application Insights as shown below.

An error is captured. In this case, it’s because wrong DB host is stated.

However, doing this way doesn’t give us details such as call stack. Hence, if we want to log an exception in our application, we need to use TrackPanic in the SDK as follows.

func checkError(err error) {
    if err != nil {
        client := appinsights.NewTelemetryClient(os.Getenv("APPINSIGHTS_INSTRUMENTATIONKEY"))

        trace := appinsights.NewTraceTelemetry(err.Error(), appinsights.Error)
        trace.Timestamp = time.Now()

        client.Track(trace)

        // false indicates that we should have this handle the panic, and
        // not re-throw it.
        defer appinsights.TrackPanic(client, false)

        panic(err)    
}
}

This will capture and report call stack of the panic and display it on Azure Portal. With this, we can easily see which exceptions are occurring and how often.

Traces and exceptions. The details of exception includes call stack.

Tracing Page Views

Besides errors, let’s capture the page views as well so that we can easily tell which handler function is called and how much time is spent in it. To do so, we introduce a new function called handleRequestWithLog.

func handleRequestWithLog(h func(http.ResponseWriter, *http.Request)) http.HandlerFunc {

    return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {

        startTime := time.Now()
        h(writer, request)
        duration := time.Now().Sub(startTime)

        client := appinsights.NewTelemetryClient(
os.Getenv("APPINSIGHTS_INSTRUMENTATIONKEY"))

        trace := appinsights.NewRequestTelemetry(
request.Method, request.URL.Path, duration, "200")
        trace.Timestamp = time.Now()

        client.Track(trace)
    })
}

Then we can modify our server.go to be as follows.

mux.HandleFunc("/", handleRequestWithLog(index))
mux.HandleFunc("/addVideo", handleRequestWithLog(addVideo))
mux.HandleFunc("/updateVideo", handleRequestWithLog(updateVideo))
mux.HandleFunc("/deleteVideo", handleRequestWithLog(deleteVideo))

Now whenever we visit a page or perform an action, the behaviour will be logged on Application Insights, as shown in the following screenshot. As you can see, the server response time is logged too.

Adding new video, deleting video, and viewing homepage actions.

With these records, the Performance chart in Application Insights will be plotted too.

Monitoring the performance of our Golang web application.

Tracing Static File Downloads

Besides the web pages, we are also interested at static files, such as understanding how fast the server responses when the static file is retrieved.

To do so, we first need to introduce a new handler function called staticFile.go.

package main

import (
    "mime"
    "net/http"
    "strings"
)

func staticFile(writer http.ResponseWriter, request *http.Request) {
    urlComponents := strings.Split(request.URL.Path, "/")

    http.ServeFile(
writer, request, "public/"+urlComponents[len(urlComponents)-1])

    fileComponents := strings.Split(
urlComponents[len(urlComponents)-1], ".")
    fileExtension := fileComponents[len(fileComponents)-1]

    writer.Header().Set(
"Content-Type", mime.TypeByExtension(fileExtension))
}

The reason why we need do as such is because we want to apply the handleRequestWithLog function for static files in server.go too.

mux.HandleFunc("/static/", handleRequestWithLog(staticFile))

By doing so, we will start to see the following on Search of Application Insights.

A list of downloaded CSS and JS static files and their corresponding server response time.

Conclusion

In ASP .NET Core applications, we normally need add the UseApplicationInsights as shown below in Program.cs then all the server actions will be automatically traced. However, this is not the case for Golang applications where there is no such convenience.

References

  1. What is Application Insights;
  2. Exploring Metrics in Application Insights;
  3. In Golang, how to convert an error to a string?
  4. [Stack Overflow] How to get URL in http.Request?
  5. [Stack Overflow] How to get request string and method?
  6. [Stack Overflow] Golang http handler – time taken for request;
  7. [golang.org] func Split;
  8. Find the Length of an Array/Slice;
  9. [GitHub] Microsoft Application Insights SDK for Go;
  10. Golang 1.6: 使用mime.TypeByExtension来设置Content-Type;
  11. [Stack Overflow] What to use? http.ServeFile(..) or http.FileServer(..)?
  12. [Stack Overflow] How do you serve a static html file using a go web server?

#application-insights, #azure, #golang