суббота, 24 апреля 2010 г.

Введение в Log4php, часть 1. Конфигурация

Как известно, писать логи хорошо и полезно. Данная статье посвящена одному из самых мощных логгеров для PHP — log4php.
Log4php относится к тому же семейству, что и любимый мной log4net, так что многие понятия повторяются. И, кстати, у log4php имеется достаточно хорошая документация. Настолько хорошая, что в этой моей статье, в общем-то, нет ровно ничего оригинального.

Установка

Для начала стоит скачать log4php. Рекомендую идти за исходниками прямо в репозиторий, т.к. версия на сайте несколько глючная. Репозиторий находится по адресу http://svn.apache.org/repos/asf/logging/log4php/trunk.
К слову. В архиве в папке src/examples есть несколько примеров.
После того, как скачали, берем из скачанного папку /src/main/php и переписываем ее содержимое в какую-нибудь папку log4php в своем проекте.
Конечно, у каждого PHP-шника свои соображения по поводу того, какова должна быть структура проекта. Я буду рассматривать простейший проект, состоящий из одного файла, и на его примере покажу, что нужно для того, чтобы подключить log4php. А дальнейшие архитектурные излишества пусть каждый выбирает сам. Итак, вот, что у меня получилось:
Структура проекта
В index.php остается добавить следующее:
<?php

require_once dirname(__FILE__) . '/Log4Php/Logger.php';
Logger::configure(dirname(__FILE__) . '/log4php.properties', 'LoggerConfiguratorIni');
$logger = Logger::getRootLogger();

$logger->info("Hello, World");

?>
Думаю, концепция понятна. Вопросы вызывает только конструкция Logger::configure. Что это значит, рассмотрим следующим номером программы.

Конфигурация

Log4php конфигурируется с помощью статической функции класса Logger со следующей сигнатурой: configure($configurationFile = null, $configurationClass = null). Конфиг должен быть в отдельном файле — XML, PHP или INI.
А еще есть класс LoggerConfiguratorBasic. Если мы не вызовем Logger::configure или вызовем его без параметров — именно этот класс создаст нам логгер по умолчанию, который будет писать в консоль в формате LoggerLayoutTTCC.

INI

В приведенном выше примере использован INI. Файл log4php.properties выглядит так:
log4php.appender.default = LoggerAppenderFile
log4php.appender.default.file = logs/app.log
log4php.appender.default.layout = LoggerLayoutPattern
log4php.appender.default.layout.ConversionPattern = %p %m%n
log4php.rootLogger = DEBUG, default
Терминология тут вся та же самая, что и в log4net, так что не буду подробно останавливаться на всех этих аппендерах и фильтрах.

XML

То же самое на XML:

log4php.xml

<?xml version="1.0" encoding="UTF-8" ?>
<log4php:configuration xmlns:log4php="http://logging.apache.org/log4php/" threshold="all">
     <appender name="default" class="LoggerAppenderFile">
        <param name="file" value="logs/app.log" />
        <layout class="LoggerLayoutPattern">
            <param name="ConversionPattern" value="%p %m%n" />
        </layout>
    </appender>
    <root>
        <level value="DEBUG" />
        <appender_ref ref="default" />
    </root>
</log4php:configuration>
Соответственно, в PHP-коде будет:
Logger::configure(dirname(__FILE__) . '/log4php.xml', 'LoggerConfiguratorXml');
Честно говоря, название класса писать необязательно, Logger и сам догадается по расширению.

PHP

И, наконец, конфигурация с помощью PHP-файла:
<?php

return array(
    'threshold' => 'ALL',
    'rootLogger' => array(
     'level' => 'DEBUG',
     'appenders' => array('default'),
    ),
    'appenders' => array(
     'default' => array(
      'class' => 'LoggerAppenderFile',
      'file' => 'logs/app.log',
      'layout' => array(
       'class' => 'LoggerLayoutPattern',
       'conversionPattern' => "%p %m%n",
      ),
     ),
    ),
);
?>
Соответственно:
Logger::configure(dirname(__FILE__) . '/log4php.php', 'LoggerConfiguratorPhp');
Сразу же напрашивается вопрос: если можно конфигурить log4php из файла с одним лишь массивом, то не проще ли не плодить файлы, а прямо завести массив и передать его конфигуратору. Однако, текущее положение дел в log4php не позволяет этого сделать, хотя в планах сообщества я где-то такое видела.

И что же выбрать?

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

Appenders

Напомню, что appender — это "пункт назначения" для логгера, т.е. то место, куда пишутся логи. Можно писать логи в файл (LoggerAppenderFile, LoggerAppenderDailyFile, LoggerAppenderRollingFile), в таблицу в БД (LoggerAppenderAdodb, LoggerAppenderPDO), в логи системы (LoggerAppenderSyslog) или апача (LoggerAppenderConsole), или отправлять по почте (LoggerAppenderMail).

Layouts

Layout — это настройка appender-а, с помощью которой можно задать формат вывода данных. Рассмотрим, что у нас здесь.

LoggerLayoutSimple

Самый простой логгер: сообщения лога выводятся в виде LEVEL - message. И никаких настроек.

LoggerLayoutTTCC

TTCC расшифровывается, как Time, Thread, Category and nested diagnostic Context. Этот layout используется всеми по умолчанию. Он выводит сообщения в виде %r [%t] %p %c %x - %m%n. С помощью всяких параметров с очевидными названиями (threadPrinting, contextPrinting и т.д.) каждое из этих полей можно спрятать. Кроме того, можно настраивать формат даты (параметр dateFormat)

LoggerLayoutPattern

В нашем примере был использован именно этот вариант. Честно говоря, я и в жизни только его использую, и в log4net тоже (только там он называется PatternLayout). Данный класс позволяет выводить самую разную информацию о том, где и при каких обстоятельствах был вызван лог. Формат вывода задается в параметре conversionPattern.
Ключи conversionPattern
%c
Название логгера. Этот ключ удобно использовать для обозначения категории лога. О категориях же — в следующей статье.
Возвращает название класса, из которого вызвана функция логгера. Какое-то время назад эта функциональность не работала, теперь залит соответствующий патч. Еще один повод качать log4php из репозитория.
%d
Дата и время. К ним, кстати, можно применять дополнительные форматы:
  • {ISO8601} — формат, используемый по умолчанию. Имеет вид: Y-m-d H:i:s,u.
  • {ABSOLUTE} — имеет вид: H:i:s.
  • {DATE} — имеет вид: d M Y H:i:s,u.
  • {<собственный формат даты>}
%F
Имя файла, из которого была вызвана функция логгера.
%l
Вся инфа о том, откуда была вызвана функция логгера, в виде <класс>.<метод>(<файл>:<строка>)
%L
Номер строки
%m
Текст сообщения
%M
Название метода
%n
Перевод строки
%p
Приоритет (уровень) сообщения. Уровни все те же, что и в log4net: INFO, DEBUG и т.д.
%r
Время, прошедшее с момента первого вызова логгера (в миллисекундах)
%t
Идентификатор потока
%u
Складывается впечатление, что собирались выводить какого-то юзера. Но код выглядит странно и не работает вовсе. При попытке использования исключения не вылетает, но в лог выводится строчка, содержащая все до этого самого %u.
%x
Nested diagnostic context. О нем тоже в следующей статье.
%X
Mapped diagnostic context. И нем тоже позже.
%%
Символ %

LoggerLayoutHtml

Лог выводится в HTML-таблицу. Кстати, получается довольно симпатично:
Результат работы LoggerLayoutHtml
Настраивать поля, правда, нельзя. У LoggerLayoutHtml вообще всего два параметра для настройки:
  • title — задает title выходного HTML-файла.
  • locationInfo — если true, то выводит еще одну колонку под названием File:Line, в которой выводятся имя файла и номер строки, в которых приключился лог.

LoggerLayoutXml

Лог выводится в XML вот такого вида:
<log4php:eventSet xmlns:log4php="http://logging.apache.org/log4php/" version="0.3" includesLocationInfo="true">
    <log4php:event logger="test" level="INFO" thread="7992" timestamp="1271684933369">
    <log4php:message><![CDATA[Something]]></log4php:message>
    <log4php:locationInfo class="SomeClass" file="C:\Something.php" line="52" method="getName" />
    </log4php:event>

    <log4php:event logger="test" level="INFO" thread="7992" timestamp="1271684933369">
    <log4php:message><![CDATA[Something else]]></log4php:message>
    <log4php:locationInfo class="SomeAnotherClass" file="C:\SomethingElse.php" line="52" method="getSomething" />
    </log4php:event>
</log4php:eventSet>
locationInfo, как и в прошлом случае, настраивается. Других параметров у данного layout-а нет.

Фильтры

Фильтр — это такая настройка appender-а, которая дает возможность блокировать сообщения по некоторому признаку. Стандартных фильтров в log4php совсем немного. Рассмотрим их очень кратко, ибо тут совсем все просто:
НазваниеНазначениеПример
LoggerFilterDenyAllБлокирует все сообщения лога
<appender name="echo" class="LoggerAppenderEcho">
    <layout class="LoggerLayoutSimple" />
    <filter class="LoggerFilterDenyAll" />
</appender>
LoggerFilterLevelMatchФильтр натравливается на сообщения с уровнем LevelToMatch и, в зависимости от значения параметра AcceptOnMatch, блокирует их или нет.Этот фильтр блокирует все сообщения с уровнем INFO.
<appender name="echo" class="LoggerAppenderEcho">
    <layout class="LoggerLayoutSimple" />
    <filter class="LoggerFilterLevelMatch">
        <param name="LevelToMatch" value="INFO" />
        <param name="AcceptOnMatch" value="false" />
    </filter>
</appender>
LoggerFilterLevelRangeФильтр натравливается на сообщения с уровнем от LevelMin до LevelMax и, в зависимости от значения параметра AcceptOnMatch, блокирует их или нет.Этот фильтр пропускает только INFO и DEBUG:
<appender name="echo" class="LoggerAppenderEcho">
    <layout class="LoggerLayoutSimple" />
    <filter class="LoggerFilterLevelRange">
        <param name="LevelMin" value="WARN" />

5 комментариев:

Анонимный комментирует...

Похоже неутверждённый патч не работает, как писался класс Logger, так и пишется

darja комментирует...

А Вы точно этот патч применили? Все работает.

Bear комментирует...

То же самое на XML: - похоже, пропущено то же самое на XML :)

darja комментирует...

2 Bear:
А ведь и правда. Похоже, Вы первый человек, прочитавший статью. Спасибо.

aloska комментирует...

Наконец хоть что-то толковое на русском про этот логгер. Спасибо, Дарья!