QWE Posted February 25 Posted February 25 кто что пользует? или ставит на телефон почтового клиента и делать отдельный zabbix-alert ящик сотруднику? Вставить ник Quote
jffulcrum Posted February 25 Posted February 25 IntelliTrend Mobile for Zabbix Когда-то за бабки покупал, но сейчас там есть бесплатный режим Вставить ник Quote
tcup Posted February 26 Posted February 26 На всякий случай поставил openfire, прикрутил скрипт отсылки через XMPP к Zabbix Вставить ник Quote
nixx Posted February 26 Posted February 26 чаты slack, rocketchat, mattermost (последние два self-hosted). у коллег туда летят оповещения мне не нравятся - слишком тяжелые приложения сами по себе, хоть для андроида, хоть для винды. но как вариант в периоды, когда на безрыбье и карась раком станет - вполне. Вставить ник Quote
alibek Posted March 22 Posted March 22 Я к Максу прикрутил. Собственно оповещалка (скопирована с Телеграма, изменен код скрипта): var Max = { token: null, to: null, message: null, proxy: null, format: null, send: function() { var params = { text: Max.message, notify: false }, data, response, request = new HttpRequest(), url = 'https://platform-api.max.ru/messages?disable_link_preview=false&chat_id=' + Max.to; if (Max.format !== null) { params['format'] = Max.format; } if (Max.proxy) { request.setProxy(Max.proxy); } request.addHeader('Authorization: ' + Max.token); request.addHeader('Content-Type: application/json'); data = JSON.stringify(params); // Remove replace() function if you want to see the exposed token in the log file. Zabbix.log(4, '[Max Webhook] URL: ' + url.replace(Max.token, '<TOKEN>')); Zabbix.log(4, '[Max Webhook] params: ' + data); response = request.post(url, data); Zabbix.log(4, '[Max Webhook] HTTP code: ' + request.getStatus()); try { response = JSON.parse(response); } catch (error) { response = null; } if (request.getStatus() !== 200) { if (typeof response.message === 'string') { throw response.message; } else { throw 'Unknown error. Check debug log for more information.' } } } } try { var params = JSON.parse(value); if (typeof params.Token === 'undefined') { throw 'Incorrect value is given for parameter "Token": parameter is missing'; } Max.token = params.Token; if (params.HTTPProxy) { Max.proxy = params.HTTPProxy; } if (['Markdown', 'HTML'].indexOf(params.Format) !== -1) { Max.format = params.Format; } Max.to = params.To; Max.message = params.Subject + '\n' + params.Message; Max.send(); return 'OK'; } catch (error) { Zabbix.log(4, '[Max Webhook] notification failed: ' + error); throw 'Sending failed: ' + error + '.'; } Токен прописывается прямо в параметрах оповещалки (параметр Token), в остальных параметрах прописаны макросы ALERT, в конечном итоге они задаются в параметрах пользователя Zabbix (раздел Оповещения). Кроме этого в оповещения добавлен скрипт для сводной информации (добавляется в "Способы оповещения" как PHP-скрипт): #!/usr/bin/php <?php // Дополнительная обработка событий Zabbix ini_set('display_errors', 1); error_reporting(E_ALL); require(".../header.php"); require(".../utf_overload.php"); $cli = (boolean)(php_sapi_name()=="cli"); if (!$cli) { $log->prn("Invalid usage! For CLI mode only!"); exit; } $script = basename(__FILE__, '.php'); $params = []; $params['self'] = array_shift($argv); $params['subj'] = array_shift($argv); $params['rcpt'] = array_shift($argv); $params['event'] = array_shift($argv); print "Подключение к NMS Zabbix\n"; if (!$zb = new Zabbix (null)) { print "! Сбой инициализации NMS Zabbix\n"; exit; } elseif ($zb->error()) { print "! Сбой подключения NMS Zabbix: " . $zb->error() . "\n"; exit; } if (file_exists("/tmp/{$script}_buffer.tmp") && ((time() - filemtime("/tmp/{$script}_buffer.tmp")) > 60)) unlink("/tmp/{$script}_buffer.tmp"); file_put_contents("/tmp/{$script}_buffer.tmp", getmypid() . "\n", FILE_APPEND); print "Запрос действующих проблем из Zabbix...\n"; $tmp = $zb->request("problem.get", ["output"=>"extend", "recent"=>false, "severities"=>[4,5], "groupids"=>[19,20,21,22], "sortfield"=>["eventid"], "sortorder"=>"DESC"]); if (!$tmp) { print "! Ошибка получения списка проблем из Zabbix\n"; exit; } if (isset($tmp['error'])) { print "! Ошибка получения списка проблем из Zabbix: {$response['error']}\n"; exit; } $tmp = $tmp['result']; print "Запрос списка проблем из Zabbix выполнен, строк: " . count($tmp) . "\n"; $lst = []; foreach ($tmp as $rec) $lst[$rec['eventid']] = ['id'=>$rec['eventid'], 'clock'=>$rec['clock'], 'problem'=>$rec['name'], 'level'=>$rec['severity']]; print "Запрос деталей проблем из Zabbix...\n"; $tmp = $zb->request("event.get", ["output"=>"extend", "eventids"=>array_keys($lst), "selectHosts"=>"extend"]); if (!$tmp) { print "! Ошибка получения деталей проблем из Zabbix\n"; exit; } if (isset($tmp['error'])) { print "! Ошибка получения деталей проблем из Zabbix: {$response['error']}\n"; exit; } $tmp = $tmp['result']; foreach ($tmp as $rec) { if (!isset($lst[$rec['eventid']])) continue; if (!$rec['value']) continue; if (isset($rec['hosts'], $rec['hosts'][0])) { if ($rec['hosts'][0]['status']) continue; $lst[$rec['eventid']]['host'] = $rec['hosts'][0]['host']; $lst[$rec['eventid']]['title'] = $rec['hosts'][0]['name']; $lst[$rec['eventid']]['date'] = date("Y-m-d H:i", $rec['clock']); $lst[$rec['eventid']]['time'] = time() - $rec['clock']; } } $lst = array_filter($lst, function($rec) { return isset($rec['host']);}); $ids = array_keys($lst); sort($ids); $ids = implode(",", $ids); $ref = null; $old = ""; $first = null; $mv = null; if (file_exists("/tmp/{$script}_max.tmp")) { $tmp = file_get_contents("/tmp/{$script}_max.tmp"); if (($tmp !== false) && $tmp) { $ref = trim(strtok($tmp, ":")); $old = trim(strtok(":")); $tmp = trim(strtok(":")); if ($tmp) $first = (int)$tmp; $tmp = trim(strtok(":")); if ($tmp) $mv = (int)$tmp; } } else { $first = time(); $mv = 0; } print "Буфферизация отправки команд...\n"; sleep(2); if (!file_exists("/tmp/{$script}_buffer.tmp")) exit; $tmp = file_get_contents("/tmp/{$script}_buffer.tmp"); if (!$tmp) exit; $tmp = explode("\n", trim($tmp)); $tmp = end($tmp); if ($tmp != getmypid()) exit; unlink("/tmp/{$script}_buffer.tmp"); $max = new Max(); $rcpt = $params['rcpt']; if ($ids) { print "# Есть аварии\n"; if ($ids == $old) { print "# Состав аварий не изменился, ничего не делать\n"; null; } else { print "# Состав аварий изменился\n"; $cnt = count($lst); if (isset($mv) && ($cnt > $mv)) $mv = $cnt; $max->build(); $max->build("Список действующих аварий ({$cnt}):\n"); foreach ($lst as $rec) { if ($cnt > 100) { $max->build("- {$rec['host']}\n"); } elseif ($cnt > 60) { $max->build("- {$rec['title']}\n"); } else { $max->build("- {$rec['title']} (с " . date("Y-m-d H:i", $rec['clock']) . ")\n"); } } $max->build("\nИнформация обновлена: " . date("Y-m-d H:i:s")); if ($ref) { print "# Если есть закрепленное сообщение, то обновить его\n"; $max->messageUpdate($ref, null); file_put_contents("/tmp/{$script}_max.tmp", "{$ref}: {$ids}" . ($first ? ": {$first}" . ($mv ? ": {$mv}" : "") : "")); } else { print "# Если закрепленного сообщения нет, то создать его и сохранить кеш\n"; if ($tmp = $max->messageSend($rcpt, null)) { $ref = $tmp['message']['body']['mid']; $max->messagePin($rcpt, $ref); file_put_contents("/tmp/{$script}_max.tmp", "{$ref}: {$ids}" . ($first ? ": {$first}" . ($mv ? ": {$mv}" : "") : "")); } } } } else { print "# Нет аварий\n"; if ($ref) { print "# Если есть закрепленное сообщение, то закрыть его и удалить кеш\n"; $max->build(); $max->build("Активные аварии отсутствуют.\n"); if ($first) { $tmp = (time() - $first); if ($tmp > 3600) { $tmp = "" . (int)($tmp / 3600) . "ч " . (int)(($tmp % 3600) / 60) . "м " . (int)($tmp % 60) . "с"; } elseif ($tmp > 60) { $tmp = "" . (int)($tmp / 60) . "м " . (int)($tmp % 60) . "с"; } else { $tmp = "" . (int)($tmp) . "с"; } $tmp .= " (с " . date("Y-m-d H:i", $first) . ")"; $max->build("Общее время: {$tmp}\n"); } if ($mv) { $max->build("Максимум: {$mv}\n"); } $max->build("\nИнформация обновлена: " . date("Y-m-d H:i:s")); $max->messageUpdate($ref, null); $max->messageUnpin($rcpt, $ref); unlink("/tmp/{$script}_max.tmp"); } } Классы Zabbix и Max не прикладываю, потому что там много зависимостей с другими библиотеками и переписывать их мне лень. Но по названиям методов вполне понятно, что они должны делать и их реализация несложна. Если не заморачиваться универсальностью и обработкой ошибок, то эти классы можно в полсотни строк вместить. При регистрации аварии скрипт добавляет сообщение со сводкой, где перечисляются недоступные хосты, это сообщение закрепляется в чате. При изменении перечня недоступных хостов это сообщение обновляется, чтобы всегда отображать текущее состояние (перечисляются недоступные хосты на текущий момент). Если все аварии устранены, то сообщение открепляется, указывается что аварий нет и указывается общая продолжительность и максимальное число недоступных хостов за этот период. При массовых авариях (когда становится недоступным одновременно большое число хостов) скрипт буфферизует данные за 2-секундный интервал (чтобы сократить rps). Результат выглядит примерно так: Вставить ник Quote
tcup Posted March 23 Posted March 23 16 hours ago, alibek said: Классы Zabbix и Max не прикладываю, потому что там много зависимостей с другими библиотеками и переписывать их мне лень. Не понял, это твои личные наработки, которыми ты не хочешь делиться? Вставить ник Quote
alibek Posted March 23 Posted March 23 Личные наработки. Не жалко, но выкладывать как есть не могу — много приватной информации, завязанной на работу фирмы. А рефакторить долго. Проще заново написать эти классы, реализовав только необходимые методы. Вставить ник Quote
tcup Posted March 23 Posted March 23 @alibek получается, для оповещалки эти классы не нужны? Вставить ник Quote
alibek Posted March 23 Posted March 23 Для оповещалки — не нужны. Для второго скрипта, который дополняет оповещения сводкой — нужны, потому что там создаются объекты Zabbix и Max, методы которых далее используются. Но для этого файлы не нужны, их проще прямо в тело скрипта встроить (добавить class Zabbix с методом request и class Max с методами messageSend/messageUpdate/messagePin/messageUnpin). Примерно так (не проверял, могут быть небольшие ошибки): #!/usr/bin/php <?php // Дополнительная обработка событий Zabbix ini_set('display_errors', 1); error_reporting(E_ALL); $cli = (boolean)(php_sapi_name()=="cli"); if (!$cli) { $log->prn("Invalid usage! For CLI mode only!"); exit; } $script = basename(__FILE__, '.php'); $rcpt = '-идентификатор-группы-max'; class Zabbix { private $curl; public function __construct() { $this->store = []; $this->store['base'] = *zabbix-api*; $this->store['counter'] = 0; $this->curl = curl_init(); $this->store['error'] = null; $token = $this->auth(*zabbix-user*, *zabbix-password*); $this->store['auth'] = $token; } private function _error($error=null) { if ($error === false) $error = null; if ($error === true) { if (isset($this->curl)) { if (curl_errno($this->curl)) { $error = ['code'=>curl_errno($this->curl), 'message'=>curl_error($this->curl)]; } else { $error = null; } } else { $error = ['code'=>-1, 'message'=>'No cURL connection']; } } $this->store['error'] = $error; } public function error($mode=null) { return $this->store['error']; } public function request($method, $params=null, $id=null, $auth=null) { if (!isset($id)) $id = ++$this->store['counter']; if ((func_num_args() < 4) && isset($this->store['auth'])) $auth = $this->store['auth']; $req = []; $req['jsonrpc'] = "2.0"; $req['method'] = $method; $req['params'] = ($params ?: []); $req['id'] = $id; if (isset($auth)) $req['auth'] = $auth; $this->_error(); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_HTTPHEADER, ["Content-Type: application/json-rpc"]); $data = json_encode($req, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); curl_setopt($this->curl, CURLOPT_POST, true); if (isset($data)) curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data); curl_setopt($this->curl, CURLOPT_URL, $this->store['base']); $res = curl_exec($this->curl); $ret = curl_getinfo($this->curl); if (empty($res) || ($res===false)) { $this->_error(true); return false; } if (is_array($ret)) list($code,$mime) = array($ret['http_code'],$ret['content_type']); if ($code >= 500) return "#error $code"; if (!empty($mime)) $mime = trim(explode(';',$mime)[0]); switch ($mime) { case "application/json": $data = json_decode($res, true); if (json_last_error() != JSON_ERROR_NONE) $data = false; break; default: $data = $res; break; } return $data; } private function result($response, &$err=null) { $err = null; if (isset($response) && is_array($response)) { if (isset($response['error'])) { $err = $response['error']; return null; } else { return $response['result']; } } } public function auth($username, $password) { $api = 'user.login'; $res = $this->result($this->request($api, ['user'=>$username, 'password'=>$password]), $err); if (isset($err)) $this->_error($err); return $res; } } class Max { protected $store; private $curl; const API_URL = 'https://platform-api.max.ru/'; public function __construct() { $this->store = []; $this->store['botname'] = *bot*; $this->store['base'] = self::API_URL; $this->store['token'] = *token*; $this->store['error'] = null; $this->curl = curl_init(); } private function _error($error=null) { if ($error === false) $error = null; if ($error === true) { if (isset($this->curl)) { if (curl_errno($this->curl)) { $error = ['code'=>curl_errno($this->curl), 'message'=>curl_error($this->curl)]; } else { $error = null; } } else { $error = ['code'=>-1, 'message'=>'No cURL connection']; } } $this->store['error'] = $error; } public function error($mode=null) { return $this->store['error']; } private function http($url, $data=null, $method='POST') { $this->_error(); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_HTTPHEADER, ["Authorization: {$this->store['token']}", "Content-Type: application/json; charset=utf-8"]); curl_setopt($this->curl, CURLOPT_POST, true); if (isset($data) && is_array($data)) $data = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); if (isset($data)) curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data); curl_setopt($this->curl, CURLOPT_URL, $this->store['base'] . $url); $res = curl_exec($this->curl); $ret = curl_getinfo($this->curl); if (empty($res) || ($res===false)) { $this->_error(true); return false; } if (is_array($ret)) list($code,$mime) = array($ret['http_code'],$ret['content_type']); if ($code >= 500) { return "#error $code"; } if (!empty($mime)) $mime = trim(explode(';',$mime)[0]); $data = json_decode($res, true); if (json_last_error() != JSON_ERROR_NONE) $data = false; if ($code >= 400) { $this->_error($data); return "#error $code"; } return $data; } private function result($response) { if (isset($response) && is_array($response)) { if (isset($response['success'])) { if (!$response['success']) $this->_error(['message'=>$response['message']]); return !!$response['success']; } else { return $response; } } } final public function messageSend($rcpt, $body, $format=null, $notify=null, $preview=null) { $api = 'messages'; if (is_string($rcpt) && substr($rcpt,0,1)=="-") { $api .= "?chat_id={$rcpt}"; } else { $api .= "?user_id={$rcpt}"; } if (isset($preview)) $api .= "&disable_link_preview=".($preview?"false":"true"); $params = []; $params['text'] = $body; if (isset($format)) $params['format'] = $format; if (isset($notify)) $params['notify'] = $notify; $res = $this->result($this->http($api, $params, 'POST')); return $res; } final public function messageUpdate($mid, $body, $format=null, $notify=null) { $api = 'messages'; $api .= "?message_id={$mid}"; $params = []; $params['text'] = $body; if (isset($format)) $params['format'] = $format; if (isset($notify)) $params['notify'] = $notify; $res = $this->result($this->http($api, $params, 'PUT')); return $res; } final public function messagePin($chat, $mid, $notify=null) { $api = "chats/{$chat}/pin"; $params = ['message_id'=>$mid]; if (isset($notify)) $params['notify'] = $notify; $res = $this->result($this->http($api, $params, 'PUT')); return $res; } final public function messageUnpin($chat, $mid) { $api = "chats/{$chat}/pin"; $params = ['message_id'=>$mid]; if (isset($notify)) $params['notify'] = $notify; $res = $this->result($this->http($api, $params, 'DELETE')); return $res; } } print "Подключение к NMS Zabbix\n"; if (!$zb = new Zabbix) { print "! Сбой инициализации NMS Zabbix\n"; exit; } if (file_exists("/tmp/{$script}_buffer.tmp") && ((time() - filemtime("/tmp/{$script}_buffer.tmp")) > 60)) unlink("/tmp/{$script}_buffer.tmp"); file_put_contents("/tmp/{$script}_buffer.tmp", getmypid() . "\n", FILE_APPEND); print "Запрос действующих проблем из Zabbix...\n"; $tmp = $zb->request("problem.get", ["output"=>"extend", "recent"=>false, "severities"=>[4,5], "groupids"=>[19,20,21,22], "sortfield"=>["eventid"], "sortorder"=>"DESC"]); if (!$tmp) { print "! Ошибка получения списка проблем из Zabbix\n"; exit; } $tmp = $tmp['result']; print "Запрос списка проблем из Zabbix выполнен, строк: " . count($tmp) . "\n"; $lst = []; foreach ($tmp as $rec) $lst[$rec['eventid']] = ['id'=>$rec['eventid'], 'clock'=>$rec['clock'], 'problem'=>$rec['name'], 'level'=>$rec['severity']]; print "Запрос деталей проблем из Zabbix...\n"; $tmp = $zb->request("event.get", ["output"=>"extend", "eventids"=>array_keys($lst), "selectHosts"=>"extend"]); if (!$tmp) { print "! Ошибка получения деталей проблем из Zabbix\n"; exit; } $tmp = $tmp['result']; foreach ($tmp as $rec) { if (!isset($lst[$rec['eventid']])) continue; if (!$rec['value']) continue; if (isset($rec['hosts'], $rec['hosts'][0])) { if ($rec['hosts'][0]['status']) continue; $lst[$rec['eventid']]['host'] = $rec['hosts'][0]['host']; $lst[$rec['eventid']]['title'] = $rec['hosts'][0]['name']; $lst[$rec['eventid']]['date'] = date("Y-m-d H:i", $rec['clock']); $lst[$rec['eventid']]['time'] = time() - $rec['clock']; } } $lst = array_filter($lst, function($rec) { return isset($rec['host']);}); $ids = array_keys($lst); sort($ids); $ids = implode(",", $ids); $ref = null; $old = ""; $first = null; $mv = null; if (file_exists("/tmp/{$script}_max.tmp")) { $tmp = file_get_contents("/tmp/{$script}_max.tmp"); if (($tmp !== false) && $tmp) { $ref = trim(strtok($tmp, ":")); $old = trim(strtok(":")); $tmp = trim(strtok(":")); if ($tmp) $first = (int)$tmp; $tmp = trim(strtok(":")); if ($tmp) $mv = (int)$tmp; } } else { $first = time(); $mv = 0; } print "Буфферизация отправки команд...\n"; sleep(2); if (!file_exists("/tmp/{$script}_buffer.tmp")) exit; $tmp = file_get_contents("/tmp/{$script}_buffer.tmp"); if (!$tmp) exit; $tmp = explode("\n", trim($tmp)); $tmp = end($tmp); if ($tmp != getmypid()) exit; unlink("/tmp/{$script}_buffer.tmp"); $max = new Max; if ($ids) { print "# Есть аварии\n"; if ($ids == $old) { print "# Состав аварий не изменился, ничего не делать\n"; null; } else { print "# Состав аварий изменился\n"; $cnt = count($lst); if (isset($mv) && ($cnt > $mv)) $mv = $cnt; $msg = ""; $msg .= "Список действующих аварий ({$cnt}):\n"; foreach ($lst as $rec) { if ($cnt > 100) { $msg .= "- {$rec['host']}\n"; } elseif ($cnt > 60) { $msg .= "- {$rec['title']}\n"; } else { $msg .= "- {$rec['title']} (с " . date("Y-m-d H:i", $rec['clock']) . ")\n"; } } $msg .= "\nИнформация обновлена: " . date("Y-m-d H:i:s"); if ($ref) { print "# Если есть закрепленное сообщение, то обновить его\n"; $max->messageUpdate($ref, $msg); file_put_contents("/tmp/{$script}_max.tmp", "{$ref}: {$ids}" . ($first ? ": {$first}" . ($mv ? ": {$mv}" : "") : "")); } else { print "# Если закрепленного сообщения нет, то создать его и сохранить кеш\n"; if ($tmp = $max->messageSend($rcpt, $msg)) { $ref = $tmp['message']['body']['mid']; $max->messagePin($rcpt, $ref); file_put_contents("/tmp/{$script}_max.tmp", "{$ref}: {$ids}" . ($first ? ": {$first}" . ($mv ? ": {$mv}" : "") : "")); } } } } else { print "# Нет аварий\n"; if ($ref) { print "# Если есть закрепленное сообщение, то закрыть его и удалить кеш\n"; $msg = ""; $msg .= "Активные аварии отсутствуют.\n"; if ($first) { $tmp = (time() - $first); if ($tmp > 3600) { $tmp = "" . (int)($tmp / 3600) . "ч " . (int)(($tmp % 3600) / 60) . "м " . (int)($tmp % 60) . "с"; } elseif ($tmp > 60) { $tmp = "" . (int)($tmp / 60) . "м " . (int)($tmp % 60) . "с"; } else { $tmp = "" . (int)($tmp) . "с"; } $tmp .= " (с " . date("Y-m-d H:i", $first) . ")"; $msg .= "Общее время: {$tmp}\n"; } if ($mv) { $msg .= "Максимум: {$mv}\n"; } $msg .= "\nИнформация обновлена: " . date("Y-m-d H:i:s"); $max->messageUpdate($ref, $msg); $max->messageUnpin($rcpt, $ref); unlink("/tmp/{$script}_max.tmp"); } } Вставить ник Quote
QWE Posted April 25 Author Posted April 25 DeltaChat , нужен почтовик для его работы Вставить ник Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.