Web
banshee/web-collection/web/https/
api-health
The health endpoint of a web service: status code, response time, declared health and version, and the health of each dependency.
by banshee1 installation2 versions
// The health endpoint of a web service, behind a token or not: its status code, its response time, the
// health and the version it declares, and the health of each of its dependencies. It reads the common forms:
// {"status": "UP", "components": {...}} (Spring Boot), {"status": "pass", "checks": {...}}, or a plain-text
// "Healthy" (ASP.NET Core).
need('protocol:http');
$token = need('protocol:http:token', 'optional');
$url = variable('web:url', 'text', array(
'label' => 'Health endpoint address',
'help' => 'The full address, for instance https://api.example/health.',
));
per('dependency', 'Dependencies');
output('code', 'number', array('label' => 'Status code'));
output('response-time', 'number', array('label' => 'Response time', 'unit' => 'ms'));
output('health', 'text', array('label' => 'Declared health'));
output('version', 'text', array('label' => 'Declared version'));
output('dependency', 'text', array('label' => 'Health of the dependency', 'per' => 'dependency'));
watch.expect('code', 'Answer in error', 'code', array('critical' => array(200)));
watch.above('slow', 'Slow service', 'response-time', array('warning' => 500, 'critical' => 2000, 'for' => '3 runs'));
watch.expect('unhealthy', 'Service not healthy', 'health', array('warning' => array('ok')));
watch.state('down', 'Service down', 'health', array('critical' => array('down')));
watch.state('dependency-down', 'Dependency down', 'dependency', array('critical' => array('down')));
watch.expect('dependency-unhealthy', 'Dependency not healthy', 'dependency', array('warning' => array('ok')));
watch.missing('dependency-missing', 'Dependency missing', 'dependency', array('enabled' => false));
watch.expect('version', 'Unexpected version', 'version', array(
'help' => 'Raises when the service declares a version other than those given, for instance after a deployment that should not have happened.',
'enabled' => false,
));
// A declared health in one word: "ok" for a service in good health, "down" for a service out, any other
// word as the service writes it, in lower case ("degraded", "warn").
function health_word($status) {
$word = text.lower($status);
if ($word == 'ok' || $word == 'up' || $word == 'pass' || $word == 'healthy') {
return 'ok';
}
if ($word == 'down' || $word == 'fail' || $word == 'error' || $word == 'out_of_service' || $word == 'unhealthy') {
return 'down';
}
return $word;
}
// The token goes in a header: the answer is not secret, its body having carried none. A service that
// refuses the connection, or does not answer in time, is down: critical, rather than unreachable.
$headers = array();
if ($token != '') {
$headers['Authorization'] = 'Bearer ' . $token;
}
$answer = http.get($url, headers=$headers, errors=$err);
if ($err['code'] == 'connection_refused' || $err['code'] == 'timeout') {
fail('critical', 'Service down', $err['text']);
}
if ($err['code'] != '') {
fail('unreachable', 'Service unreachable', $err['text']);
}
value('code', $answer['status']);
value('response-time', $answer['time']);
// The body is read whatever the code: a service that says it is down often answers 503 with its health.
// Read only when its type says JSON or plain text: an error page of a proxy says neither.
$type = text.lower(list.get($answer['headers'], 'content-type', ''));
$health = 'unreadable';
$version = 'unreadable';
if (text.contains($type, 'json')) {
$body = json.read($answer['body']);
$health = 'missing';
if (list.has($body, 'status')) {
$health = health_word($body['status']);
}
$version = list.get($body, 'version', 'missing');
$checks = list.get($body, 'checks', list.get($body, 'components', array()));
foreach ($checks as $name => $check) {
$state = 'missing';
if (list.has($check, 'status')) {
$state = health_word($check['status']);
}
value('dependency', $state, $name);
}
} elseif (text.contains($type, 'text/plain')) {
$health = health_word(text.trim($answer['body']));
$version = 'missing';
}
value('health', $health);
value('version', $version);