How to capture HTTP status codes in Go

Published on Apr 1, 2019

Having just started writing your first HTTP service in Go you might be thinking "How can I log the status code of the response?". Good question! In Go there is no built in feature to achieve this - but thanks to embedding, it is in fact much much easier than one might think!

To capture the status code for a HTTP response we just have to decorate the http.ResponseWriter for the current request, while also implementing the http.ResponseWriter interface in net/http.

To do this we create a StatusCodeRecorder struct. However, you can call it whatever you like. The name is not important provided it is descriptive and consise.

type StatusCodeRecorder struct {
    http.ResponseWriter
    statusCode int
}

This struct has two roles: first to pass around the http.ResponseWriter for the current request, and second to decorate the call to WriteHeader and store a copy of the status code inside the StatusCodeRecorder struct. The decorated WriteHeader looks like this:

func (r *StatusCodeRecorder) WriteHeader(statusCode int) {
    r.statusCode = statusCode
    r.ResponseWriter.WriteHeader(statusCode)
}

There is need to define the other two methods for the http.ResponseWriter interface as they are inheritied by embedding http.ResponseWriter in the definition of the struct.

Last we just need to create an HTTP handler to log the status code using the StatusCodeRecorder we created earlier.

func statusCodeLogger func(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        rw := StatusCodeRecorder{w: w}
        next.ServeHTTP(rw, r)
        log.Printf("%v: HTTP %v", r.RequestURI, rw.statusCode)
    }
}

You could easily write a different handler which, instead of calling log.Printf, emits the status code as a metric in Prometheus.

We can now start our HTTP server, using the statusCodeLogger handler as a middleware handler for some other http.HandlerFunc:

http.HandleFunc("/", statusCodeLogger(func(
    w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
}))
http.ListenAndServe("127.0.0.1:8080", nil)