Skip to content

Easily add health checks to your go services

License

Notifications You must be signed in to change notification settings

manifoldco/healthz

Healthz

Code of Conduct | Contribution Guidelines

GitHub release GoDoc Build Status Go Report Card License

This is a package that allows you to set up health checks for your services. By default, the health checks will be available at the /_healthz endpoint but can be configured with the Prefix and Endpoint variables.

By default, the health check package will add a single default test. This test doesn't validate anything, it simply returns no error.

Registering additional tests

If you want to add more tests in case your service/worker depends on a specific set of tools (like a JWT key mounted on a volume), you can register a new test as follows:

func init() {
	healthz.RegisterTest("jwt-key", jwtCheck)
}

func jwtCheck(ctx context.Context) error {
	_, err := os.Stat("/jwt/ecdsa-private.pem")
	return err
}

Attaching the endpoints to an existing server

If you have an existing server running, you can attach the /_healthz endpoint to it without having to start a separate server.

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	handler := healthz.NewHandler(mux)
	http.ListenAndServe(":3000", handler)
}

This will create a new mux which listens to the /hello request. We then attach the healthcheck handler by using healthz.NewHandler(mux).

Creating a standalone server

When your application isn't a web server, but you still want to add health checks, you can use the provided server implementation.

func main() {
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)

	srv := healthz.NewServer("0.0.0.0", 3000)
	go srv.Start()

	<-stop

	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
	srv.Shutdown(ctx)
}

Using middleware

With both the NewHandler and NewServer implementation, we've provided a way to add middleware. This allows you to add logging to your health checks for example.

The middleware functions are respectively NewHandlerWithMiddleware and NewServerWithMiddleware. They accept the arguments of their parent function but also a set of middlewares as variadic arguments.

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	handler := healthz.NewHandlerWithMiddleware(mux, logMiddleware)
	http.ListenAndServe(":3000", handler)
}

func logMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Print("start request")
		next.ServeHTTP(w, r)
		log.Print("end request")
	})
}

Output

On the health check endpoint, we return a set of values useful to us. Extending the example from above, these are both return values (one where the file is present, one where it isn't).

200 OK

{
  "checked_at": "2017-11-22T14:18:50.339Z",
  "duration_ms": 0,
  "result": "success",
  "tests": {
    "default": {
      "duration_ms": 0,
      "result": "success"
    },
    "jwt-key": {
      "duration_ms": 0,
      "result": "success"
    }
  }
}

503 Service Unavailable

{
  "checked_at": "2017-11-22T14:18:50.339Z",
  "duration_ms": 1,
  "result": "failure",
  "tests": {
    "default": {
      "duration_ms": 0,
      "result": "success"
    },
    "jwt-key": {
      "duration_ms": 0,
      "result": "failure"
    }
  }
}

Middlewares

We've included a set of standard middlewares that can be useful for general use.

Cache

The cache middleware allows you to cache a response for a specific duration. This prevents the health check to overload due to different sources asking for the health status. This is especially useful when the health checks are used to check the health of other services as well.

To use this middleware, simply add it to the chain:

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	handler := healthz.NewHandlerWithMiddleware(
		mux,
		healthz.CacheMiddleware(5*time.Second),
	)
	http.ListenAndServe(":3000", handler)
}