А это клавиатура, с помощью которой я творю...
http://kurepin.ru/php/slang.ru/10/
Rambler's Top100
Строим сайт slang.ru, глава 10

Строим сайт slang.ru

Глава 10. class_utils: журнал операций и обработка ошибок


Ну что, распишем некоторые функции в классе class_utils? А почему бы и нет. Тем более что этот класс в нашем ООП-древе идет сразу за class_mysql.

log_insert().

Мы уже договорись о том, что журнал операций будет храниться в виде таблицы базы данных. Самое время создать эту таблицу:

create table tbl_log
(
 l_id       bigint unsigned not null auto_increment primary key,
 l_user     smallint unsigned not null default 0,
 l_dt       datetime not null,
 l_action   smallint not null default 0,
 l_dic      smallint not null default 0,
 l_ip       char(15) not null default '',
 l_desc     varchar(255) not null default ''
);

По строкам:
  • l_id - идентификатор записи;
  • l_user - идентификатор пользователя, совершившего действие;
  • l_dt - дата и время выполнения действия;
  • l_action - код операции;
  • l_dic - словарь, в рамках которого выполнялась операция
  • l_ip - IP-адрес компьютера, с которого было выполнено действие;
  • l_desc - комментарий к действию в свободном текстовом виде.

При необходимости, потом эту таблицу можно расширить и дополнить.

Сама функция получилась у меня такой:

<?   // журнал операций   function log_insert($action=0$dic=0$desc='')   {    $user=$this->u_id// идентификатор пользователя    $action=(int)$action// только целое число    $dic=(int)$dic// только целое число    $ip=$_SERVER['REMOTE_ADDR']; // IP пользователя берем из окружения    $desc=AddSlashes($desc); // описание ошибки может содержать кавычки    $this->sql_query='insert into tbl_log';    $this->sql_query.='(l_user, l_dt, l_action, l_dic, l_ip, l_desc) ';    $this->sql_query.='values('.$user.', now(), '.$action.', '.$dic.', "'.$ip.'", "'.$desc.'")';    $err=$this->sql_execute();    if($err) return($err);    return(0);   } ?>

Внимание! Еще раз напоминаю, что скрипты пишутся "на лету" и проверять я их буду потом, по ходу отладки, поэтому никаких гарантий относительно их работоспособности - не даю!

Функция очень проста, как вы видите: сбор основных данных и размещение их в таблице tbl_log нашей базы данных.

Обратите внимание на то, что переменные в объявлении функции даны с указанием значений по умолчанию. Это сделано для того, чтобы в функцию можно было передавать не все значения, а только те, которые у нас есть. Остальные переменные будут иметь значения по умолчанию, а php-интерпретатор не будет ругаться на то, что мы не все значения предали в функцию при ее вызове.

В этой функции встречается новая глобальная переменная $u_id. Она будет присваиваться в случае удачной авторизации пользователя. Пока же надо объявить ее в class_utils:

<?  var $u_id// идентификатор пользователя ?>

err_report() - обработчик ошибок.

Я уже немного рассказал о том, как работает эта функция, поэтому давайте ее запишем. Она хоть и маленькая, но очень умная и полезная:

<?   // обработчик ошибок   function err_report($num$sendmail=true)   {    $num=(int)$num// номер ошибки должен быть целым числом    $str=''// обнуляем переменную с описанием ошибки    $file_err=$this->path_info.'/errors.txt'// путь к файлу с ошибками    // открываем файл ошибок для чтения    if($r=@fopen($file_err'r'))    {     flock($r,1); // залочить файл на чтение     while((!feof($r)) && ((int)$str-$num)) $str=fgets($r,1024);     fclose($r); // закрываем файл     list($found_num,$str)=explode("\t",trim($str));     } else     {      $str='no description';      $this->err_to_support('0 ['.$num.']','не удалось открыть файл '.$file_err);      $sendmail=false;    }    if(($found_num<=$this->debug_level) && ($sendmail))    {     // отправляем письмо администратору     $err=$this->err_to_support($num,$str);     if($err$err_note=' ('.$this->err_report(301,false).')';    }    // формируем окончательное сообщение для пользователя    $str=$str.$err_note;    return($str); // возвращаем текст   } ?>

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

Вот это выражение понятно?

<? while((!feof($r)) && ((int)$str-$num)) $str=fgets($r,1024); ?>

Кому не понятно, объясняю: цикл while выполняется до тех пор, пока не найден конец файла (!feof) или пока результатом вычитания из номера ошибки ($num) очередной строки (ее целочисленного начала) не станет ноль. С тем же успехом можно было написать не $str-$num, а $str!=$num.

В данной реализации есть небольшой баг: если номер ошибки в файле не был обнаружен, а сам файл заканчивается строкой с номером, то переменной $str будет присвоен текст из последней строки. Можно было бы от этого защититься, поставив в конце файла что-нибудь вроде:

; end of file

Но я решил превратить баг в фичу - записал последней строкой в файл errors.txt:

1	не определена

Таким образом, если файл удалось прочесть, а номер ошибки не был найден, описанием ошибки станет фраза "не определена".

Это весьма удобная фича, выполняющая два важных дела: первое - дает соответствующее описание неизвестным ошибками; второе - отправляет уведомление администратору, так как номер у этой ошибки - 1 - самый низкий. Видите, мы на протяжении всей функции используем переменную $num, полученную извне, а в проверке на критичность ошибки перед отправкой письма используем переменную $found_num, в которой хранится номер найденной ошибки при просмотре файла. $found_num всегда будет равна $num, кроме случая, когда описание ошибки в файле не будет найдено. На этом и сыграли.

Что будет, если не обнаружится сам файл errors.txt или если его не удастся прочесть? Будет произведена отправка соответствующего уведомления администратору, а пользователь получит номер ошибки и заглушку "no description" вместо описания. Заодно мы выставляем $sendmail в состояние "false", чтобы дважды не получать сообщение об ошибке.

При отправке письма администратору тоже производится анализ возвращенной ошибки; если письмо не было отправлено, мы рекурсивно вызываем функцию err_report(), передав ей в качестве параметра, номер ошибки и запрет на отправку письма администратору. Почему запрет? Да потому, что подобная рекурсия может превратиться в замкнутый цикл. А нам этого совсем не надо. Однако, получив ошибку отправки сообщения администратору, мы уведомляем об этом пользователя, добавив описание ошибки 301 к описанию основной ошибки.

Ошибку 301 я записал как:

301	не удалось отправить администратору сервера email-уведомление;
пожалуйста, найдите способ связаться с администрацией сервера,
чтобы сообщить номер ошибки - это очень важно!

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

Готовы идти дальше, господа программисты? Тогда - вперед!





[шаг назад] [печатать] [в начало сайта]



copyright ©2000-2017 Ruslan Kurepin