понедельник, 14 марта 2011 г.

Log4j эксплоит

Все мы знаем о том, как прекрасен log4j. Вставляешь выводы для разных уровней DEBUG, INFO, ERROR в коде, а потом через настройки в текстовом формате можешь контролить степень подробности описания выполнения проги. Код написанный для log4j не является бизнес логикой, он не влияет на ход выполнения проги никак, лишь описывая текущее состояние системы для последующего администрирования.
Однако log4j можно применить и непосредственно для создания функциональности.
Мне потребовалось написать программу, сохраняющую поток некоторых данных в текстовом виде, с разбиением данных на отдельные файлы в зависимости от календарного дня. Была необходима  архивация данных прошедших дней. Можно было написать все это вручную, но зачем, когда все требуемые действия умеет производить log4j.
Разбиение в зависимости от даты с последующей архивацией завершенных файлов умеет выполнять org.apache.log4j.rolling.RollingFileAppender. Это класс из дополнительного пакета log4j-extras. Так что нам понадобятся две джарки: log4j и log4j-extras. Вот пример конфигурации в log4j.xml, в которой

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">

<log4j:configuration>
  <appender name="dailyLog" class="org.apache.log4j.rolling.RollingFileAppender">
    <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
      <param name="FileNamePattern" value="/tmp/my-log.%d{yyyy-MM-dd}.gz"/>
    </rollingPolicy>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} - %c{1} - %m%n"/>
    </layout>
  </appender>
  <logger name="MyBusinessData">
      <level value="info"/>
      <appender-ref ref="dailyLog"/>
  </root>
</log4j:configuration>

В этой конфигурации
  • осуществляется вывод логгера MyBusinessData с уровнем не ниже INFO
  • вывод осуществляется в файлы /tmp/my-log.*  - в зависимости от
    даты, так логирование, произошедшее 14 марта 2011 запишется в файл /tmp/my-log.2011.03.14; подробнее про настройку FileNamePattern здесь
  • по окончанию дня соответствующий файл архивируется gzip'ом.
  • в самом файле формат вывода определяется строчкой "%d{HH:mm:ss,SSS} - %c{1} - %m%n", где вначале идет формат времени, затем указание категории, затем само сообщение и символ новой строки. Подробнее про настройку ConversionPattern здесь

Теперь я могу записывать свои данные с помощью обычного логгера:
private Logger dataLogger = Logger.getLogger("MyBusinessData");
...
dataLogger.info(data);
и при указанной конфигурации данные будут сохраняться так как и требовалось. Однако возникает проблема которая как раз и связана со словами "при указанной конфигурации". Проблема в том, что изменив текстовый файл настройки log4j.xml (либо тупо забыв его поместить в нужное место и указать нужные значения), можно заглушить запись данных, то есть поломать программу. Гибкость настройки log4j в нашем случае оборачивается опасностью. Непорядок!
Поэтому переходим к программной настройке. Идея в том, чтобы игнорировать какие-либо настройки в текстовом файле для нашего дата-логгера, а вместо этого использовать жестко заданные в коде.
Вот те же самые настройки, произведенные программно:
dataLogger.removeAllAppenders();    //we don't want any configuration other than this
dataLogger.setAdditivity(false);    //avoid sending messages to root logger
RollingFileAppender rfa = new RollingFileAppender();
TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy();

//the smallest unit in the pattern is day, so rolling will occur at the end of each day
//.gz suffix tells log4j to compress finished day
rollingPolicy.setFileNamePattern("/tmp/my-log.%d{yyyy-MM-dd}.gz");
rollingPolicy.activateOptions();
rfa.setRollingPolicy(rollingPolicy);
PatternLayout layout = new PatternLayout();
layout.setConversionPattern("%d{HH:mm:ss,SSS} - %c{1} - %m%n");
rfa.setLayout(layout);
rfa.activateOptions();
dataLogger.addAppender(rfa);
dataLogger.setLevel(Level.INFO);

Очень важна здесь вторая строчка dataLogger.setAdditivity(false). Благодаря ей, сообщения, записываемые нашим дата-логером, не будут дублироваться на верхние уровни иерархии, в частности на рут логер. Таким образом, настроив обычное логирование даже для рута, мы не будем получать в довесок все данные логируемые нашим логером. Т.е. мы отдельно настроили дата-логер без ущерба для задачи логирования. Теперь можно удалить файл настроек log4j, либо настроить произвольным образом логирование, наша запись данных всегда будет производиться и не мешать остальному логированию. Ура!


Комментариев нет:

Отправить комментарий