Let's Go Further Metrics › Exposing metrics with expvar
Previous · Contents · Next
Chapter 18.1.

Exposing metrics with expvar

Viewing metrics for our application is made easy by the fact that the expvar package provides an expvar.Handler() function which returns an HTTP handler exposing your application metrics.

By default this handler displays information about memory usage, along with a reminder of what command-line flags you used when starting the application, all outputted in JSON format.

So the first thing that we’re going to do is mount this handler at a new GET /debug/vars endpoint, like so:

Method URL Pattern Handler Action
GET /debug/vars expvar.Handler() Display application metrics
File: cmd/api/routes.go
package main

import (
    "expvar" // New import
    "net/http"

    "github.com/julienschmidt/httprouter"
)

func (app *application) routes() http.Handler {
    router := httprouter.New()

    router.NotFound = http.HandlerFunc(app.notFoundResponse)
    router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse)

    router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler)

    router.HandlerFunc(http.MethodGet, "/v1/movies", app.requirePermission("movies:read", app.listMoviesHandler))
    router.HandlerFunc(http.MethodPost, "/v1/movies", app.requirePermission("movies:write", app.createMovieHandler))
    router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.requirePermission("movies:read", app.showMovieHandler))
    router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.requirePermission("movies:write", app.updateMovieHandler))
    router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.requirePermission("movies:write", app.deleteMovieHandler))

    router.HandlerFunc(http.MethodPost, "/v1/users", app.registerUserHandler)
    router.HandlerFunc(http.MethodPut, "/v1/users/activated", app.activateUserHandler)

    router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication", app.createAuthenticationTokenHandler)

    // Register a new GET /debug/vars endpoint pointing to the expvar handler.
    router.Handler(http.MethodGet, "/debug/vars", expvar.Handler())

    return app.recoverPanic(app.enableCORS(app.rateLimit(app.authenticate(router))))
}

OK, let’s try this out.

Go ahead and restart the API, passing in a couple of command-line flags for demonstration purposes. Like so:

$ go run ./cmd/api -limiter-enabled=false -port=4000
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established"
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development

And if you visit http://localhost:4000/debug/vars in your web browser, you should see a JSON response containing information about your running application.

In my case, the response looks like this:

18.01-01.png

We can see that the JSON here currently contains two top-level items: "cmdline" and "memstats". Let’s quickly talk through what these represent.

The "cmdline" item contains an array of the command-line arguments used to run the application, beginning with the program name. This is essentially a JSON representation of the os.Args variable, and it’s useful if you want to see exactly what non-default settings were used when starting the application.

The "memstats" item contains a ‘moment-in-time’ snapshot of memory usage, as returned by the runtime.MemStats() function. Documentation and descriptions for all of the values can be found here, but the most important ones are: