-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
789b1ef
commit b525079
Showing
6 changed files
with
512 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace tool_heartbeat; | ||
|
||
use core\check\check; | ||
use core\check\result; | ||
use Throwable; | ||
|
||
/** | ||
* Check API checker class | ||
* | ||
* Processes check API results and returns them in a nice format for nagios output. | ||
* | ||
* @package tool_heartbeat | ||
* @author Matthew Hilton <[email protected]> | ||
* @copyright 2023, Catalyst IT | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class checker { | ||
/** @var array Nagios level prefixes **/ | ||
public const NAGIOS_PREFIXES = [ | ||
0 => "OK", | ||
1 => "WARNING", | ||
2 => "CRITICAL", | ||
3 => "UNKNOWN", | ||
]; | ||
|
||
/** | ||
* Returns an array of check API messages. | ||
* If exceptions are thrown, they are caught and returned as result messages as well. | ||
* | ||
* @return array array of resultmessage objects | ||
*/ | ||
public static function get_check_messages(): array { | ||
// First try to get the checks, if this fails return a critical message (code is very broken). | ||
$checks = []; | ||
|
||
try { | ||
$checks = \core\check\manager::get_checks('status'); | ||
} catch (Throwable $e) { | ||
return [self::exception_to_message("Error getting checks: ", $e)]; | ||
} | ||
|
||
// Execute each check and store their messages. | ||
$messages = []; | ||
|
||
foreach ($checks as $check) { | ||
try { | ||
$messages[] = self::process_check_and_get_result($check); | ||
} catch (Throwable $e) { | ||
$messages[] = self::exception_to_message("Error processing check " . $check->get_ref() . ": ", $e); | ||
} | ||
} | ||
|
||
return $messages; | ||
} | ||
|
||
/** | ||
* Turns the given exception into a warning resultmessage. | ||
* @param string $prefix | ||
* @param Throwable $e | ||
* @return resultmessage | ||
*/ | ||
private static function exception_to_message(string $prefix, Throwable $e): resultmessage { | ||
$res = new resultmessage(); | ||
$res->level = resultmessage::LEVEL_WARN; | ||
$res->title = $prefix . $e->getMessage(); | ||
$res->message = (string) $e; | ||
return $res; | ||
} | ||
|
||
/** | ||
* Processes the check and maps its result and status to a resultmessage. | ||
* @param check $check | ||
* @return resultmessage | ||
*/ | ||
private static function process_check_and_get_result(check $check): resultmessage { | ||
$res = new resultmessage(); | ||
|
||
$checkresult = $check->get_result(); | ||
|
||
// Map check result to nagios level. | ||
$map = [ | ||
result::WARNING => resultmessage::LEVEL_WARN, | ||
result::CRITICAL => resultmessage::LEVEL_CRITICAL, | ||
result::OK => resultmessage::LEVEL_OK, | ||
result::NA => resultmessage::LEVEL_OK, | ||
result::WARNING => resultmessage::LEVEL_WARN, | ||
result::UNKNOWN => resultmessage::LEVEL_UNKNOWN, | ||
result::ERROR => resultmessage::LEVEL_CRITICAL, | ||
]; | ||
|
||
// Get the level, or default to unknown. | ||
$status = $checkresult->get_status(); | ||
$res->level = isset($map[$status]) ? $map[$status] : resultmessage::LEVEL_UNKNOWN; | ||
|
||
$res->title = $check->get_name(); | ||
|
||
// Add the first line of summary to the title. | ||
$summarylines = explode("\n", $checkresult->get_summary()); | ||
$res->title .= ": " . $summarylines[0] ?: ''; | ||
|
||
$res->message = $checkresult->get_summary(); | ||
|
||
return $res; | ||
} | ||
|
||
/** | ||
* From an array of resultmessage, determines the highest nagios level. | ||
* Note, it considers UNKNOWN to be less than CRITICAL or WARNING. | ||
* | ||
* @param array $messages array of resultmessage objects | ||
* @return int the calculated nagios level | ||
*/ | ||
public static function determine_nagios_level(array $messages): int { | ||
// Find the highest level. | ||
$levels = array_column($messages, "level"); | ||
|
||
// Add a default "OK" in case no messages were returned. | ||
$levels[] = resultmessage::LEVEL_OK; | ||
|
||
$hasunknown = !empty(array_filter($levels, function($l) { | ||
return $l == resultmessage::LEVEL_UNKNOWN; | ||
})); | ||
|
||
// Remove unknowns. | ||
$levels = array_filter($levels, function($l) { | ||
return $l != resultmessage::LEVEL_UNKNOWN; | ||
}); | ||
|
||
$highest = max($levels); | ||
|
||
// If highest was OK but it had an unknown, return unknown. | ||
// This stops UNKNOWN from masking WARNING or CRITICAL. | ||
if ($highest == resultmessage::LEVEL_OK && $hasunknown) { | ||
return resultmessage::LEVEL_UNKNOWN; | ||
} | ||
|
||
// Else return OK. | ||
return $highest; | ||
} | ||
|
||
/** | ||
* Creates a summary from the given messages. | ||
* If there are no messages or only OK, OK is returned. | ||
* If there is a single message, its details are returned. | ||
* If there are multiple messages, the levels are aggregated and turned into a summary. | ||
* | ||
* @param array $messages array of resultmessage objects | ||
* @return string | ||
*/ | ||
public static function create_summary(array $messages): string { | ||
// Filter out any OK messages. | ||
$messages = array_filter($messages, function($m) { | ||
return $m->level != resultmessage::LEVEL_OK; | ||
}); | ||
|
||
// If no messages, return OK. | ||
if (count($messages) == 0) { | ||
return "OK"; | ||
} | ||
|
||
// If only one message, use it as the top level. | ||
if (count($messages) == 1) { | ||
return $messages[0]->title; | ||
} | ||
|
||
// Otherwise count how many of each level. | ||
$counts = array_count_values(array_column($messages, 'level')); | ||
|
||
$countswithprefixes = []; | ||
foreach ($counts as $level => $occurrences) { | ||
$prefix = self::NAGIOS_PREFIXES[$level]; | ||
$countswithprefixes[] = "{$occurrences} {$prefix}"; | ||
} | ||
|
||
return "Multiple problems detected: " . implode(", ", $countswithprefixes); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace tool_heartbeat; | ||
|
||
/** | ||
* A data-only class for holding a message about a result from a check API class. | ||
* | ||
* @package tool_heartbeat | ||
* @author Matthew Hilton <[email protected]> | ||
* @copyright 2023, Catalyst IT | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class resultmessage { | ||
/** @var int OK level **/ | ||
public const LEVEL_OK = 0; | ||
|
||
/** @var int WARN level **/ | ||
public const LEVEL_WARN = 1; | ||
|
||
/** @var int CRITICAL level **/ | ||
public const LEVEL_CRITICAL = 2; | ||
|
||
/** @var int UNKNOWN level **/ | ||
public const LEVEL_UNKNOWN = 3; | ||
|
||
/** @var int $level The level of this message **/ | ||
public $level = self::LEVEL_UNKNOWN; | ||
|
||
/** @var string $title Title of the message **/ | ||
public $title = ''; | ||
|
||
/** @var string $message Details of this message **/ | ||
public $message = ''; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* Check API Health Check | ||
* | ||
* @package tool_heartbeat | ||
* @copyright 2023 Matthew Hilton <[email protected]> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
* | ||
* This can be run either as a web api, or on the CLI. When run on the | ||
* CLI it conforms to the Nagios plugin standard. | ||
* | ||
* See also: | ||
* - http://nagios.sourceforge.net/docs/3_0/pluginapi.html | ||
* - https://nagios-plugins.org/doc/guidelines.html#PLUGOUTPUT | ||
* | ||
*/ | ||
|
||
use tool_heartbeat\checker; | ||
use tool_heartbeat\resultmessage; | ||
|
||
// @codingStandardsIgnoreStart | ||
define('NO_UPGRADE_CHECK', true); | ||
|
||
// Start output buffering. This stops for e.g. debugging messages from breaking the output. | ||
// When a nagios.php send_* function is called, they will collect the buffer | ||
// and warn if it is not empty (but do it nicely). | ||
ob_start(); | ||
|
||
$dirroot = __DIR__ . '/../../../'; | ||
require($dirroot.'config.php'); | ||
require_once(__DIR__.'/nagios.php'); | ||
|
||
global $PAGE; | ||
|
||
if (isset($CFG->mnet_dispatcher_mode) and $CFG->mnet_dispatcher_mode !== 'off') { | ||
// This is a core bug workaround, see MDL-77247 for more details. | ||
require_once($CFG->dirroot.'/mnet/lib.php'); | ||
} | ||
|
||
$messages = checker::get_check_messages(); | ||
|
||
// Filter out any OK ones, we only care about the others. | ||
$messages = array_filter($messages, function($m) { | ||
return $m->level != resultmessage::LEVEL_OK; | ||
}); | ||
|
||
// Construct the output message. | ||
$PAGE->set_context(\context_system::instance()); | ||
|
||
// Indent the messages. | ||
$msg = array_map(function($message) { | ||
global $OUTPUT; | ||
|
||
// Indent messages. | ||
$spacer = " "; | ||
$indentedmessage = $spacer . str_replace("\n", "\n" . $spacer, $message->message); | ||
|
||
return $OUTPUT->render_from_template('tool_heartbeat/resultmessage', [ | ||
'prefix' => checker::NAGIOS_PREFIXES[$message->level], | ||
'title' => $message->title, | ||
'message' => nl2br($indentedmessage), | ||
]); | ||
}, $messages); | ||
|
||
$summary = checker::create_summary($messages); | ||
$msg = $summary . "<br />" . implode("", $msg); | ||
|
||
$level = checker::determine_nagios_level($messages); | ||
|
||
send($level, $msg); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
|
||
{{! | ||
This file is part of Moodle - https://moodle.org/ | ||
Moodle is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
Moodle is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with Moodle. If not, see | ||
<http: //www.gnu.org/licenses />. | ||
}} | ||
{{! | ||
@template tool_heartbeat/resultmessage | ||
Template by JS to render output of result report getting (loading, url, error) | ||
Classes required for JS: | ||
* none | ||
Context variables required for this template: | ||
* none | ||
Example context (json): | ||
{ | ||
"prefix": "CRTIICAL", | ||
"title": "Something broke", | ||
"message": "Some more details" | ||
} | ||
}} | ||
|
||
* {{prefix}} {{title}}<br/> | ||
{{{message}}}<br/> | ||
<br /> |
Oops, something went wrong.