Оповещения из скриптов

Posted: 2012-07-05 in IT, Software
Метки:

Данная заметка будет посвящена теме автоматизации скриптами. Если ваш скрипт делает нечто, работоспособность чего зависит от внешних условий, то в случае сбоя на удаленной стороне админ обычно узнает об этом только тогда, когда начинают ругаться пользователи. Как этого избежать ? …

В принципе, несложно. Обычно если в скриптах делается обработка ошибок, то текст этих ошибок сыпется в консоль на STDOUT/STDERR. Это все замечательно и прекрасно ровно до тех пор, пока скрипт используется интерактивно. То есть вам что-то надо, вы запускаете свой скрипт, он написал в консоль ворох текста, из которого всё понятно.

Но если скрипт прописан в крон на замурованном в бункере сервере — то эти сообщения читать некому, их надо отсылать.
Тут обычно применяют два варианта:
1). Отсылка логов на почту.
Беда этого метода в том, что почту проверяю я редко, дополнительные оповещалки включать влом, логи обычно высылаются целиком и т.д. То есть мало того я могу почту забыть проверить, так ещё и место используется неоптимально. На сервере надо прикручивать ротацию логов, а ценности они обычно не имеют. Неудобно и криво.
2). Отсылка СМС-ок через соответствующие сервисы типа http://www.smstraffic.ru/
Тут основная трабла — что отсылка сообщений стоит денег. А сильно обезумевший скрипт может просадить баланс в ноль. Да и телефон может офигеть от приема сильно большого числа сообщений. Не говоря уже о том, что есть проблемы с поддержкой юникода и сильное ограничение на длину сообщения. Вдобавок, текст сообщения идет по сетям операторов незашифроанным.

Поэтому мне больше понравился третий вариант — отсылка в джаббер. Джаббер-клиент есть везде, запущен он постоянно, да и у него нет тех идиотских проблем, что есть у СМС.

Расскажу, как я это настроил.

Сперва качаем библиотеку XMPPHP: http://code.google.com/p/xmpphp/ и распаковываем её на каком-нить веб-сервере в каталог xmpphp.

Дальше нам понадобится учётка на каком-нить джаббер-сервере, чтобы с неё слать сообщения. Я решительно рекомендую всем компаниям, у которых есть свой админ в штате, поднять свой jabber-сервер. Это решает сразу две проблемы — пользователям не надо авторизовывать друг друга и составлять свои контакт листы (всех сотрудников можно добавить в shared-ростер, сразу разбив по группам) и не надо тратить время на авторизацию 100500 контактов.

Пишем скрипт для отсылки сообщений (скрипт написан на скорую руку под использование своего jabber-сервера):

<?php

/* this script sendmsg.php for send messages to Jabber */

$date = date("Y-m-d H:i:s");
$xmpp_domain = 'plesetzk.ru';
$xmpp_server = 'jabber.plesetzk.ru';
$xmpp_port = '5222';
$xmpp_user = 'user_for_send_messages';
$xmpp_pwd = 'mega_password_for_annoncer_account';
$xmpp_instance = 'sendxmpp';

include 'xmpphp/XMPP.php';

date_default_timezone_set('Europe/Moscow');

$user  = isset($_GET['user'])  ? $_GET['user']  : '';
$theme = isset($_GET['theme']) ? $_GET['theme'] : '';
$msg   = isset($_GET['msg'])   ? $_GET['msg']   : '';

if ( $user == '' ) { print '10 Parameter user Not defined'; exit; }
elseif ( $msg  == '' ) { print '12 Parameter msg not defined'; exit; };
$users = explode ("|", $user);

$xmpp_msg = "[".$date."] [".$_SERVER['REMOTE_ADDR']."] :: $theme :: $msg";

$conn = new XMPPHP_XMPP($xmpp_server, $xmpp_port, $xmpp_user, $xmpp_pwd,
            $xmpp_instance, $xmpp_domain, $printlog=false, $loglevel=XMPPHP_Log::LEVEL_INFO);
try { $conn->connect();
      $conn->processUntil('session_start'); $conn->presence();
          foreach ($users as $usr) {
             $conn->message($usr.'@'.$xmpp_domain, $xmpp_msg);
          }
      $conn->disconnect();
      print '0';
    } catch(XMPPHP_Exception $e) { print '1'; print($e->getMessage()); };

?>

Не будет лишним прописать .htaccess, чтобы этот скрипт можно было дергать только с определенных адресов.
После этого всё, что потребуется сделать в скрипте для отсылки сообщения — дернуть скрипт по примерно вот такой ссылке:

http://my.mini.web.server.local/sendmsg.php?user=admin1|admin2&theme=subsystem1&msg=error115_detected

с помощью того же wget:

wget -O /dev/null "http://my.mini.web.serv...cted"

. После такого вызова двум админам придет сообщение:

[<дата_на_сервере>] [<ip того, кто вызвал скрипт>] :: subsystem1 :: error115_detected

Тут есть важный момент: на некоторых системах (не будем показывать пальцем) wget может лежать по такому пути, который в $PATH для учетки cron-а не прописан. Симптомы очевидны — запускаем руками от пользователя — сообщения идут, прописываем в крон — не идут. Самое простое решение — вызывать wget по полному пути.

Допустим, у вас есть скрипты, который коннектятся к базам, потом по этим данным обходит какие-нить сторонние железки, делают бэкапы и творят прочие непотребства. Очевидно, что повесить отсылку сообщений надо на а) невозможность коннекта к базе б) недоступность железки. в) иные ситуации, возникновение которых может грозит головной болью.
Нужные нам данные (например, адрес железки и текст сообщения об ошибке) дописываем в параметр msg.

Всё, в случае ошибок на джабберы admin1@plesetzk.ru и admin2@plesetzk.ru (список получателей указываем в параметре user через вертикальную черту) будут приходить примерно такие сообщения:

[2012-07-05 03:25:09] [192.168.15.14] :: Backup :: OK
[2012-07-05 03:41:11] [192.168.15.18] :: Backup :: OK
[2012-07-05 03:49:25] [192.168.15.25] :: Backup :: OK
[2012-07-05 07:30:01] [192.168.100.28] :: ACL :: DB_CONNECT_FAILED
[2012-07-05 07:35:02] [192.168.100.28] :: ACL :: DB_CONNECT_FAILED
[2012-07-05 07:40:02] [192.168.100.28] :: ACL :: DB_CONNECT_FAILED
[2012-07-05 07:45:01] [192.168.100.28] :: ACL :: DB_CONNECT_FAILED
[2012-07-05 07:50:02] [192.168.100.28] :: ACL :: DB_CONNECT_FAILED
[2012-07-05 10:30:02] [192.168.100.28] :: ACL :: TOO_OLD_DATA__41567_seconds_diff
[2012-07-05 10:31:01] [192.168.100.28] :: ACL :: TOO_OLD_DATA__41626_seconds_diff
[2012-07-05 11:40:27] [192.168.8.3] :: SCE-8000 :: Telnet_cmd_import-subscribers_timed-out
[2012-07-05 11:51:05] [192.168.8.3] :: BillingDump :: CONNECT_DB_FAILED_wrong_login_or_passwword
[2012-07-05 11:52:28] [192.168.100.28] :: ACL :: HW_10.1.75.115_NOT_ACCESSIBLE
[2012-07-05 11:52:49] [192.168.100.28] :: ACL :: HW_10.1.192.16_NOT_ACCESSIBLE
[2012-07-05 11:55:48] [192.168.100.28] :: ACL :: HW_10.1.192.17_NOT_ACCESSIBLE
[2012-07-05 11:56:09] [192.168.100.28] :: ACL :: HW_10.1.75.119_NOT_ACCESSIBLE
[2012-07-05 12:02:05] [192.168.1.118] :: TEST :: Test_message

Все наглядно, сразу видно, какая подсистема какого сервера на чем и когда споткнулась, где искать проблему. При этом дополнительные логи нигде не копятся (разве что на апаче вашего веб-сервера и в вашем джаббер-клиенте), и нет необходимости читать ворох мутных логов целиком.

Внутри каждого скрипта имеет смысл обернуть вызов вот в такую функцию /пример на перле/:

 sub xmpp_alarm($$$) {
        ($text, $to, $theme) = @_;
        $theme = 'Bill_script_1' if $theme eq '';
        $to = 'amin' if $to eq '';
        system('/usr/local/bin/wget -O /dev/null "http://my.mini.web.server.local/sendxmpp/index.php?mode=silent&user='.$to.'&theme='.$theme.'&msg='.$text.'"');
    }

и вызывать её например так:

$db->connect(...) or xmpp_alarm('DB_CONNET_FAIL', '', '');

Это позволит более гибко управлять списком получателей уведомлений и копипастить код в другие скрипты, меняя только тему сообщений.

P.S.
— Если есть потребность слать сообщения с не-английскими символами — то слать их надо в UTF-8.
— Библиотека XMPPHP находится в стадии разработки, но нужное нам работает
— Можно замутить и более сложные вещи, типа рассылок по группам из ростера

Надеюсь, эта заметка будет кому-нибудь полезной, хотя бы в качестве примера.

Обсуждение закрыто.