Гибкое журналирование с помощью log4j
Гибкое журналирование с помощью log4j
Log4j — это инструмент для журналирования с открытым исходным кодом, разработанный под эгидой глобального проекта Jakarta Apache. Он представляет собой набор API, с помощью которых разработчики могут вставлять в свой код выражения, выводящие некоторую информацию (отладочную, информационную, сообщения об ошибках и т.д.), и конфигурировать этот вывод с помощью внешних конфигурационных файлов. В этой статье рассматриваются основные идеи, заложенные в данный инструмент, а также затрагиваются некоторые интересные моменты, касающиеся написания демонстрационного web-приложения.
Программа, использованная в этой статье в качестве примера, представляет собой web-приложение, которое было разработано и протестировано с помощью JDK 1.3.1, Tomcat 4.0.1 ( http://jakarta.apache.org/tomcat ), log4j 1.2.4 ( http://jakarta.apache.org/log4j ) и Ant 1.4.1 ( http://jakarta.apache.org/ant ). Вся работа выполнялась на компьютере с установленной операционной системой Windows XP. Вы можете загрузить файл приложения log4jdemo. war ( http://java.linux.by/bible-arc/log4jdemo.war ), которое можно легко развернуть в любом сервлет-контейнере. Вы можете также загрузить исходный код этого приложения ( http://java.linux.by/bible-arc/log4jsrc.zip ).
Наше приложение содержит следующие ветки, очень часто встречающиеся во многих web-приложениях:
1. Основная страница -> Авторизированный вход -> Страничка приветствия -> Отправление комментария.
2. Основная страница -> Регистрация -> Авторизированный вход.
3. Основная страница -> Страничка для тех, кто забыл пароль.
Очень важно, чтобы вы понимали, что данное приложение является очень тривиальным. Единственная его цель в этой статье — помочь вам научиться работать с log4j. Вы вправе спросить, почему было выбрано именно web-приложение, а не обычное Java-приложение. Все просто. На сегодняшний день очень многие web-приложения используют предпочтительно механизм журналирования именно на основе log4j. В свою очередь, среди обычных Java-приложений процент несколько ниже, но популярность log4j стремительно растет, что неудивительно.
Настройка
Загрузить log4j можно с сайта Apache: http://jakarta.apache.org/log4j/ docs/download.html. На сайте вам будет предложено выбрать один из двух типов форматов архива с log4j — это tar.gz (http://jakarta.apache.org/ log4j/jakarta-log4j-1.2.4.tar.gz) и zip (http://jakarta.apache.org/log4j/jakarta-log4j-1.2.4.zip). Каждый из этих архивов содержит в себе исходный код log4j, документацию, примеры и непосредственно дистрибутивные .jar-архивы log4j. Распакуйте загруженный вами архив в какую-нибудь директорию по вашему выбору. Исходный код поставляется для того, если вы вдруг захотите скомпилировать и собрать log4j самостоятельно. Эту операцию вы можете провести с помощью Ant и предоставляемых вместе с исходниками файлов build.xml. Если потребуется, вы можете легко изменить эти скрипты (build.xml). После компиляции и сборки вы получите файл log4j-1.2.4.jar, который будет находиться в директории dist/lib. Учтите, что для успешной сборки вам понадобится Java Management Extensions (JMX) API, который должен быть прописан в переменной окружения CLASSPATH.
Чтобы использовать классы log4j в своем приложении, вам необходимо добавить в CLASSPATH вашего приложения файл log4j-1.2.4.jar. Этот файл, как уже было упомянуто ранее, находится в директории dist/lib. В случае с нашим примером web-приложения необходимо, чтобы этот файл располагался внутри .jar-архива этого приложения, в директории WEB-INF/lib. Это все, что необходимо, чтобы наше приложение могло работать с log4j API. Остальные усилия нужно приложить лишь для написания конфигурационного файла, который будет определять для log4j, что, куда и как нужно журналировать, а также для вставки выражений в код самого приложения, которые непосредственно будут журналировать события.
Что такое Logger?
Прежде, чем приступить к рассмотрению кусков кода нашего web-приложения, давайте разберем основные понятия в log4j. Log4j имеет три базовые составляющие: logger (журналирующий элемент), appender (элемент, определяющий, куда нужно журналировать) и layout (представление, в котором будет производиться журналирование). Для удобства мы так и будем их называть на протяжении всей статьи. Logger берет на себя функцию журналирования какого-либо события в место назначения, определяемое элементом appender, в специально определенном формате, который задается элементом layout.
Logger можно представлять себе как компонент некоего приложения, который будет принимать и выполнять ваши запросы на запись каких-либо событий в регистрационный журнал, представленный файлом, базой данных, консолью и т.д. (определяется appender'ом). Каждый класс вашего приложения может иметь свой собственный logger или же быть привязанным к общему logger'у. Log4j предусматривает корневой logger, от которого будут наследоваться все создаваемые вами объекты этого типа. Это значит, что, если у вас не предусмотрен свой logger для каждого объекта, вы всегда сможете воспользоваться корневым, с помощью метода Logger.getRootLogger(), однако так поступать не рекомендуется.
Для того, чтобы создать logger и использовать его для журналирования каких-либо событий, возникающих в этом классе, вам в самом простом случае необходимо вызвать статический метод класса Logger, который получит за вас этот объект по указанному имени. Если такой объект на данный момент еще не существует, он будет создан. Но нужно учесть, что в любой момент времени может существовать только один экземпляр объекта logger'а с одним и тем же именем.
Logger'ы обязательно должны знать, куда им нужно слать ваши запросы на запись событий. Эта функция ложится непосредственно на плечи элементов appender. Log4j поддерживает запись в файлы (FileAppen-der), на консоль (ConsoleAppender), в базы данных (JDBCAppender), в журнал событий операционных систем семейства Windows NT/2000/XP (NTEventLogAppender), на SMTP-серверы (SMTPAppender), на удаленные серверы (SocketAppender) и не только. Таким образом, понятно, что appender определяет место, куда нужно журналировать события. Так, например, если мы привяжем JDBCAppen-der к одному из наших logger'ов, это будет означать для log4j, что все запросы на журналирование событий, поступающих от этого logger'а, будут записываться в определенную базу данных с заданными параметрами (URL, имя пользователя, пароль и т.д.). Эти параметры являются свойствами объектов элементов типа appender (в данном случае JDBCAppender).
Итак, logger'ы и appender'ы занимаются созданием и направлением наших запросов на журналирование. А что насчет формата вывода? Тут в игру вступают элементы layout. Эти элементы определяют стиль и содержание выводимых в журнал записей. Log4j предоставляет несколько предопределенных layout'ов. Однако вы можете без проблем создать свой собственный, если возникнет такая необходимость. Элемент layout может определять, включать или не включать в выводимые записи дату и время, включать ли информацию об используемом для вывода объекте logger, включать или нет информацию о номере текущей строки, в которой был создан этот запрос на журналирование, и т.д.
Все элементы logger следуют шаблону отношений типа родитель — потомок. Как упоминалось ранее, log4j предусматривает по умолчанию корневой элемент logger. Отношение типа родитель — потомок также соответствует шаблону именования классов. Это подразумевает, что абсолютно все наши элементы logger будут наследовать корневой элемент. Отношения типа родитель — потомок хорошо просматриваются в шаблоне именования классов. Скажем, у вас есть класс MyClass, который находится в пакете с именем com.foo.bar. Теперь создадим экземпляр элемента logger с именем com.foo.bar.MyClass. В этом случае logger с именем com.foo.bar.MyClass будет потомком logger'а com.foo.bar, если такой существует. Если же он не существует, то родителем logger'а com.foo.bar.MyClass будет считаться корневой элемент logger (это, конечно, в случае, если не существует также logger'ов с именами com.foo и com).
Каждому элементу logger в log4j привязывается свой уровень. Если такой уровень не будет привязан к какому-нибудь конкретному элементу, то этот элемент будет автоматически приобретать уровень своего родителя. Даже в случае, если пользователь совсем не будет присваивать никаких уровней создаваемым им элементам logger, они будут иметь уровень корневого элемента, который по умолчанию равен DEBUG. Таким образом, каждый элемент logger будет гарантированно иметь определенный уровень.
Log4j предусматривает пять уровней журналирования:
• DEBUG
• INFO
• WARN
• ERROR
• FATAL
Эти уровни определяют также порядок журналирования, которому следует log4j. Об этом ниже.
Каким образом установленный для элемента logger уровень влияет на его функциональность? Все просто. Дело в том, что запрос на занесение в журнал какого-либо события с помощью определенного элемента appender из нашего приложения будет выполнен лишь в том случае, если уровень журналируемого события (запроса) не ниже, чем уровень самого элемента logger, через который этот запрос был создан. Это означает, что все запросы, которые мы можем создавать в нашем приложении, могут быть только какого-то одного из этих пяти уровней. Это очень важное правило, и фактически это является ядром функциональности log4j. Давайте вернемся к нашему предыдущему примеру com.foo.bar.MyClass и попробуем более подробно рассмотреть все, о чем сейчас говорилось.
Предположим, в пределах класса com.foo.bar.MyClass (мы назвали класс и logger для этого класса одним и тем же именем — это обычная практика для программистов, использующих log4j), используя его logger, вы пытаетесь журналировать некоторое сообщение типа WARN. Теперь, скажем, ваш logger com.foo.bar.MyClass имеет уровень ERROR, который был установлен вами при конфигурации. Что это означает? Это означает, что ваш запрос на журналирование типа WARN выполнен не будет.
Почему? Потому, что log4j определяет, что уровень запроса на журналирование ниже, чем уровень самого logger'а, через который этот запрос был сделан. Если бы этот запрос имел уровень ERROR или FATAL, то он без проблем был бы выполнен, и сообщение заняло бы свое место в регистрационном журнале согласно используемому элементу appender. Аналогично, если бы уровень logger'а был WARN, то этот запрос также был бы выполнен. Итак, мы пришли к очень важной идее, заложенной в log4j. Вы можете извне менять уровень каждого из ваших элементов logger без необходимости вносить какие-либо изменения в исходный код приложения, без необходимости перекомпилировать приложение и разворачивать его заново.
Основное конфигурирование log4j происходит посредством внешнего конфигурационного файла. API также предоставляет возможность конфигурировать систему log4j и из приложения. Далее мы рассмотрим простой пример приложения и посмотрим, как правильно создавать конфигурационный файл.
Пример использования log4j
Приложение, которое мы собрались рассматривать в качестве примера, содержит пять сервлетов (SetupServlet (стартует при запуске сервлет-контейнера), LogonServlet, GetCommentsServ-let, ForgotPasswordServlet, Register Servlet), четыре JSP-страницы (forgotpassword, logon, register, welcome), а также базу данных в виде файла, в которой хранится информация о пользователях (файл userDB, который должен располагаться в директории WEB-INF; убедитесь, что путь к нему корректно прописан в конфигурационном файле web.xml).
Чтобы увидеть это приложение в действии, разверните файл log4j demo.war в вашем сервлет-контейнере. Как только вы это сделали, измените файл web.xml таким образом, чтобы параметр init сервлета SetupServlet для файла props содержал путь к файлу config-simple.properties. Этот файл находится в директории WEB-INF нашего приложения (там также содержатся три других конфигурационных файла: config-JDBC, config-MDC, config-NDC). Теперь перезапустите ваш сервер (в данном случае имеется в виду сервлет-контейнер). На консоли вашего сервера появятся некоторые сообщения. Если SetupServlet смог найти файл userDB, то вы, возможно, увидите следующие сообщения:
Using properties file:
C:\jakarta-tomcat-4.0.1\ webapps\Log4JDemo\WEB-INF\config-simple.properties
Watch is set to: null
DEBUG — Added line: JohnDoe, herbert,johndoe@john.com,John Doe
Первые два выражения, появившиеся на вашем экране, представляют собой простой вывод с помощью выражений System.out.println. Следующие же два сообщения были выведены с помощью log4j. Давайте посмотрим, как это было сделано в программе.
Откройте файл config-simple.properties в вашем любимом текстовом редакторе. Этот файл содержит всего три строчки (не считая комментариев). Первая строка (log4j.rootLogger= debug, R)сообщает log4j, что уровень корневого элемента logger будет DEBUG. Такой уровень для корневого элемента устанавливается по умолчанию, и поэтому эта строчка не обязательна. Однако значение после запятой (R) является обязательным. Оно сообщает log4j, что корневой элемент logger должен быть привязан к элементу appender с именем "R". Оставшиеся строки в этом файле определяют свойства этого appender'а R.
Строка log4j.appender.R=org.apa-che.log4j.ConsoleAppender говорит о том, что appender R имеет тип ConsoleAppender; т.е. это означает, что вывод будет производиться на консоль. Строка log4j.appender.R.layout=org.apache.log4j.SimpleLayout сообщает log4j, что для appender'а R будет использоваться простой элемент layout, который, как вы могли заметить, просто выводит две вещи: имя уровня запроса на журналирование и непосредственно само сообщение.
Теперь, когда мы разобрались с простым примером конфигурационного файла, самое время посмотреть, как происходит журналирование в самом коде приложения. Для начала откройте файл SetupServlet.java в текстовом редакторе и найдите 81 строку:
PropertyConfigurator.configure(props);
Пока не обращайте внимания на выражения, находящиеся рядом с этой строкой. Эта строчка сообщает log4j, что требуется найти файл, определенный переменной props, и использовать его для установки настроек log4j. Это и есть тот самый файл, который мы только что с вами разобрали, config-simple.properties. Эта строка должна выполняться только один раз за всю работу цельного приложения. Обычно она вызывается при старте приложения, а сервлет, вызываемый при старте сервлет-контейнера — это идеальное место для выполнения этого конфигурирования в web-приложении.
Как только мы выполнили все операции по конфигурированию log4j, можно приступать непосредственно к журналированию. Следующим шагом необходимо получить или создать нужный logger, и уже потом с его помощью вставлять в код программы выражения для вывода сообщений в регистрационный журнал. Это можно сделать либо с помощью корневого элемента logger, вызвав метод Logger.getRootLogger(), либо же попытаться взять logger класса, в котором мы сейчас находимся (т.е. из которого хотим журналировать какое-либо событие) — в нашем случае это SetupServlet. В случае второго варианта мы получим logger с именем demo.log4j.servlet.SetupServlet, выполнив следующую строчку кода:
Logger log = Logger.getLogger (SetupServlet.class);
С помощью этой строчки кода мы получаем объект класса Logger, с помощью которого можно посылать запросы на журналирование событий. Так, например, в строке с номером 112 мы вызываем DEBUG-метод этого logger'а: log.debug("Added line: " + data);. Это и есть то сообщение, которое мы недавно наблюдали на консоли. Заметьте, что, поскольку наш конфигурационный файл установил уровень для корневого элемента logger в DEBUG, мы не озадачиваем себя установкой какого-либо уровня для нашего logger'а с именем demo.log4j.servlet.SetupServlet, поскольку он наследует этот уровень от корневого элемента. Так, когда мы выполняем операцию log.debug("Added line: " + data); в пределах нашего класса, этот запрос на журналирование сообщения посылается appender'у (ConsoleAppender). Теперь попробуйте изменить уровень корневого элемента logger на ERROR. В этом случае строчки, которые ранее выводились, теперь не появляются, поскольку уровень logger'а стал выше, чем уровень самого запроса. Также заметьте, что для того, чтобы изменить уровень корневого элемента logger, нужно всего лишь модифицировать конфигурационный файл и перезапустить сервлет-контейнер (хотя это не обязательно, позже мы поговорим о том, как можно не перегружать сервер, используя ConfigureAndWatch).
На этом мы завершим рассмотрение нашего примера. Давайте еще раз вспомним, что нужно для того, чтобы начать пользоваться log4j в нашем простом примере:
1. Написать конфигурационный файл. В этом файле:
— Определить уровень корневого элемента logger и привязать его к appender'у.
— Определить свойства appender'а.
— Определить layout для этого appender'а.
2. В коде приложения возвратить экземпляр класса Logger, используя либо сам класс, либо его имя. Обычно это должен быть logger, присоединенный к текущему классу.
3. Начать журналирование с помощью методов: log.debug(), log.info(), log.warn(), log.error(), log.fatal().
Хоть мы и рассмотрели пример web-приложения, в котором для журналирования использовался log4j, и вы, надеюсь, увидели его мощь и функциональность, однако наше приложение слишком простое, чтобы предоставить хоть сколько-нибудь полноценный обзор возможностей log4j. Например:
• Журналирование на консоль не всегда является хорошим ходом, поскольку требует от пользователя постоянного наблюдения за происходящим на экране, чтобы отследить сообщения уровня ERROR и FATAL.
• SimpleLayout, который мы использовали, слишком прост и предоставляет минимум информации.
• В web-приложениях разные пользователи, обращаясь к различным сервлетам, будут генерировать всевозможные сообщения в регистрационный журнал, каждый свои. И это значительно затрудняет возможность идентифицировать, разделить и изолировать сообщения, поступающие от разных пользователей приложения.
Давайте посмотрим, как мы можем решить эти проблемы с помощью log4j. Не будем долго останавливаться на втором пункте, поскольку это довольно просто, заменить SimpleLayout на PatternLayout и определить специальный шаблон для формата вывода сообщений. Для этого достаточно обратиться к документации Javadoc по классу Pattern Layout, поставляемой вместе с дистрибутивом log4j, чтобы узнать обо всех существующих шаблонах и способах их задания.
Дополнительные возможности
JDBCAppender
Сейчас мы попробуем журналировать наши сообщения как-нибудь иначе, чем просто на консоль. Поскольку с недавних пор журналирование в базы данных стало заметно популярнее, давайте попробуем модифицировать наш пример для записи сообщений в базу данных. Для тестирования примера использовался сервер управления базами данных MySQL, но этот пример должен работать с любым другим сервером, поскольку мы целиком и полностью придерживались технологии JDBC. Главное, чтобы для этого сервера баз данных был подходящий JDBC-драйвер. Одно маленькое замечание по поводу использования JDBCAppender в текущей версии log4j: в документации сказано, что данный appender в будущих версиях будет полностью заменен целиком переработанной новой версией. Однако не стоит беспокоиться. Хотя внутренняя часть может быть кардинально изменена, но интерфейс работы с этим appender'ом измениться не должен.
Итак, давайте начнем с того, что откроем файл config-JDBC.properties из нашего приложения. Первое выражение, которое встречается в этом файле, не должно быть вам незнакомым. Так же, как и в случае с простым приложением, которое мы рассмотрели ранее, мы устанавливаем уровень корневого элемента logger в DEBUG и привязываем его к appender'у R. Следующие несколько строчек определяют R как объект класса JDBCAppender и объявляют его свойства:
log4j.appender.R=org.apache. log4j.jdbc.JDBCAppender
log4j.appender.R.URL=jdbc:my sql://localhost/LOG4JDemo
log4j.appender.R.user=default
log4j.appender.R.password= default
log4j.appender.R.sql=INSERT INTO JDBCTEST (Message) VALUES ('%d — %c — %p — %m')
Первая строка сообщает log4j о том, что R — это JDBCAppender. Вторая определяет URL к базе данных, в которую мы хотим записывать сообщения. Строки 3 и 4 задают имя пользователя и пароль для доступа к этой базе данных. Последняя строка — это SQL-запрос, который будет выполняться при записи сообщений. Об этом мы поговорим поподробнее чуть позже.
Разработчики, которым раньше доводилось работать с JDBC, сразу заметят схожесть между значениями, которые задаются в этом конфигурационном файле, и общими параметрами при соединении с базой данных в нормальном JDBC. Это те значения, которые вы должны обязательно задать в любом нормальном JDBC-приложении. Вы, безусловно, можете изменить эти значения так, как вам будет нужно. Например, если вы планируете соединяться с сервером баз данных Oracle, то в этом случае URL будет иметь вид, например, вот такой: jdbc:oracle:thin:@yourhostSID. Помимо этого, нельзя забывать о том, что эти драйверы (JDBC-драйверы к конкретным СУБД) должны быть доступны вашему приложению, чтобы log4j мог корректно выполнять возложенные на него функции.
Давайте более подробно рассмотрим последнее выражение этого конфигурационного файла. SQL-выражение, которое вы используете для журналирования сообщений в базу данных, может изменяться согласно вашим потребностям. То выражение, которое использовалось в примере конфигурационного файла, записывает в таблицу JDBCTEST следующие данные: дату (%d), logger (%c), приоритет (%p), само сообщение (%m). Все эти данные объединяются в строку в определенном формате и записываются в поле Message. Log4j берет это выражение и пропускает его через специальный фильтр, который, применяя layout специально для этого appender'а, заменяет формальные параметры (%d, %c, %p, %m и пр.) на действительные значения. После чего log4j просто выполняет отформатированный SQL-запрос.
Если необходимо, вы можете записывать разные значение в разные поля. Например, как в данном выражении:
INSERT INTO JDBCTEST (Date, Logger, Priority, Message) VALUES ('%d', '%c', '%p', '%m')
Это означает, что в нашей базе данных мы должны определить таблицу JDBCTEST, которая будет содержать поля Date, Logger, Priority и Message.
После всего этого вы еще раз должны были убедиться, что для того, чтобы перенаправить запись сообщений в базу данных, абсолютно не нужно изменять какой-либо код приложения. Мы просто меняем конфигурационные файлы или редактируем параметры старого файла для того, чтобы журналирование шло не на консоль, а в таблицу JDBCTEST нашей базы данных.
NDC/MDC
Nested Diagnostic Context (NDC) и Mapped Diagnostic Context (MDC) помогают в ситуациях, когда одно приложение вынуждено обслуживать одновременно несколько клиентов, и вы заинтересованы в том, чтобы разделить содержание журнала сообщений или хотя бы иметь возможность различать, при каком из пользователей было записано сообщение. Web-приложение — это великолепный пример для такой ситуации.
Так каким образом мы сможем различать наших пользователей? Очень просто: мы будем записывать специфичную для каждого из клиентов информацию. Например, в случае с web-приложением можно включить, помимо прочих вещей, IP-адрес, который всегда можно получить из сервлета. В NDC вы кладете эту информацию в стек при входе в контекст и удаляете (получаете) ее из стека при выходе из этого же контекста. Log4j заменяет шаблон %x на специфичную для контекста информацию при записи в свой appender. Этот шаблон нужно использовать в связанном с appender'ом layout'е. Чтобы посмотреть, как это работает, давайте обратимся к конфигурационному файлу config-NDC.properties и файлу GetCommentsServlet.java.
В конфигурационном файле config-NDC вы можете видеть заметные различия по сравнению с предыдущими. Во-первых, в нем определено несколько logger'ов. Первые несколько строк схожи со строками файла config-simple. Для всех остальных logger'ов в нашем приложении, кроме того, который связан с классом GetCommentsServlet, мы хотим, чтобы вывод был связан с консолью и представлен простым layout'ом (SimpleLayout). Для logger'а с именем demo.log4j.servlet.GetCommentsServlet нам нужно иметь свой appender (R1), который также будет выводить информацию на экран, но, помимо этого, его шаблон будет содержать символ %x.
log4j.logger.demo.log4j. servlet.GetCommentsServlet=debug, R1
...
log4j.appender.R1.layout.ConversionPattern=%p — [%x] — %m%n
Обратите внимание, как мы ссылаемся на logger GetCommentsServlet. На все logger'ы (кроме rootLogger, который обычно именуется как log4j. rootLogger) можно ссылаться при помощи log4j.logger.<имя logger'а> . Такого типа именование подходит для большинства элементов log4j.
Теперь обратимся к исходному коду класса GetCommentsServlet. Именно в этом классе мы может посмотреть, как реализована работа с добавлением и удалением уникальной информации о клиенте из стека NDC.
А сейчас посмотрите на строки 40 и 57. В строке 40 мы заносим уникальную информацию о клиенте в стек. Теперь любое выражение, которое выполняет журналирование сообщения, будет содержать эту информацию, вставляя ее вместо шаблона %x. Строкой 57 мы удаляем эту информацию из стека. Таким образом, мы можем записывать сообщения, по которым не составит труда узнать, какой именно клиент повлек за собой запись данной информации в журнал.
Теперь давайте поговорим немного о Mapped Diagnostic Context (MDC). MDC очень многим похож на NDC, но с той лишь разницей, что вместо записи и удаления информации о клиенте из стека она сохраняется в структуре Map (java.util.Map). Это подразумевает, что каждый кусок специфичной для клиента информации должен сопровождаться каким-либо уникальным ключом. Если вы посмотрите на строку 43 в файле GetCommentsServlet.java, вы увидите, как это реализовано.
MDC.put("RemoteHost", remHost);
Класс MDC предоставляет статический метод для манипулирования специфичной для каждого из клиентов информацией. Таким образом, с каждым выражением для записи в журнал какого-либо сообщения эта информация, соответствующая Re-moteHost, будет заменять шаблон MDC, который имеет вид %X{key}. Что такое key? В нашем случае key мы ассоциируем со значением remHost, которое мы добавляем в Map под именем RemoteHost. Если, например, мы хотим добавить RemoteAddress к нашему выводу, можно написать следующее:
В исходном коде:
MDC.put("RemoteAddress", req. getRemoteAddr());
А в конфигурационном файле добавить следующее:
%X{RemoteAddress}
Пример такого конфигурационного файла вы можете найти в файле config-MDC.properties. Хоть он и схож с файлом config-NDC.properties, но имеет два очень важных отличия. Во-первых, мы используем MDC вместо NDC, а во-вторых, appender для нашего второго logger'а — это Rolling FileAppender вместо ConsoleAppender.
Несколько советов напоследок
Первый совет, который окажет вам неоценимую помощь и даст не одну тысячу советов, особенно если вы собираетесь стать постоянным пользователем log4j, — подпишитесь на лист рассылки, посвященный log4j. Архив всех сообщений вы можете найти на сайте http://www.mail-archive.com/log4j-user@jakarta.apache.org . Подписаться же на сам лист рассылки можно, послав письмо по адресу log4j-user-subscribe@jakarta.apache.org .
ConfigureAndWatch: Вы уже должны знать, что после того, как вы внесли изменения в конфигурационный файл, чтобы эти изменения вступили в силу, необходимо перезапустить сервлет-контейнер. Но это иногда бывает очень обременительно и неудобно. Lo4j предоставляет механизм, с помощью которого он может постоянно отслеживать изменения в конфигурационном файле из вашего приложения. Для того, чтобы воспользоваться этой возможностью, замените выражение PropertyConfigura-tor.configure(props); на PropertyCon-figurator.configureAndWatch(props);, которое использует значение по умолчанию (60 секунд) в качестве промежутка времени, по истечении которого нужно проверить, не изменился ли конфигурационный файл. Естественно, при надобности вы без проблем можете изменить это значение.
Заключение
Log4j — это популярный инструмент для журналирования проекта Apache Project. Помимо него, существуют еще и другие инструменты для журналирования включая специальный API, встроенный в JDK 1.4. Однако log4j является признанным лидером среди всех существующих инструментов, поскольку позволяет поддерживать беспрецедентный контроль над всеми аспектами журналирования и, помимо этого, является иерархическим. Он предоставляет контроль во время выполнения приложения над процессом журналирования без необходимости вносить какие-либо изменения в исходный код приложения.
По материалам Vikram Goyal
Подготовил Алексей Литвинюк,
litvinuke@tut.by
Log4j — это инструмент для журналирования с открытым исходным кодом, разработанный под эгидой глобального проекта Jakarta Apache. Он представляет собой набор API, с помощью которых разработчики могут вставлять в свой код выражения, выводящие некоторую информацию (отладочную, информационную, сообщения об ошибках и т.д.), и конфигурировать этот вывод с помощью внешних конфигурационных файлов. В этой статье рассматриваются основные идеи, заложенные в данный инструмент, а также затрагиваются некоторые интересные моменты, касающиеся написания демонстрационного web-приложения.
Программа, использованная в этой статье в качестве примера, представляет собой web-приложение, которое было разработано и протестировано с помощью JDK 1.3.1, Tomcat 4.0.1 ( http://jakarta.apache.org/tomcat ), log4j 1.2.4 ( http://jakarta.apache.org/log4j ) и Ant 1.4.1 ( http://jakarta.apache.org/ant ). Вся работа выполнялась на компьютере с установленной операционной системой Windows XP. Вы можете загрузить файл приложения log4jdemo. war ( http://java.linux.by/bible-arc/log4jdemo.war ), которое можно легко развернуть в любом сервлет-контейнере. Вы можете также загрузить исходный код этого приложения ( http://java.linux.by/bible-arc/log4jsrc.zip ).
Наше приложение содержит следующие ветки, очень часто встречающиеся во многих web-приложениях:
1. Основная страница -> Авторизированный вход -> Страничка приветствия -> Отправление комментария.
2. Основная страница -> Регистрация -> Авторизированный вход.
3. Основная страница -> Страничка для тех, кто забыл пароль.
Очень важно, чтобы вы понимали, что данное приложение является очень тривиальным. Единственная его цель в этой статье — помочь вам научиться работать с log4j. Вы вправе спросить, почему было выбрано именно web-приложение, а не обычное Java-приложение. Все просто. На сегодняшний день очень многие web-приложения используют предпочтительно механизм журналирования именно на основе log4j. В свою очередь, среди обычных Java-приложений процент несколько ниже, но популярность log4j стремительно растет, что неудивительно.
Настройка
Загрузить log4j можно с сайта Apache: http://jakarta.apache.org/log4j/ docs/download.html. На сайте вам будет предложено выбрать один из двух типов форматов архива с log4j — это tar.gz (http://jakarta.apache.org/ log4j/jakarta-log4j-1.2.4.tar.gz) и zip (http://jakarta.apache.org/log4j/jakarta-log4j-1.2.4.zip). Каждый из этих архивов содержит в себе исходный код log4j, документацию, примеры и непосредственно дистрибутивные .jar-архивы log4j. Распакуйте загруженный вами архив в какую-нибудь директорию по вашему выбору. Исходный код поставляется для того, если вы вдруг захотите скомпилировать и собрать log4j самостоятельно. Эту операцию вы можете провести с помощью Ant и предоставляемых вместе с исходниками файлов build.xml. Если потребуется, вы можете легко изменить эти скрипты (build.xml). После компиляции и сборки вы получите файл log4j-1.2.4.jar, который будет находиться в директории dist/lib. Учтите, что для успешной сборки вам понадобится Java Management Extensions (JMX) API, который должен быть прописан в переменной окружения CLASSPATH.
Чтобы использовать классы log4j в своем приложении, вам необходимо добавить в CLASSPATH вашего приложения файл log4j-1.2.4.jar. Этот файл, как уже было упомянуто ранее, находится в директории dist/lib. В случае с нашим примером web-приложения необходимо, чтобы этот файл располагался внутри .jar-архива этого приложения, в директории WEB-INF/lib. Это все, что необходимо, чтобы наше приложение могло работать с log4j API. Остальные усилия нужно приложить лишь для написания конфигурационного файла, который будет определять для log4j, что, куда и как нужно журналировать, а также для вставки выражений в код самого приложения, которые непосредственно будут журналировать события.
Что такое Logger?
Прежде, чем приступить к рассмотрению кусков кода нашего web-приложения, давайте разберем основные понятия в log4j. Log4j имеет три базовые составляющие: logger (журналирующий элемент), appender (элемент, определяющий, куда нужно журналировать) и layout (представление, в котором будет производиться журналирование). Для удобства мы так и будем их называть на протяжении всей статьи. Logger берет на себя функцию журналирования какого-либо события в место назначения, определяемое элементом appender, в специально определенном формате, который задается элементом layout.
Logger можно представлять себе как компонент некоего приложения, который будет принимать и выполнять ваши запросы на запись каких-либо событий в регистрационный журнал, представленный файлом, базой данных, консолью и т.д. (определяется appender'ом). Каждый класс вашего приложения может иметь свой собственный logger или же быть привязанным к общему logger'у. Log4j предусматривает корневой logger, от которого будут наследоваться все создаваемые вами объекты этого типа. Это значит, что, если у вас не предусмотрен свой logger для каждого объекта, вы всегда сможете воспользоваться корневым, с помощью метода Logger.getRootLogger(), однако так поступать не рекомендуется.
Для того, чтобы создать logger и использовать его для журналирования каких-либо событий, возникающих в этом классе, вам в самом простом случае необходимо вызвать статический метод класса Logger, который получит за вас этот объект по указанному имени. Если такой объект на данный момент еще не существует, он будет создан. Но нужно учесть, что в любой момент времени может существовать только один экземпляр объекта logger'а с одним и тем же именем.
Logger'ы обязательно должны знать, куда им нужно слать ваши запросы на запись событий. Эта функция ложится непосредственно на плечи элементов appender. Log4j поддерживает запись в файлы (FileAppen-der), на консоль (ConsoleAppender), в базы данных (JDBCAppender), в журнал событий операционных систем семейства Windows NT/2000/XP (NTEventLogAppender), на SMTP-серверы (SMTPAppender), на удаленные серверы (SocketAppender) и не только. Таким образом, понятно, что appender определяет место, куда нужно журналировать события. Так, например, если мы привяжем JDBCAppen-der к одному из наших logger'ов, это будет означать для log4j, что все запросы на журналирование событий, поступающих от этого logger'а, будут записываться в определенную базу данных с заданными параметрами (URL, имя пользователя, пароль и т.д.). Эти параметры являются свойствами объектов элементов типа appender (в данном случае JDBCAppender).
Итак, logger'ы и appender'ы занимаются созданием и направлением наших запросов на журналирование. А что насчет формата вывода? Тут в игру вступают элементы layout. Эти элементы определяют стиль и содержание выводимых в журнал записей. Log4j предоставляет несколько предопределенных layout'ов. Однако вы можете без проблем создать свой собственный, если возникнет такая необходимость. Элемент layout может определять, включать или не включать в выводимые записи дату и время, включать ли информацию об используемом для вывода объекте logger, включать или нет информацию о номере текущей строки, в которой был создан этот запрос на журналирование, и т.д.
Все элементы logger следуют шаблону отношений типа родитель — потомок. Как упоминалось ранее, log4j предусматривает по умолчанию корневой элемент logger. Отношение типа родитель — потомок также соответствует шаблону именования классов. Это подразумевает, что абсолютно все наши элементы logger будут наследовать корневой элемент. Отношения типа родитель — потомок хорошо просматриваются в шаблоне именования классов. Скажем, у вас есть класс MyClass, который находится в пакете с именем com.foo.bar. Теперь создадим экземпляр элемента logger с именем com.foo.bar.MyClass. В этом случае logger с именем com.foo.bar.MyClass будет потомком logger'а com.foo.bar, если такой существует. Если же он не существует, то родителем logger'а com.foo.bar.MyClass будет считаться корневой элемент logger (это, конечно, в случае, если не существует также logger'ов с именами com.foo и com).
Каждому элементу logger в log4j привязывается свой уровень. Если такой уровень не будет привязан к какому-нибудь конкретному элементу, то этот элемент будет автоматически приобретать уровень своего родителя. Даже в случае, если пользователь совсем не будет присваивать никаких уровней создаваемым им элементам logger, они будут иметь уровень корневого элемента, который по умолчанию равен DEBUG. Таким образом, каждый элемент logger будет гарантированно иметь определенный уровень.
Log4j предусматривает пять уровней журналирования:
• DEBUG
• INFO
• WARN
• ERROR
• FATAL
Эти уровни определяют также порядок журналирования, которому следует log4j. Об этом ниже.
Каким образом установленный для элемента logger уровень влияет на его функциональность? Все просто. Дело в том, что запрос на занесение в журнал какого-либо события с помощью определенного элемента appender из нашего приложения будет выполнен лишь в том случае, если уровень журналируемого события (запроса) не ниже, чем уровень самого элемента logger, через который этот запрос был создан. Это означает, что все запросы, которые мы можем создавать в нашем приложении, могут быть только какого-то одного из этих пяти уровней. Это очень важное правило, и фактически это является ядром функциональности log4j. Давайте вернемся к нашему предыдущему примеру com.foo.bar.MyClass и попробуем более подробно рассмотреть все, о чем сейчас говорилось.
Предположим, в пределах класса com.foo.bar.MyClass (мы назвали класс и logger для этого класса одним и тем же именем — это обычная практика для программистов, использующих log4j), используя его logger, вы пытаетесь журналировать некоторое сообщение типа WARN. Теперь, скажем, ваш logger com.foo.bar.MyClass имеет уровень ERROR, который был установлен вами при конфигурации. Что это означает? Это означает, что ваш запрос на журналирование типа WARN выполнен не будет.
Почему? Потому, что log4j определяет, что уровень запроса на журналирование ниже, чем уровень самого logger'а, через который этот запрос был сделан. Если бы этот запрос имел уровень ERROR или FATAL, то он без проблем был бы выполнен, и сообщение заняло бы свое место в регистрационном журнале согласно используемому элементу appender. Аналогично, если бы уровень logger'а был WARN, то этот запрос также был бы выполнен. Итак, мы пришли к очень важной идее, заложенной в log4j. Вы можете извне менять уровень каждого из ваших элементов logger без необходимости вносить какие-либо изменения в исходный код приложения, без необходимости перекомпилировать приложение и разворачивать его заново.
Основное конфигурирование log4j происходит посредством внешнего конфигурационного файла. API также предоставляет возможность конфигурировать систему log4j и из приложения. Далее мы рассмотрим простой пример приложения и посмотрим, как правильно создавать конфигурационный файл.
Пример использования log4j
Приложение, которое мы собрались рассматривать в качестве примера, содержит пять сервлетов (SetupServlet (стартует при запуске сервлет-контейнера), LogonServlet, GetCommentsServ-let, ForgotPasswordServlet, Register Servlet), четыре JSP-страницы (forgotpassword, logon, register, welcome), а также базу данных в виде файла, в которой хранится информация о пользователях (файл userDB, который должен располагаться в директории WEB-INF; убедитесь, что путь к нему корректно прописан в конфигурационном файле web.xml).
Чтобы увидеть это приложение в действии, разверните файл log4j demo.war в вашем сервлет-контейнере. Как только вы это сделали, измените файл web.xml таким образом, чтобы параметр init сервлета SetupServlet для файла props содержал путь к файлу config-simple.properties. Этот файл находится в директории WEB-INF нашего приложения (там также содержатся три других конфигурационных файла: config-JDBC, config-MDC, config-NDC). Теперь перезапустите ваш сервер (в данном случае имеется в виду сервлет-контейнер). На консоли вашего сервера появятся некоторые сообщения. Если SetupServlet смог найти файл userDB, то вы, возможно, увидите следующие сообщения:
Using properties file:
C:\jakarta-tomcat-4.0.1\ webapps\Log4JDemo\WEB-INF\config-simple.properties
Watch is set to: null
DEBUG — Added line: JohnDoe, herbert,johndoe@john.com,John Doe
Первые два выражения, появившиеся на вашем экране, представляют собой простой вывод с помощью выражений System.out.println. Следующие же два сообщения были выведены с помощью log4j. Давайте посмотрим, как это было сделано в программе.
Откройте файл config-simple.properties в вашем любимом текстовом редакторе. Этот файл содержит всего три строчки (не считая комментариев). Первая строка (log4j.rootLogger= debug, R)сообщает log4j, что уровень корневого элемента logger будет DEBUG. Такой уровень для корневого элемента устанавливается по умолчанию, и поэтому эта строчка не обязательна. Однако значение после запятой (R) является обязательным. Оно сообщает log4j, что корневой элемент logger должен быть привязан к элементу appender с именем "R". Оставшиеся строки в этом файле определяют свойства этого appender'а R.
Строка log4j.appender.R=org.apa-che.log4j.ConsoleAppender говорит о том, что appender R имеет тип ConsoleAppender; т.е. это означает, что вывод будет производиться на консоль. Строка log4j.appender.R.layout=org.apache.log4j.SimpleLayout сообщает log4j, что для appender'а R будет использоваться простой элемент layout, который, как вы могли заметить, просто выводит две вещи: имя уровня запроса на журналирование и непосредственно само сообщение.
Теперь, когда мы разобрались с простым примером конфигурационного файла, самое время посмотреть, как происходит журналирование в самом коде приложения. Для начала откройте файл SetupServlet.java в текстовом редакторе и найдите 81 строку:
PropertyConfigurator.configure(props);
Пока не обращайте внимания на выражения, находящиеся рядом с этой строкой. Эта строчка сообщает log4j, что требуется найти файл, определенный переменной props, и использовать его для установки настроек log4j. Это и есть тот самый файл, который мы только что с вами разобрали, config-simple.properties. Эта строка должна выполняться только один раз за всю работу цельного приложения. Обычно она вызывается при старте приложения, а сервлет, вызываемый при старте сервлет-контейнера — это идеальное место для выполнения этого конфигурирования в web-приложении.
Как только мы выполнили все операции по конфигурированию log4j, можно приступать непосредственно к журналированию. Следующим шагом необходимо получить или создать нужный logger, и уже потом с его помощью вставлять в код программы выражения для вывода сообщений в регистрационный журнал. Это можно сделать либо с помощью корневого элемента logger, вызвав метод Logger.getRootLogger(), либо же попытаться взять logger класса, в котором мы сейчас находимся (т.е. из которого хотим журналировать какое-либо событие) — в нашем случае это SetupServlet. В случае второго варианта мы получим logger с именем demo.log4j.servlet.SetupServlet, выполнив следующую строчку кода:
Logger log = Logger.getLogger (SetupServlet.class);
С помощью этой строчки кода мы получаем объект класса Logger, с помощью которого можно посылать запросы на журналирование событий. Так, например, в строке с номером 112 мы вызываем DEBUG-метод этого logger'а: log.debug("Added line: " + data);. Это и есть то сообщение, которое мы недавно наблюдали на консоли. Заметьте, что, поскольку наш конфигурационный файл установил уровень для корневого элемента logger в DEBUG, мы не озадачиваем себя установкой какого-либо уровня для нашего logger'а с именем demo.log4j.servlet.SetupServlet, поскольку он наследует этот уровень от корневого элемента. Так, когда мы выполняем операцию log.debug("Added line: " + data); в пределах нашего класса, этот запрос на журналирование сообщения посылается appender'у (ConsoleAppender). Теперь попробуйте изменить уровень корневого элемента logger на ERROR. В этом случае строчки, которые ранее выводились, теперь не появляются, поскольку уровень logger'а стал выше, чем уровень самого запроса. Также заметьте, что для того, чтобы изменить уровень корневого элемента logger, нужно всего лишь модифицировать конфигурационный файл и перезапустить сервлет-контейнер (хотя это не обязательно, позже мы поговорим о том, как можно не перегружать сервер, используя ConfigureAndWatch).
На этом мы завершим рассмотрение нашего примера. Давайте еще раз вспомним, что нужно для того, чтобы начать пользоваться log4j в нашем простом примере:
1. Написать конфигурационный файл. В этом файле:
— Определить уровень корневого элемента logger и привязать его к appender'у.
— Определить свойства appender'а.
— Определить layout для этого appender'а.
2. В коде приложения возвратить экземпляр класса Logger, используя либо сам класс, либо его имя. Обычно это должен быть logger, присоединенный к текущему классу.
3. Начать журналирование с помощью методов: log.debug(), log.info(), log.warn(), log.error(), log.fatal().
Хоть мы и рассмотрели пример web-приложения, в котором для журналирования использовался log4j, и вы, надеюсь, увидели его мощь и функциональность, однако наше приложение слишком простое, чтобы предоставить хоть сколько-нибудь полноценный обзор возможностей log4j. Например:
• Журналирование на консоль не всегда является хорошим ходом, поскольку требует от пользователя постоянного наблюдения за происходящим на экране, чтобы отследить сообщения уровня ERROR и FATAL.
• SimpleLayout, который мы использовали, слишком прост и предоставляет минимум информации.
• В web-приложениях разные пользователи, обращаясь к различным сервлетам, будут генерировать всевозможные сообщения в регистрационный журнал, каждый свои. И это значительно затрудняет возможность идентифицировать, разделить и изолировать сообщения, поступающие от разных пользователей приложения.
Давайте посмотрим, как мы можем решить эти проблемы с помощью log4j. Не будем долго останавливаться на втором пункте, поскольку это довольно просто, заменить SimpleLayout на PatternLayout и определить специальный шаблон для формата вывода сообщений. Для этого достаточно обратиться к документации Javadoc по классу Pattern Layout, поставляемой вместе с дистрибутивом log4j, чтобы узнать обо всех существующих шаблонах и способах их задания.
Дополнительные возможности
JDBCAppender
Сейчас мы попробуем журналировать наши сообщения как-нибудь иначе, чем просто на консоль. Поскольку с недавних пор журналирование в базы данных стало заметно популярнее, давайте попробуем модифицировать наш пример для записи сообщений в базу данных. Для тестирования примера использовался сервер управления базами данных MySQL, но этот пример должен работать с любым другим сервером, поскольку мы целиком и полностью придерживались технологии JDBC. Главное, чтобы для этого сервера баз данных был подходящий JDBC-драйвер. Одно маленькое замечание по поводу использования JDBCAppender в текущей версии log4j: в документации сказано, что данный appender в будущих версиях будет полностью заменен целиком переработанной новой версией. Однако не стоит беспокоиться. Хотя внутренняя часть может быть кардинально изменена, но интерфейс работы с этим appender'ом измениться не должен.
Итак, давайте начнем с того, что откроем файл config-JDBC.properties из нашего приложения. Первое выражение, которое встречается в этом файле, не должно быть вам незнакомым. Так же, как и в случае с простым приложением, которое мы рассмотрели ранее, мы устанавливаем уровень корневого элемента logger в DEBUG и привязываем его к appender'у R. Следующие несколько строчек определяют R как объект класса JDBCAppender и объявляют его свойства:
log4j.appender.R=org.apache. log4j.jdbc.JDBCAppender
log4j.appender.R.URL=jdbc:my sql://localhost/LOG4JDemo
log4j.appender.R.user=default
log4j.appender.R.password= default
log4j.appender.R.sql=INSERT INTO JDBCTEST (Message) VALUES ('%d — %c — %p — %m')
Первая строка сообщает log4j о том, что R — это JDBCAppender. Вторая определяет URL к базе данных, в которую мы хотим записывать сообщения. Строки 3 и 4 задают имя пользователя и пароль для доступа к этой базе данных. Последняя строка — это SQL-запрос, который будет выполняться при записи сообщений. Об этом мы поговорим поподробнее чуть позже.
Разработчики, которым раньше доводилось работать с JDBC, сразу заметят схожесть между значениями, которые задаются в этом конфигурационном файле, и общими параметрами при соединении с базой данных в нормальном JDBC. Это те значения, которые вы должны обязательно задать в любом нормальном JDBC-приложении. Вы, безусловно, можете изменить эти значения так, как вам будет нужно. Например, если вы планируете соединяться с сервером баз данных Oracle, то в этом случае URL будет иметь вид, например, вот такой: jdbc:oracle:thin:@yourhostSID. Помимо этого, нельзя забывать о том, что эти драйверы (JDBC-драйверы к конкретным СУБД) должны быть доступны вашему приложению, чтобы log4j мог корректно выполнять возложенные на него функции.
Давайте более подробно рассмотрим последнее выражение этого конфигурационного файла. SQL-выражение, которое вы используете для журналирования сообщений в базу данных, может изменяться согласно вашим потребностям. То выражение, которое использовалось в примере конфигурационного файла, записывает в таблицу JDBCTEST следующие данные: дату (%d), logger (%c), приоритет (%p), само сообщение (%m). Все эти данные объединяются в строку в определенном формате и записываются в поле Message. Log4j берет это выражение и пропускает его через специальный фильтр, который, применяя layout специально для этого appender'а, заменяет формальные параметры (%d, %c, %p, %m и пр.) на действительные значения. После чего log4j просто выполняет отформатированный SQL-запрос.
Если необходимо, вы можете записывать разные значение в разные поля. Например, как в данном выражении:
INSERT INTO JDBCTEST (Date, Logger, Priority, Message) VALUES ('%d', '%c', '%p', '%m')
Это означает, что в нашей базе данных мы должны определить таблицу JDBCTEST, которая будет содержать поля Date, Logger, Priority и Message.
После всего этого вы еще раз должны были убедиться, что для того, чтобы перенаправить запись сообщений в базу данных, абсолютно не нужно изменять какой-либо код приложения. Мы просто меняем конфигурационные файлы или редактируем параметры старого файла для того, чтобы журналирование шло не на консоль, а в таблицу JDBCTEST нашей базы данных.
NDC/MDC
Nested Diagnostic Context (NDC) и Mapped Diagnostic Context (MDC) помогают в ситуациях, когда одно приложение вынуждено обслуживать одновременно несколько клиентов, и вы заинтересованы в том, чтобы разделить содержание журнала сообщений или хотя бы иметь возможность различать, при каком из пользователей было записано сообщение. Web-приложение — это великолепный пример для такой ситуации.
Так каким образом мы сможем различать наших пользователей? Очень просто: мы будем записывать специфичную для каждого из клиентов информацию. Например, в случае с web-приложением можно включить, помимо прочих вещей, IP-адрес, который всегда можно получить из сервлета. В NDC вы кладете эту информацию в стек при входе в контекст и удаляете (получаете) ее из стека при выходе из этого же контекста. Log4j заменяет шаблон %x на специфичную для контекста информацию при записи в свой appender. Этот шаблон нужно использовать в связанном с appender'ом layout'е. Чтобы посмотреть, как это работает, давайте обратимся к конфигурационному файлу config-NDC.properties и файлу GetCommentsServlet.java.
В конфигурационном файле config-NDC вы можете видеть заметные различия по сравнению с предыдущими. Во-первых, в нем определено несколько logger'ов. Первые несколько строк схожи со строками файла config-simple. Для всех остальных logger'ов в нашем приложении, кроме того, который связан с классом GetCommentsServlet, мы хотим, чтобы вывод был связан с консолью и представлен простым layout'ом (SimpleLayout). Для logger'а с именем demo.log4j.servlet.GetCommentsServlet нам нужно иметь свой appender (R1), который также будет выводить информацию на экран, но, помимо этого, его шаблон будет содержать символ %x.
log4j.logger.demo.log4j. servlet.GetCommentsServlet=debug, R1
...
log4j.appender.R1.layout.ConversionPattern=%p — [%x] — %m%n
Обратите внимание, как мы ссылаемся на logger GetCommentsServlet. На все logger'ы (кроме rootLogger, который обычно именуется как log4j. rootLogger) можно ссылаться при помощи log4j.logger.<имя logger'а> . Такого типа именование подходит для большинства элементов log4j.
Теперь обратимся к исходному коду класса GetCommentsServlet. Именно в этом классе мы может посмотреть, как реализована работа с добавлением и удалением уникальной информации о клиенте из стека NDC.
А сейчас посмотрите на строки 40 и 57. В строке 40 мы заносим уникальную информацию о клиенте в стек. Теперь любое выражение, которое выполняет журналирование сообщения, будет содержать эту информацию, вставляя ее вместо шаблона %x. Строкой 57 мы удаляем эту информацию из стека. Таким образом, мы можем записывать сообщения, по которым не составит труда узнать, какой именно клиент повлек за собой запись данной информации в журнал.
Теперь давайте поговорим немного о Mapped Diagnostic Context (MDC). MDC очень многим похож на NDC, но с той лишь разницей, что вместо записи и удаления информации о клиенте из стека она сохраняется в структуре Map (java.util.Map). Это подразумевает, что каждый кусок специфичной для клиента информации должен сопровождаться каким-либо уникальным ключом. Если вы посмотрите на строку 43 в файле GetCommentsServlet.java, вы увидите, как это реализовано.
MDC.put("RemoteHost", remHost);
Класс MDC предоставляет статический метод для манипулирования специфичной для каждого из клиентов информацией. Таким образом, с каждым выражением для записи в журнал какого-либо сообщения эта информация, соответствующая Re-moteHost, будет заменять шаблон MDC, который имеет вид %X{key}. Что такое key? В нашем случае key мы ассоциируем со значением remHost, которое мы добавляем в Map под именем RemoteHost. Если, например, мы хотим добавить RemoteAddress к нашему выводу, можно написать следующее:
В исходном коде:
MDC.put("RemoteAddress", req. getRemoteAddr());
А в конфигурационном файле добавить следующее:
%X{RemoteAddress}
Пример такого конфигурационного файла вы можете найти в файле config-MDC.properties. Хоть он и схож с файлом config-NDC.properties, но имеет два очень важных отличия. Во-первых, мы используем MDC вместо NDC, а во-вторых, appender для нашего второго logger'а — это Rolling FileAppender вместо ConsoleAppender.
Несколько советов напоследок
Первый совет, который окажет вам неоценимую помощь и даст не одну тысячу советов, особенно если вы собираетесь стать постоянным пользователем log4j, — подпишитесь на лист рассылки, посвященный log4j. Архив всех сообщений вы можете найти на сайте http://www.mail-archive.com/log4j-user@jakarta.apache.org . Подписаться же на сам лист рассылки можно, послав письмо по адресу log4j-user-subscribe@jakarta.apache.org .
ConfigureAndWatch: Вы уже должны знать, что после того, как вы внесли изменения в конфигурационный файл, чтобы эти изменения вступили в силу, необходимо перезапустить сервлет-контейнер. Но это иногда бывает очень обременительно и неудобно. Lo4j предоставляет механизм, с помощью которого он может постоянно отслеживать изменения в конфигурационном файле из вашего приложения. Для того, чтобы воспользоваться этой возможностью, замените выражение PropertyConfigura-tor.configure(props); на PropertyCon-figurator.configureAndWatch(props);, которое использует значение по умолчанию (60 секунд) в качестве промежутка времени, по истечении которого нужно проверить, не изменился ли конфигурационный файл. Естественно, при надобности вы без проблем можете изменить это значение.
Заключение
Log4j — это популярный инструмент для журналирования проекта Apache Project. Помимо него, существуют еще и другие инструменты для журналирования включая специальный API, встроенный в JDK 1.4. Однако log4j является признанным лидером среди всех существующих инструментов, поскольку позволяет поддерживать беспрецедентный контроль над всеми аспектами журналирования и, помимо этого, является иерархическим. Он предоставляет контроль во время выполнения приложения над процессом журналирования без необходимости вносить какие-либо изменения в исходный код приложения.
По материалам Vikram Goyal
Подготовил Алексей Литвинюк,
litvinuke@tut.by
Компьютерная газета. Статья была опубликована в номере 36 за 2003 год в рубрике программирование :: разное