Многие Java разработчики используют в своих приложениях библиотеку log4j для логирования. Иногда возникает необходимость писать логи в базу данных. В этой статье я расскажу как это сделать, используя стандартные возможности log4j.
Сперва хотелось бы рассказать об обычном подходе разработчика, который получает задачу логировать некоторую информацию в базу. Мне бы хотелось подчеркнуть, что эта информация никак не связана с логикой работы приложения, это именно логи.
Итак, разработчик создает некий класс, к примеру,
Рассмотрим недостатки, свойственные описанному подходу.
Рассмотрим подход, лишенный всех этих недостатков. Для меня вполне логично, что если стоит задача логирования чего-либо, то нужно использовать систему log4j. Возможно, что для некоторых разработчиков кажется проще написать свой метод логирования, чем изучить и расширить log4j, особенно такие мысли могут возникнуть, если время на разработку сильно ограничено. Надеюсь, что я смогу переубедить таких разработчиков.
В самом простом случае можно воспользоваться стандартным классом log4j, предназначенным для сохранения записей в базу — JDBCAppender.
В вашем коде необходимо использовать log4j обычным образом. Дополнительно нужно будет сконфигурировать JDBCAppender в log4j.properties файле:
При таком подходе используется стандартный
Преимущества данного подхода очевидны — логирование отделено от логики приложения, используется SQL запрос из конфигурационного файла. Но существует один существенный недостаток. Если вам нужно логировать составной объект, каждое поле которого записывается в отдельную колонку базы данных, то
Возникает следующая задача: необходимо логировать объект класса
Класс, реализующий бизнес-логику должен создать и заполнить поля объекта
Далее вам необходимо создать свой
Вам осталось реализовать метод сохранения вашего объекта в базу данных —
Дополнительно нужно сказать пару слов по поводу времени жизни JDBC Connection: переменная connection инициализируется при первом обращении к методу
Библиотека log4j предоставляет гибкую систему для ведения логов. Более того из этой статьи можно увидеть, как легко можно расширить базовую функциональность log4j. Заменив класс
Как не следует делать
Сперва хотелось бы рассказать об обычном подходе разработчика, который получает задачу логировать некоторую информацию в базу. Мне бы хотелось подчеркнуть, что эта информация никак не связана с логикой работы приложения, это именно логи.
Итак, разработчик создает некий класс, к примеру,
DBUtils, в котором определяет метод saveLog(:). В нужном месте кода создается объект DBUtils и с помощью метода saveLog ведется логирование. Понятно, что конфигурация для доступа к базе данных считывается из основного конфигурационного файла приложения (если он существует).Недостатки подхода
Рассмотрим недостатки, свойственные описанному подходу.
- В
DBUtils, скорее всего, инкапсулирована логика приложения для работы с базой, а так же логика, не связанная с бизнес-спецификой приложения — методsaveLog(:) - Если логирование в базу данных не всегда нужно, то потребуется писать код, который по дополнительному флагу из конфигурационного файла будет включать/выключать логирование.
- Разработчик, который будет поддерживать такой код, должен заглянуть в метод
saveLog(), чтобы понять влияет ли он на ход работы приложения. А если это не один метод, и они называются не так однозначно, то поддержка кода становится все более затруднительной (собственно, это тот же пункт первый, только не с архитектурной точки зрения, а с точки зрения разработчика).
Log4j — для логирования
Рассмотрим подход, лишенный всех этих недостатков. Для меня вполне логично, что если стоит задача логирования чего-либо, то нужно использовать систему log4j. Возможно, что для некоторых разработчиков кажется проще написать свой метод логирования, чем изучить и расширить log4j, особенно такие мысли могут возникнуть, если время на разработку сильно ограничено. Надеюсь, что я смогу переубедить таких разработчиков.
Используем JDBCAppender
В самом простом случае можно воспользоваться стандартным классом log4j, предназначенным для сохранения записей в базу — JDBCAppender.
В вашем коде необходимо использовать log4j обычным образом. Дополнительно нужно будет сконфигурировать JDBCAppender в log4j.properties файле:
log4j.logger.com.yourcompany.examples=DEBUG, jdbc
log4j.appender.jdbc=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.jdbc.sql=insert into logTable (message, class, priority, log_date) values ('%m', '%c', '%p', '%d')
log4j.appender.jdbc.URL=jdbc:odbc:myDB
log4j.appender.jdbc.user=me
log4j.appender.jdbc.password=password
log4j.appender.JDBC.buffersize=1
При таком подходе используется стандартный
PatternLayout. Вы можете использовать любые символы подстановки (conversion characters), свойственные PatternLayout.Преимущества данного подхода очевидны — логирование отделено от логики приложения, используется SQL запрос из конфигурационного файла. Но существует один существенный недостаток. Если вам нужно логировать составной объект, каждое поле которого записывается в отдельную колонку базы данных, то
JDBCAppender не сможет справиться с этой задачей. Вам придется написать свой Appender.Расширяем JDBCAppender
Возникает следующая задача: необходимо логировать объект класса
LogObj, каждое поле которого должно быть записано в отдельную колонку базы. Класс, реализующий бизнес-логику должен создать и заполнить поля объекта
LogObj. В нужный момент logObj логируется с помощью стандартного вызова, к примеру: dbLog.info(logObj);
dbLog — это экземпляр класса org.apache.log4j.Logger.Далее вам необходимо создать свой
Appender, наследник JDBCAppender и переопределить в нем метод flushBuffer():
public class DBAppender extends JDBCAppender {
public void flushBuffer() {
//Do the actual logging
removes.ensureCapacity(buffer.size());
for (Iterator i = buffer.iterator(); i.hasNext();) {
try {
LoggingEvent logEvent = (LoggingEvent) i.next();
LogObj obj = getLogObject(logEvent);
saveLogObj(obj);
Connection con = getConnection();
con.commit();
removes.add(logEvent);
}
catch (SQLException e) {
errorHandler.error("Failed to excute sql", e,
ErrorCode.FLUSH_FAILURE);
}
}
// remove from the buffer any events that were reported
buffer.removeAll(removes);
// clear the buffer of reported events
removes.clear();
}
protected LogObj getLogObject(LoggingEvent event) {
if (event.getMessage() instanceof LogObj) {
return (LogObj) event.getMessage();
}
LogObj obj = new LogObj();
obj.setMessage(event.getMessage().toString());
return obj;
}
...
}
Вам осталось реализовать метод сохранения вашего объекта в базу данных —
saveLogObj(LogObj obj). Это обычный вызов JDBC insert инструкции или хранимой процедуры.Дополнительно нужно сказать пару слов по поводу времени жизни JDBC Connection: переменная connection инициализируется при первом обращении к методу
getConnection() и закрывается только в методе finalize(). Если посмотреть на метод closeConnection() в классе JDBCAppender, то вы можете увидеть, что он пустой и реально не закрывает connection, о чем и сообщается в комментарии к этому методу.Заключение
Библиотека log4j предоставляет гибкую систему для ведения логов. Более того из этой статьи можно увидеть, как легко можно расширить базовую функциональность log4j. Заменив класс
PatternParser, вы можете легко можете ввести свои символы подстановки. Это позволит решить ту же задачу с помощью класса JDBCAppender, с SQL выражениями, заданными в конфигурационных фалах и своими символами подстановки, специфичными для вашего объекта логирования.