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

В этой статье я хочу рассказать о своем опыте написания скрипта для мгновенного оповещения о возникающих в системе ошибках посредством ICQ сообщений. В качестве промежуточного звена и накопителя репортов используется брокер сообщений ActiveMQ, я расскажу как его установить и настроить для работы с MySQL. Главная часть — это ICQ-бот, написанный на PHP, в его обязанности входит прослушивание определенного канала в брокере и пересылка сообщений на указанные номера ICQ. Также я расскажу как запустить этот PHP скрипт в качестве службы Windows.


Схема работы данной системы следующая: приложение (веб или десктопное) при возникновении ошибки формирует некое XML-сообщение, в котором содержится краткое описание ошибки и ICQ-номер получателя. Это сообщение отправляется по STOMP протоколу брокеру ActiveMQ и попадает в очередь. На другом конце света или на той же машине находится ICQ-бот, который слушает эту очередь и при попадании в нее сообщения тут же пересылает его по указанному адресу. А теперь в деталях.

Установка и настройка ActiveMQ


Все программы будут ставиться под Windows. Пару слов о самом ActiceMQ, он представляет собой брокер сообщений, в котором есть 2 основных понятия: очереди (Queue) и темы (Topic).
Topic предназначен для оповещения большого числа подписчиков о каком-нибудь событии, например новость или выход очередного патча.
Queue представляет соб��й очередь заданий, на нее может быть подписано несколько слушателей, но любое сообщение получит только один из подписчиков, это как раз нам подходит.

Для начала качаем дистрибутив. Установка сводится к распаковке архива в какую-нибудь далекую папку. Для удобства работы установим его как службу Windows, для этого необходимо запустить скрипт ActiveMQ\bin\win32\InstallService.bat, наличие установленной JAVA машины предполагается само собой.
Проверить работоспособность можно через «Администрирование» -> «Службы», но только подождите секунд 10 после запуска и обновите список, на Win 2003 server у меня возникла проблема: служба вроде запускалась, но сразу же падала, долгое копание в логах и гуглах привело к простому решению – необходимо создать папку work в каталоге ActiveMQ\bin\win32.

Для настройки ActiveMQ необходимо открыть файл \conf\activemq.xml
Описываем наш брокер:
<broker xmlns="activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.base}/data" destroyApplicationContextOnStop="true" persistent="true" useShutdownHook="false">

Обратите внимание на параметр persistent, он отвечает за то, что все ваши сообщения будут хранится в БД и не пропадут после перезапуска службы. Надо это вам или нет решайте сами, я эту функцию подключил, причем перенастроил со встроенной kahaDB на более понятную и прозрачную MySQL. Делается это следующим образом:
Скачиваем коннектор для явы, из него берем mysql-connector-java-5.1.14-bin.jar и закидываем его в папку \lib. В самой MySQL создаем БД с названием activemq, для нее создадим пользователя activemq с таким же паролем. Опишем это все в конфиге:
<persistenceAdapter>        <br/>
   <jdbcPersistenceAdapter dataSource="#mysql-ds"/>            <br/>
</persistenceAdapter><br/>
 <br/>
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><br/>
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/><br/>
   <property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/><br/>
   <property name="username" value="activemq"/><br/>
   <property name="password" value="activemq"/><br/>
   <property name="poolPreparedStatements" value="true"/><br/>
</bean>

Блок должен располагаться за пределами тега . После перезапуска службы в БД должны будут создаться 3 таблицы, если так и произошло, значит все сделано правильно.

Идем дальше по конфигу, настроим возможные подключения:
<transportConnectors><br/>
 <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/><br/>
 <transportConnector name="openwire2" uri="stomp://0.0.0.0:61613"/><br/>
</transportConnectors>
Здесь я задал 2 различных подключения по разным протоколам, это связано с тем, что на PHP для подключения я использую библиотеку STOMP, а под Delphi удалось найти компонент стабильно работающий по TCP.

Далее займемся авторизацией, в блок добавим следующий кусок:
<plugins><br/>
 <jaasAuthenticationPlugin configuration="activemq-domain" /><br/>
 <authorizationPlugin><br/>
  <map><br/>
   <authorizationMap><br/>
    <authorizationEntries><br/>
     <authorizationEntry queue=">" read="admins" write="admins" admin="admins" /><br/>
     <authorizationEntry queue="icq.>" read="users" write="users" admin="admins" /><br/>
     <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users" write="guests,users" admin="guests,users" /><br/>
    </authorizationEntries><br/>
   </authorizationMap><br/>
  </map><br/>
 </authorizationPlugin><br/>
</plugins>

Теперь создаем файл groups.properties в папке /conf со следующим содержимым:
admins=system,sslclient,client,broker1,broker2
users=icq
guests=guest

Там же создадим файл users.properties и впишем в него:
system=password
icq=secret
Еще понадобится файл login.config, его проще скачать целиком и кинуть в папку /conf. После всех настроек перезапускаем службу и заходим на адрес http://localhost:8161/admin/, если все сделали правильно, вас встретит панель управления, если так, то заходим в Queues и там создаем очередь с названием icq.

Скрипт ICQ-бота


За основу бота я взял библиотеку WebIcqPro, она довольно простая и стабильная. Также понадобится библиотека для отправки сообщений брокеру, я нашел вполне стабильное решение на STOMP-протоколе, однако при разрыве соединения его вышибало, поэтому мне пришлось его немного модифицировать, чтобы появилась возможность реконнекта. Приводить весь код бота я здесь не буду, кому это действительно интересно могут скачать архив.

Для проверки работоспособности запустим бота через командную строку:
«C:\Program Files\PHP\php» C:\icqbot\icq.php
Если с настройками все в порядке, он должен установить соединение с ActiveMQ и сервером ICQ, после чего начать прослушивать канал «* Waiting for messages...». Для отправки пробного сообщения можно воспользоваться скриптом send.php из архи��а:
<?php<br/>
require_once 'Stomp.php';<br/>
$c = new StompConnection("localhost");<br/>
$result = $c->connect("icq", "bot");<br/>
 <br/>
$mess = '<?xml version="1.0" encoding="windows-1251"?><br/>
            <reference><br/>
            <type>send</type><br/>
            <to>111111111</to><br/>
            <from>Test send:</from><br/>
            <mes>Проверка русского message</mes><br/>
            </reference>'
;<br/>
 <br/>
$mess =  iconv('cp1251','UTF-8',$mess);<br/>
$c->send("/queue/icq", $mess, array('persistent' => 'true'));<br/>
$c->disconnect();<br/>
?>
Мне довольно долго пришлось возится с кодировками, чтобы можно было отправлять сообщения с русским текстом, поэтому не удивляйтесь когда в скриптах увидите каскадные преобразования в различные кодировки, по другому это все работать не хотело. После вызова send.php на указанный UIN придет сообщение и в логах этот факт также отразится.

Запускаем бота как службу Windows


Для этого нам понадобится набор Windows NT Resource Kit, у кого нет качаем. Допустим все файлы бота у нас лежат в папке C:\icqbot. Открываем консоль и пишем
“C:\Program Files\Windows Resource Kits\Instsrv.exe” ICQBot “C:\Program Files\Windows Resource Kits\Srvany.exe”
Далее запускаем regedit и идем в раздел HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ICQBot внутри него создаем раздел Parameters и в нем параметр Application типа REG_SZ со значением «C:\Program Files\PHP\php.exe» C:\icqbot\icq.php
Далее идем в службы и запускаем оттуда нашего бота, теперь он полностью автономен.

Ловец ошибок для сайта


Наша конечная цель – отлавливать все ошибки, составлять подробный отчет и отправлять уведомление об этом на ICQ. Таким образом осталось описать только скрипт отлова этих ошибок, у меня он выглядит следующим образом и подключается во все скрипты где требуется контроль:
<?php<br/>
ini_set('display_errors',0);<br/>
error_reporting(2);<br/>
 <br/>
if($send_report){<br/>
  include_once("Stomp.php");<br/>
 <br/>
  function send_report($message, $to = '1212312', $from = 'SENDER'){<br/>
    global $send_report, $stomp_server, $stomp_url, $stomp_user, $stomp_psw;<br/>
    if(!$send_report)return false;<br/>
    $c = new StompConnection($stomp_server);<br/>
    $result = $c->connect($stomp_user, $stomp_psw);<br/>
    if(!is_array($to))$to = array($to);<br/>
    foreach($to as $i){<br/>
      $mess = '<?xml version="1.0" encoding="windows-1251"?><br/>
              <reference><br/>
              <type>send</type><br/>
              <to>'
.$i.'</to><br/>
              <from>'
.$from.'</from><br/>
              <mes>'
.$message.'</mes><br/>
              </reference>'
;<br/>
      $c->send($stomp_url, iconv('cp1251','UTF-8',$mess), array('persistent' => 'true'));<br/>
    }<br/>
    $c->disconnect();<br/>
  }<br/>
}<br/>
 <br/>
function user_log ($errno, $errmsg, $file, $line) {<br/>
  global $send_report;<br/>
  if($errno == 2){<br/>
    $filename = strftime('%d.%m.%Y %H-%M-%S_').$_REQUEST['PHPSESSID'].'.err';<br/>
    $fl = fopen('errors/'.$filename,'w');<br/>
    $_SESSION['ERROR_TEXT'] =  'WARNING: '.$errmsg.' in '.$file.' on line '.$line;<br/>
    $_SESSION['ERROR_TIME'] =  strftime('%d.%m.%Y %H-%M-%S');<br/>
    $_SESSION['ERROR_PHPSESSID'] =  $_REQUEST['PHPSESSID'];<br/>
    $_SESSION['ERROR_TYPE'] =  'PHP SCRIPT ERROR';<br/>
    fwrite($fl,serialize($_SESSION));<br/>
    fclose($fl);<br/>
    if($send_report)send_report($_SESSION['ERROR_TEXT']);<br/>
  }<br/>
}<br/>
 <br/>
set_error_handler('user_log');<br/>
?>
По понятным причинным все содержимое этих отчетов я криптую, что и всем советую делать.

Таким образом, получилась довольно стабильная, простая и универсальная схема оповещения, позволяющая отправлять сообщения на ICQ из любого приложения на любом языке, если для него описан один из протоколов взаимодействия с ActiveMQ.

Список используемых файлов
  1. ActiveMQ 5.4.2
  2. Конфиги для ActiveMQ
  3. Коннектор MySQL
  4. Библиотека WebICQPro для PHP
  5. Библиотека STOMP для PHP
  6. Готовый комплект скриптов бота и перехватчика ошибок
  7. Набор Windows NT Resource Kit