Полезное для программистов:

Фриланс
Новости
Статьи
   
Рубрики:

Error. Page cannot be displayed. Please contact your service provider for more details. (14)


Логгирование с помощью Log4j

Поиск:
Предположим вы пишете, или уже написали и отлаживаете, свое приложение. Пока приложение маленькое вам вполне хватет возможностей System.out.println(), но по мере увеличения и усложнения приложения его возможностей уже нехватает. В частности что нельзя (или затруднительно) сделать с помощью System.out.println():
1) управление логгированием: отключить логгирование одних сообщений, и включить логгирование других
2) запись логов в файл, БД и т.п.
3) вывод дополнительной информации о сообщении: время события, место события, контекст
4) форматирование вывода
для решения всех этих проблем и были созданы системы логгирования. Две наиболее популярные это Log4j от Apache и Java Logging API входящее в состав JDK 1.4 и старше. Надо еще упомянуть про Commons Logging, которая тоже часто используется. Commons Logging не является системой логгирования как таковой, это просто надстройка которая предоставляет единый интерфейс к системам логгирования (Log4j и Java Logging API). Из этих двух систем я предпочитаю Log4j, т.к. это готовая система логгирования, а Java Logging API для своего использования требует "доработки напильником".

Итак приступаем. Идем на сайт Log4j и скачиваем последнюю версию (на момент написания статьи последняя стабильная версия 1.2), распаковываем и подключаем log4j-X.X.XX.jar к проекту.

Создаем новый класс и пишем:
Код

package ru.vingrad.log;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class Test
{
  private static Logger logger = Logger.getLogger(Test.class);

  public static void main(String[] args) throws Exception
  {
    BasicConfigurator.configure();
    logger.info("Hello world!");
    logger.error("Error!", new Exception("An exception"));
  }
}

запускаем и получаем:
Код

0 [main] INFO ru.vingrad.log.Test  - Hello world!
0 [main] ERROR ru.vingrad.log.Test  - Error!
java.lang.Exception: An exception
    at ru.vingrad.log.Test.main(Test.java:14)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:623)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)


Итак, что же тут произошло.

Базовым классом через которое и будет идти все логгирование является org.apache.log4j.Logger. Для получения логгера используется статический метод getLogger() куда в качестве параметра надо передать имя этого логгера. Также есть перегруженный метод куда можно передать класс, и он вернет логгер с именем этого класса (в примере это ru.vingrad.log.Test).  Каждый логгер имеет уникальное имя, и вызов getLogger() с одним и тем же именем вернет один и тот же логгер. Логгер синхронизирован и его можно свободно использовать в многопоточной среде.

Для именования логгеров принято использовать правила именования пакаджей Java. Для логгеров существует иерархия аналогичная иерархии пакаджей, т.е. для ru.vingrad.log.Test родительским будет логгер ru.vingrad.log, для ru.vingrad.log - ru.vingrad и т.д. Плюс существует корневой логгер, являющийся родительским для всех остальных, получит его можно: Logger.getRootLogger(). Важным фактом является то, что логгер  если логгер не сконфигурирован индивидуально, то он будет использовать настройки своего родительского логгера. Т.е. мы можем сконфигурировать только корневой логгер, а все логгеры будут использовать его настройки. Так же логгер использует аппендеры родительского логгера (об аппендерах далее), это можно отключается свойством additivity.

Логгер может писать сообщения разных уровней (org.apache.log4j.Level), уровень очень важное понятие для управления логгированием. Уровни упорядочены по старшинству (в порядке убывания): FATAL, ERROR, WARN, INFO, DEBUG, TRACE. Уровни FATAL, ERROR, WARN предназначенны для сообщений об ошибках, разной степени серьезности, INFO информационные сообщения, DEBUG - отладочные, TRACE - очень подробная отладочная информация. Когда мы конфигурируем систему логгирования мы указываем сообщения какого уровня надо писать в лог. В лог будут писаться сообщения этого уровня и выше, т.е если мы настроили систему на уровень INFO то будут писаться сообщения с уровнями FATAL, ERROR, WARN, INFO. Плюс еще есть спец уровни ALL и OFF, которые предназначены для включения/выключения записи всех сообщений.

Следующая важная составляющая системы логгирования это - аппендер (org.apache.log4j.Appender). Аппендер это класс который занимается непосредственно записью сообщений. org.apache.log4j.Appender это интерфейс, но у него есть реализации для записи сообщений в консоль, файл, БД, e-mail и т.д. Так же у аппендера можно установить фильтры (org.apache.log4j.spi.Filter) для фильтрации сообщений. И форматер сообщений (org.apache.log4j.Layout) для форматирования выводимых сообщений.

А теперь со всей этой фигней на борту мы попробуем взлететь. Как же происходит запись логов?
1. Вызываем logger.info("Hello world!")
2. Происходит проверка уровня логгирования для данного логгера, если данному логгеру не был явно задан уровень, то тогда проверка осуществляется для его родителей. Если уровень логгера выше уровня сообщения, то оно отбрасывается и запись его не происходит. В нашем случае корневой аппендер сконфигурирован на уровешь DEBUG а у сообщения уровень INFO, т.е. уровень логгера ниже уровня сообщения и оно проходит проверку.
3. Далее сообщение передается аппендерам установленным для данного логгера (если есть).
4. Если свойство additivity установлено в true (по умолчанию оно установлено), то сообщение так же передается родительским логгерам.
5. Аппендер получив сообщение проверяет его установленными фильтрами (если есть). Если сообщение прошло фильтры оно форматируется и записывается в лог.

Это все конечно хорошо, но как же конфигурировать логгеры, не кодом же в main() добавлять аппендеры и логгеры?
Для конфигурирования Log4j используются файлы конфигурации log4j.xml и log4j.properties, можно загружать конфигурацию вручную, но проще положить один из этих файлов в CLASSPATH и тогда при запуске Log4j сам загрузит из него конфигурацию. Какой из конфигов использовать дело вкуса, мне больше нравится xml, да и возможностей у него побольше, так что расскажу как конфигурировать с его помощью.

Итак создаем в папке с исходниками файл log4j.xml и пишем в него:
Код

<?xml version='1.0' encoding='ISO-8859-1' ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
</log4j:configuration>

(файл log4j.dtd можно найти внутри log4j.jar)

Для начала добавим аппендеры. Консоль:
Код

  <appender name="CONSOLE-DEBUG" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.out"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%5p] %m at %l%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="LevelMin" value="ALL"/>
      <param name="LevelMax" value="INFO"/>
    </filter>
  </appender>

CONSOLE-DEBUG имя аппендера, по этому имени мы будем его упоминать в этом конфиге.
org.apache.log4j.ConsoleAppender - имя класса аппендера
<param name="target" value="System.out"/> - устанавливаем параметр аппендера который определяет куда писать лог
<layout - форматер, подробнее о формате шаблона можно почитать в Java doc по классу форматера (org.apache.log4j.PatternLayout)
<filter - фильтр сообщений, в данные аппендер будут писаться все сообщения уровней INFO, DEBUG и TRACE

Аналогично конфигурируем аппендер для ошибок:
Код

  <appender name="CONSOLE-WARN" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.err"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%5p] %m at %l%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="LevelMin" value="WARN"/>
    </filter>
  </appender>

разница в том, ошибки будут писаться в System.err.

Конфигурируем запись в файл
Код

  <appender name="LOG-FILE-APPENDER" class="org.apache.log4j.FileAppender">
    <param name="file" value="app.log"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%5p] %c %m at %l%n"/>
    </layout>
  </appender>

тут все аналогично, параметр file задает имя файла.

Теперь сконфигурируем логгеры.

Предположим что мы используем Hibernate и хотим видеть только сообщения об ошибках и писать их только в  консоль но не файл.
Код

  <category name="org.hibernate" additivity="false">
    <priority value="WARN"/>
    <appender-ref ref="CONSOLE-WARN"/>
    <appender-ref ref="CONSOLE-DEBUG"/>
  </category>

но в то же самое время, мы хотим видеть отладочные сообщения в org.hibernate.SQL (где пишутся выполняемые Hibernate SQL запросы).
Код

  <category name="org.hibernate.SQL">
    <priority value="DEBUG"/>
  </category>


Для всех остальных категорий включаем уровень DEBUG и запись в консоль и файл
Код

  <root>
    <priority value="DEBIG"/>
    <appender-ref ref="CONSOLE-WARN"/>
    <appender-ref ref="CONSOLE-DEBUG"/>
    <appender-ref ref="LOG-FILE-APPENDER"/>
  </root>


В итоге мы получаем следующий файл:
Код

<?xml version='1.0' encoding='ISO-8859-1' ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>

  <appender name="CONSOLE-DEBUG" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.out"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%5p] %m at %l%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="LevelMin" value="ALL"/>
      <param name="LevelMax" value="INFO"/>
    </filter>
  </appender>

  <appender name="CONSOLE-WARN" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.err"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%5p] %m at %l%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="LevelMin" value="WARN"/>
    </filter>
  </appender>

  <appender name="LOG-FILE-APPENDER" class="org.apache.log4j.FileAppender">
    <param name="file" value="app.log"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%5p] %c %m at %l%n"/>
    </layout>
  </appender>

  <category name="org.hibernate" additivity="false">
    <priority value="WARN"/>
    <appender-ref ref="CONSOLE-WARN"/>
    <appender-ref ref="CONSOLE-DEBUG"/>
  </category>

  <category name="org.hibernate.SQL">
    <priority value="DEBUG"/>
  </category>

  <root>
    <priority value="DEBIG"/>
    <appender-ref ref="CONSOLE-WARN"/>
    <appender-ref ref="CONSOLE-DEBUG"/>
    <appender-ref ref="LOG-FILE-APPENDER"/>
  </root>

</log4j:configuration>


Теперь самое время протестировать, что получилось.
Код

public class Test
{
  private static Logger logger = Logger.getLogger(Test.class);

  public static void main(String[] args) throws Exception
  {
    logger.debug("Inside main()");
    logger.info("Hello world!");
    logger.error("Error!", new Exception("An exception"));

    Logger hibernateGeneral = Logger.getLogger("org.hibernate");
    hibernateGeneral.debug("Starting Hibernate");

    Logger hibernateSql = Logger.getLogger("org.hibernate.SQL");
    hibernateSql.debug("select * from my table");

    hibernateGeneral.error("Hibernate error");
  }
}

на выходе получим:
Код

2008-06-06 18:18:16,253 [DEBUG] Inside main() at ru.vingrad.log.Test.main(Test.java:11)
2008-06-06 18:18:16,253 [ INFO] Hello world! at ru.vingrad.log.Test.main(Test.java:12)
2008-06-06 18:18:16,253 [ERROR] Error! at ru.vingrad.log.Test.main(Test.java:13)
java.lang.Exception: An exception
    at ru.vingrad.log.Test.main(Test.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:623)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
2008-06-06 18:18:16,253 [DEBUG] select * from my table at ru.vingrad.log.Test.main(Test.java:19)
2008-06-06 18:18:16,268 [ERROR] Hibernate error at ru.vingrad.log.Test.main(Test.java:21)


Автор благодарит алфавит за любезно предоставленные буквы :)
Автор: LSD
Сайт: about:blank






Просмотров: 30363

 

 

Новые статьи:


Популярные:
  1. Как сделать цикличным проигрывание MIDI-файла?
  2. Создание AVI файла из рисунков
  3. Как устройство "отключить в данной конфигурации"?
  4. Kто в данный момент присоединен через Сеть?
  5. Как узнать количество доступной памяти?
  6. Как реализовать в RichEdit разноцветный текст?
  7. Как скрыть свое приложение от ProcessViewer
  8. Как программно нажать/скрыть/показ кнопку "Start"?
  9. Модуль работы с ресурсами в PE файлах
10. Функции вызова диалоговых окон выбора
11. Проверка граматики средствами Word'а из Delphi.
12. Модуль для упрощенного вызова сообщений
13. Функции для записи и чтение своих данных в, ЕХЕ- файле
14. Рекурсивный просмотр директорий
15. Network Traffic Monitor
16. Разные модули
17. Универсальная функция для обращения к любым экспортируем функциям DLL
18. Библиотека от VladS
19. Протектор для UPX'а
20. Еще об ICQ, сообщения по контакт листу?
21. Использование открытых интерфейсов
22. Теория и практика использования RTTI
23. Работа с TApplication
24. Примеры использования Drag and Drop для различных визуальных компонентов
25. Что такое порт? Правила для работы с портами
26. Симфония на клавиатуре
27. Загрузка DLL
28. Исправление автоинкремента
29. Взаимодействие с чужими окнами
30. Проверить дубляжи в столбце


 

 

 
 
На главную