PHP-4 Эффективная работа


Первое знакомство с WDDX
Функции WDDX-генератора и анализатора
Генерация системного журнала в XML+WDDX

19.3. Генерация системного журнала в XML+WDDX,

Теперь в качестве иллюстрации рассмотрим практический пример работы подсистемы обработки ошибок в РНР. Мы создадим собственную функцию обработчика ошибок, которая будет генерировать журнал, представленный в модном XML-формате, а также отправлять письмо администратору узла при обнаружении критических ошибок. Поскольку нам придется иметь дело с массивами, для их помещения в журнал мы воспользуемся WDDX.

примечание
       Для изучения работы этого примера вам потребуется материал из главы 14.

Возможно, пример, приведенный в листинге 19.1, покажется вам достаточно длинным, но, поверьте, дело того стоит.

Листинг 19.1. Генератор системного журнала в XML-формате
<HTML>
<ТIТLЕ>Генератор системного журнала в ХМL-формате</ТIТLЕ>
<BODY BGCOLOR="#FFFFFF">
<h3>Демонстрация работы пользовательского обработчика ошибок</hЗ>

В этой программе осуществляется формирование пользовательского системного журнала, который располагается в каталоге <t>/tmp</b>.
Формат системного журнала - XML+WDDX.<p>

<h4>Первый этап - генерация журнала</h4>
<?php

$sysadmin = "root@local.host";    // адрес администратора системы,
                                                  // который получает сообщение об
                                                  // ошибке в программе
@unlink("/tmp/errorlog.txt") ;           // прежний журнал не нужен
// Закрываем префиксом @ для предотвращения генерации сообщения
// при отсутствии такого файла

// Сначала отключаем системный обработчик ошибок
// error_reporting(0);
// Затем открываем сеанс для записи
error_log("\n",3,"/tmp/errorlog. txt") ;

// И вводим свой собственный
function userErrorHandler ($errno, $errmsg, $filename, $linenum, $vars) {
     // Формат отображения времени в журнале в память об
     // операционной системе РАФОС (RT-11)
     $dt = date("d-M-Y H:i :s");

     // Теперь создадим хэш-массив с кодами и категориями
     // сообщений об ошибках
     $errortype = array (
       1 => "Ошибка",
       2 => "Предупреждение",
       4 => "Ошибка компилятора",
       8 => "Подсказка",
       16 => "Ошибка РНР-машины",
       32 => "Предупреждение РНР-машины",
       64 => "Ошибка компилятора",
       128 => "Предупреждение компилятора",
       256 => "Ошибка пользователя",
       512 => "Предупреждение пользователя",
       1024 => "Подсказка пользователя"
       );
     // Теперь формируем маску отслеживаемых событий
     $user_errors = array(E_USER_ERROR, E_USER_WARNING,E_USER_NOTICE);

     // Теперь сформируем XML-запись сообщения об ошибке
     // Поскольку формат прост, мы не будем использовать
     // DOM-модель с автоматической генерацией документа
     $err = "<ERRORENTRY>\n";
     $err .= "<DATETIME>".$dt."</DATETIME>\n";
     $err .= "<ERRORNUM>".$errno."</ERRORNUM>\n";
     $err .= "<ERRORTYPE>".$errortype[$errno],"</ERRORTYPE>\n";
     $err .= "<ERRORMSG>".$errmsg."</ERRORMSG>\n";
     $err .= "<SCRIPTNAME>".$filename."</SCRIPTNAME>\n";
     $err .= "<SCRIPTLINENUM>".$linenum."</SCRIPTLINENUM>\n";

     if (in_array($errno. $user_errors))
     {
       // Массив преобразуем с помощью WDDX
       $err .= "\t<VARTRACE>";
       $err .= wddx_serialize_value($vars,"Variables");
       $err .= "</VARTRACE>\n";
       }
       $err .= "</ERRORENTRY>\n\n";
     // Тестовая печать в процессе отладки программы
     // echo "<pre>$err</pre><<p>";

     // Теперь помещаем сформированную запись в журнал, и если       // это критическая ошибка, сгенерируем сообщение системному       // администратору       error_log($err, 3, "/tmp/errorlog.txt");
     if ($errno == E_USER_ERROR)
     mai1($sysadmin,"Критическая ошибка в программе!".$err);
     // Функция, вычисляющая расстояние между двумя векторами,
     // которая генерирует немало сообщений об ошибках 
function distance ($vectl, $vect2) {
     if (!is_array($vectl) || ! is_array ($vect2) ) {
       trigger_error ("Недопустимый тип аргументов!",E_USER_ERROR) ;
       return NULL;
       }
     if (count($vectl) != count ($vect2) ) {
       trigger_error("Beкторы должны иметь одинаковый размер" E_USER_ERROR) ;
       return NULL;
       }
     for ($i=0; $i<count ($vectl) ; $i++) {
       $cl = $vectl[$i]; $c2 = $vect2[$i];
       $d = 0.0;
       if ( ! is_numeric($cl) ) {
          trigger_error ("$i -я координата вектора 1". "не является числом", E_USER_WARNING) ;
          $cl = 0.0;
       }
     if ( ! is_numeric($c2)) {
          trigger_error ("$i -я координата вектора 2"."не является числом", E_USER_WARNING) ;
          $с2 = 0.0;
       }
       $d += $с2*$с2 - $cl*$cl;
       }
     return sqrt($d);
     }
// Переустанавливаем обработчик ошибок
$old_error_handler = set_error_handler ("userErrorHandler") ;
?>
<ul>
<li> Неопределенная константа - сформирует сообщение об ошибке
<?
$t = I_AM_NOT_DEFINED;

// Теперь создадим несколько векторов
$а = array (2 , 3 , "foo") ;
$b = array(5.5, 4.3, -1.6) ;
$c = array (1, -3) ;
?>
<li>Генерируем сообщение об ошибке при вычислении функции
<?
$tl = distance($c,$b) . "\n" ;
?>
<li> и еще одно - о неверных аргументах
<?
$t2 = distance($b, "А я не массив. .."). "\n" ;
?>
<li>A в заключение сгенерируем предупреждение...
<?
$t3 = distance($a,$b) ."\n";

// В заключение восстановим системный обработчик
restore_error_handler ($old_error_handler) ;
error_log("</ERRORLOG>\n" ,3, "/tmp/errorlog. txt") ;
?>
</ul> <h4>Восстанавливаем содержимое журнала и выводим его в виде таблицы</h4>
       <?
     class logger
       {
       var $xml_parser;
       var $xml_file;
       var $html;
       var $open_tag;
       var $close_tag;
       var $wddx_flag;    // флажок обнаружения WDDX-пакета
       var $wddx;    // поток WDDX-данных (сборка пакета)
     // Конструктор класса
     function logger () {
     $this->xml_parser = "";
     $this->xml_file = "" ;
     $this->html = "";
     $this->wddx_flag = 0;
     $this->wddx = " " ;
     $this->open_tag = array(
     // Настраиваем интерпретатор отдельных
     // тегов нашего заказного формата
     "ERRORLOG"          => "<TABLE CELLPADDING=5>" ,
     "ERRORENTRY"          =>"<TR><TD BGCOLOR=#f2f2f2>" ,
     "DATETIME"          => "<FONT COLOR=#8B3B00>" ,
     "ERRORNUM"          => "<TD BGCOLOR=#f 2f2f2>" .
                         "Код ошибки: <FONT COLOR=#000A0C><B>" ,
     "ERRORTYPE"          => "Категория ошибки: <FONT COLOR=#3F3F00>"
     "ERRORMSG"          => "<FONT SIZE=-1><FONT COLOR=#D22323>" ,
     "SCRIPTNAME"           =>"<р>Файл: ",
     "SCRIPTLINENUM"           =>"строка - <B>" .
     "VARTRACE"           => "<p><FONT COLOR=\"BLUE\" Переменные : ");
     // а здесь - закрывающие теги
     $this->close_tag = array(
     "ERRORLOG"           => "</TABLE>\n\n" ,
     "ERRORENTRY"          =>"</TD></TR>",
     "DATETIME"          => "</FONT></TD>",
     "ERRORNUM"          => "</B>",
     "ERRORTYPE"          => "<br>",
     "ERRORMSG"           => "</FONT></FONT>",
     "SCRIPTNAME"           =>"&bsр;&nbsр;",
     "SCRIPTLINENUM"           =>"</B>",
     "VARTRACE"           => "</FONT>   ") ;
     }
     // Деструктор класса. В отличие от Си++ и Perl его
     // необходимо вызывать вручную
function destroy( )
{
xml_parser_free( $this->xml_parser) ;
}
//Основные функции введенного нами класса
function concat($str)
{
     // дописывает в генерируемый HTML-поток очередную строку
     $this-> html .= $str;
}
// Вызывается при начале нового элемента в документе
function startElement( $parser, $name, $attrs)
{
if ($format= $this->open_tag[$name] )
     {
       $this-> html .= $format;
     }
     if ($name == "VARTRACE") { $this->wddx_flag = 1;
                                        $this->wddx = " ";}
     else
       if ($this->wddx_flag == 1)
       {
          $this->wddx .= "<". $name." "; // открываем тег
          // теперь обрабатываем его аргументы (атрибуты)
          while (list ($key, $val) = each($attrs) )
               {
                   $this->wddx .= $key . "="' .$val. " ' ";
               }
          $this->wddx .= ">"
       }
     }
// Вызывается при окончании обработки элемента
function endElement ($parser , $name )
{
     global $close_tag;
     if ($format= $this->close_tag[$name] )
       {
          Sthis-> html .= $format;
       }
     if ($name == "VARTRACE")
       {
          $this->wddx_flag = 0;
          $m = wddx_deserialize($this->wddx) ;
          // отладочная печать
          // echo "<pre>" . $this->wddx. "</pre>" ;
          // var_dump($m) ;
          while (list ($key, $val) = each($m))
          {
               // Добавляем в поток HTML информацию о переменных
               $this->html .= "Аргумент <font color=\"black\">" ;
               $this->html .= "<b>" . $key . "</b></font>: ";
               if (is_array($val)) {
               $this->html .= implode( ' , ' ,$val) ; }
               else {$this->html .= $val; }
               $this->html .= "&nbsр ;&nbsр" ;
          }
       }
     if ($this ->wddx_flag == 1)
          {
          $this->wddx .="</". $name. ">" ;
               }
          }
// Обработчик символьных данных
function characterData( $parser, $data )
{
     if ($this->wddx_flag == 1) { // во избежание недомолвок
          $this->wddx .= $data;
          }
     else {
        $this-> html .= $data;
          }
       }
       //
// Разбор файла
//
function parse()
{
     $this-> xml_parser = xml_parser_create() ;
     xml_set_object ($this->xml_parser , &$this) ;
// обратите внимание на нивелирование регистров тегов
// что позволяет при любом раскладе установить соответствие
// их с записями $map_array
     xml_parser_set_option($this->xml_parser ,
                                       XML_OPTION_CASE_FOLDING, false );
     xml_set_element_handler ($this->xml_parser ,
                                       "startElement" , "endElement" );
     xml_set_character_data_handler ($thi s->xml_parser,
                                       "characterData") ;
if (!($fp = fopen($this->xml_file, "r")))
{
     die("He могу открыть входной файл XML");
}
while ($data = fread( $fp, 4096))
     <
     if ( !xml_parse($this->xml_parser , $data, feof($fp)))
       { die( spri ntf ("Ошибка в XML файле : %s на строке %d",
             xml_error_string( xml_get_error_code(
                                       $thi s->xml_parser)),
             xml_get_current_line_number ($thi s->xml_parser))) ;
                 }
             }
          }
       }
$log = new logger() ;
$log->xml_file ="/tmp/errorlog.txt";
$log->parse() ;

print ($log->html) ;

$log->destroy () ;
?>

Давайте рассмотрим работу этой программы подробнее. Состоит она из двух основных частей: формирование журнала и его последующий просмотр. Поскольку пользователь nobody, от имени которого обычно запускаются PHP-программы на сервере Apache, не имеет особых прав на работу с файловой системой, для размещения системного журнала мы будем использовать каталог tmp.

совет
       В некоторых версиях Linux доступ на запись в этот каталог «посторонним» пользователям закрыт, поэтому вам может потребоваться изменить права доступа к нему с помощью команды chmod a+w/tmp.

примечание
       Автор предупреждает, что такое решение может рассматриваться как грубейшее нарушение режима безопасности и не рекомендует повторять его на узлах, включенных в сеть Интернет. Подробнее см. [1], [2].

Обратите внимание на команду unlinks самом начале программы. Дело в том, что XML-документ может содержать только один элемент верхнего уровня, а команда еrror_l og осуществляет дописывание в конец существующего файла. Поэтому если вы не будете создавать системный журнал заново, то выполнить программу больше одного раза просто не сможете: анализатор XML аварийно завершит работу при обнаружении второго документа верхнего уровня.

После этого мы создаем новый файл журнала и помещаем в него открывающий тег <ERRORLOG>. Затем устанавливаем свой собственный обработчик ошибочных ситуаций, в задачу которого входит сформировать запись в XML-формате, которая будет записана в журнал. До тех пор, пока структура записи определена достаточно жестко:

<ERRORLOG>
     <ERRORENTRY>
     <DАТЕТIМЕ>Дата и время события</DАТЕТIМЕ>
     <ERRORNUM>Kод oшибки</ERRORNUM>
     <ERRORTYPE>Kaтегорияl</ERRORTYPE>
     <ERRORMSG>Сообщение об oшибкe</ERRORMSG>
     <SCRIPTNAME>Файл, в котором зафиксирована ошибкаa</SCRIPTNAME>
     <SCRIPTLINENUM>Hoмep строки в фаилe</SCRIPTLINENUM>
</ERRORENTRY>

никакой необходимости в использовании WDDX не возникает. Эти данные без особых проблем считываются и разбираются анализатором XML точно так же, как и в разделе 18.4. Но в некоторых ситуациях нам бы хотелось, чтобы в системный журнал наряду с текстом сообщения об ошибках помещались также значения переменных, действующих на момент возникновения этой ошибки. Стандартный механизм обработки ошибочных ситуаций PHP-машиной этой информации нам не дает. Но мы сможем справиться с этой проблемой и самостоятельно.

Для этого нам достаточно всего трех строчек:
$еrr .= "\t<VARTRACE>";
$еrr .= wddx_senialize_value($vars , "Variables");
$err .= "</VARTRACE>\n";

В результате мы создаем собственное поле <VARTRACE>, внутри которого поместим WDDX-пакет, составленный из списка локальных переменных (аргумент $vars), определенных в процедуре в момент обнаружения ошибки. Понятно, что и имена, и значения этих переменных будут меняться от функции к функции и от ошибки к ошибке. Но для нас важно другое: PHP-машина автоматически формирует WDDX-пакет и затем помещает его в выходной файл. В результате мы полу- чаем запись вида:

<ERRORENTRY>
<DATETIME>09-Aug-2001 10:44:42</DATETIME>
<ERRORNUM>512</ERRORNUM>
<ERRORTYPE>Предупреждение пользователя</ERRORTYPE>
<ERRORMSG>2-я координата вектора 1 не является числом</ERRORMSG>
<SCRIPTNAME>/usr/local/apache/htdocs/php/part2/error-proc.php</SCRIPTNAME>
<SCRIPTLINENUM>107</SCRIPTLINENUM>
       <VARTRACE>
          <wddxPacket version='1.0'>
             <header>
                    <comment>Variables</comment>
             </header>
             <data>
             <struct>
                    <var name=vect1'>
                         <array length='3'>
                              <number>2</number>
                              <number>3</number>
                              <string>foo</string>
                         </array>
                    </var>
                    <var name='vect2 ' >
                         <array length='3'>
                              <number>5.5</number>
                              <number>4.3</number>
                              <number>-1.6</number>
                         </array>
                    </var>
                    <var name='i'>
                         <number>2</number>
                    </var>
                    <var name='c1'>
                         <string>foo</string>
                    </var>
                    <var name='c2'>
                         <number>-1.6</number>
                    </var>
                    <var name='d'>
                         <number>0</number>
                    </var>
             </struct>
             </data>
          </wddxPacket>
       </VARTRACE>
</ERRORENTRY>

Как видите, WDDX-пакет органично помещается внутрь XML-доку мента, что одновременно и хорошо, и плохо. Хорошо с той точки зрения, что позволяет аккуратно закруглить4 главу, заявив, что анализ XML был рассмотрен выше и использование функции wddx_desrialize () автоматически решит все ваши проблемы. А плохо потому, что это на самом деле не так и анализ XML-документа со встроенными пакетами WDDX реализуется не столь прямолинейно, как нам хотелось бы.

Собственно говоря, именно поэтому в рассматриваемый нами пример я включил и новую версию анализатора, ориентированную именно на обработку сгенерированного нами файла.

Проблема заключается в том, что теги WDDX ничем, по сути, не отличаются от тегов XML, а следовательно, они будут автоматически обрабатываться обработчиками событий. Но так ли это здорово на самом деле? Ведь чтобы разобрать XML-документ с вложенными WDDX-пакетами вам потребуется фактически самостоятельно реализовать обработку тегов WDDX внутри анализатора. При этом вам не удастся «просто так» получить исходный код пакета, который можно одним вызовом функции превратить в структуру данных, аналогичную той, которая использовалась для синтеза пакета. Но разве мы за это боролись?

Единственное достаточно компактное решение состоит в том, что мы в процессе анализа восстанавливаем текст WDDX-пакета со всеми его атрибутами, а после завершения его сборки (о чем нам сигнализирует тег </VARTRACE>) мы разбираем полученный нами пакет функцией wddx_deserialize и только потом извлекаем из нее список переменных и их значения.

Результаты выполнения этой программы приведены на рис. 19.1. Как видите, нам удалось реализовать механизм отладки программы, который позволяет старым добрым методом контрольной печати вскрыть практически любые ошибки времени исполнения программ. Но гораздо важнее, что теперь в вашем распоряжении имеется механизм, позволяющий полностью реализовать межзадачный обмен данными — как из приложения на РНР, так и из внешнего мира.

Рис. 19.1. Формирование и считывание журнала
назад
далее

Сайт управляется системой uCoz