Skip to content

A go implementation of the Health Checks API used for microservice exploration, documentation and monitoring.

License

Notifications You must be signed in to change notification settings

hootsuite/healthchecks

Repository files navigation

go healthchecks

Introduction

A go implementation of the Health Checks API used for microservice exploration, documentation and monitoring.

How to Use It

Using the healthchecks framework in your service is easy.

  • Define a StatusEndpoint for each dependency in your service.
  • Register the healthchecks framework to respond to all /status/... requests passing a slice of all your StatusEndpoints.
  • That's it! As long as you have defined your StatusEndpoints correctly, the framework will take care of the rest.

Example:

// Define a StatusEndpoint at '/status/db' for a database dependency
db := healthchecks.StatusEndpoint{
  Name: "The DB",
  Slug: "db",
  Type: "internal",
  IsTraversable: false,
  StatusCheck: sqlsc.SQLDBStatusChecker{
    DB: myDB
  },
  TraverseCheck: nil,
}

// Define a StatusEndpoint at '/status/service-organization' for the Organization service
org := healthchecks.StatusEndpoint{
  Name: "Organization Service",
  Slug: "service-organization",
  Type: "http",
  IsTraversable: true,
  StatusCheck: httpsc.HttpStatusChecker{
    BaseUrl: "[Read value from config]",
  },
  TraverseCheck: httpsc.HttpStatusChecker{
    BaseUrl: "[Read value from config]",
  },
}

// Define the list of StatusEndpoints for your service
statusEndpoints := []healthchecks.StatusEndpoint{ db, org }

// Set the path for the about and version files
aboutFilePath := "conf/about.json"
versionFilePath := "conf/version.txt"

// Set up any service injected customData for /status/about response.
// Values can be any valid JSON conversion and will override values set in about.json.
customData := make(map[string]interface{})
// Examples:
//
// String value
// customData["a-string"] = "some-value"
//
// Number value
// customData["a-number"] = 123
//
// Boolean value
// customData["a-bool"] = true
//
// Array
// customData["an-array"] = []string{"val1", "val2"}
//
// Custom object
// customObject := make(map[string]interface{})
// customObject["key1"] = 1
// customObject["key2"] = "some-value"
// customData["an-object"] = customObject

// Register all the "/status/..." requests to use our health checking framework
http.Handle("/status/", healthchecks.Handler(statusEndpoints, aboutFilePath, versionFilePath, customData))

Writing a StatusCheck

A StatusCheck is a struct which implements the function func CheckStatus(name string) StatusList. A StatusCheck is defined or used in a service but executed by the healthchecks framework. The key to a successful StatusCheck is to handle all errors on the dependency you are checking. Below is an example of a StatusCheck that checks the connection of Redis using the gopkg.in/redis.v4 driver.

type RedisStatusChecker struct {
	client RedisClient
}

func (r RedisStatusChecker) CheckStatus(name string) healthchecks.StatusList {
	pong, err := r.client.Ping()

	// Set a default response
	s := healthchecks.Status{
		Description:  name,
		Result: healthchecks.OK,
		Details: "",
	}

	// Handle any errors that Ping() function returned
	if err != nil {
		s = healthchecks.Status{
			Description:  name,
			Result: healthchecks.CRITICAL,
			Details: err.Error(),
		}
	}

	// Make sure the pong response is what we expected
	if pong != "PONG" {
		s = healthchecks.Status{
			Description:  name,
			Result: healthchecks.CRITICAL,
			Details: fmt.Sprintf("Expecting `PONG` response, got `%s`", pong),
		}
	}

	// Return our response
	return healthchecks.StatusList{ StatusList: []healthchecks.Status{ s }}
}

Writing a TraverseCheck

A TraverseCheck is a struct which implements the function func Traverse(traversalPath []string, action string) (string, error). A TraverseCheck is defined or used in a service but executed by the healthchecks framework. The key to a successful TraverseCheck is to build and execute the /status/traverse?action=[action]&dependencies=[dependencies] request to the service you are trying to traverse to and returning the response or error you got. Below is an example of a TraverseCheck for an HTTP service.

type HttpStatusChecker struct {
	BaseUrl string
	Name    string
}

func (h HttpStatusChecker) Traverse(traversalPath []string, action string) (string, error) {
	dependencies := ""
	if len(traversalPath) > 0 {
		dependencies = fmt.Sprintf("&dependencies=%s", strings.Join(traversalPath, ","))
	}

	// Build our HTTP request
	url := fmt.Sprintf("%s/status/traverse?action=%s%s", h.BaseUrl, action, dependencies)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		fmt.Printf("Error creating request: %s \n", err.Error())
		return "", err
	}

	// Execute HTTP request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("Error executing request: %s \n", err.Error())
		return "", err
	}

	// Defer the closing of the body
	defer resp.Body.Close()

	// Read our response
	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("Error reading response body: %s", err.Error())
		return "", err
	}

	// Return our response
	return string(responseBody), nil
}

How To Contribute

Contribute by submitting a PR and a bug report in GitHub.

License

healthchecks is released under the Apache License, Version 2.0. See LICENSE for details.

Maintainers