В публикации рассмотрим как можно внедрить логирование jdbc операций в существующее приложение без его пересборки и перекомпиляции. Это даст возможность протоколировать параметры запросов, которые заполняет программа, и многие другие аспекты работы с jdbc.
Хорошо, если у вас есть возможность добавить log4jdbc в сборку приложения.
<dependency>
<groupId>com.googlecode.log4jdbc</groupId>
<artifactId>log4jdbc</artifactId>
<version>1.2</version>
</dependency>
И использовать net.sf.log4jdbc.DriverSpy при создании соединения.
Если же такой возможности нет, то aspectj-scripting поможет вам с логированием jdbc операций.
Подопытной программой остается SonarQube, как и в статьях про hawt.io/h2 и CRaSH-ssh. В этих статьях был подробнее рассмотрен процесс конфигурирования и принцип работы aspectj-scripting и приведены пошаговые инструкции.
Для логирования jdbc операций изменим параметры запуска jvm сонара:
sonar.web.javaAdditionalOpts=-javaagent:aspectj-scripting-1.0-agent.jar -Dorg.aspectj.weaver.loadtime.configuration=config:file:h2_jdbc.xml
Также требуется доступность jvm агента aspectj-scripting и файл конфигурации h2_jdbc.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
<aspects>
<name>com.github.igorsuhorukov.JdbcLog</name>
<type>AROUND</type>
<pointcut>execution(* org.apache.commons.dbcp.BasicDataSource.getConnection())</pointcut>
<process>
<expression>
log4jdbcresultUrls = com.github.smreed.dropship.MavenClassLoader.forMavenCoordinates("com.googlecode.log4jdbc:log4jdbc:1.2").getURLs();
slf4jresultUrls = com.github.smreed.dropship.MavenClassLoader.forMavenCoordinates("org.slf4j:slf4j-simple:1.6.0").getURLs();
resultUrls = new java.net.URL[log4jdbcresultUrls.length + slf4jresultUrls.length];
System.arraycopy(log4jdbcresultUrls, 0, resultUrls, 0, log4jdbcresultUrls.length);
System.arraycopy(slf4jresultUrls, 0, resultUrls, log4jdbcresultUrls.length, slf4jresultUrls.length);
log4jdbcLoader = new java.net.URLClassLoader(resultUrls, Thread.currentThread().getContextClassLoader());
log4jdbcLoader.loadClass("net.sf.log4jdbc.ConnectionSpy");
currentLoader = java.lang.Thread.currentThread().getContextClassLoader();
java.lang.Thread.currentThread().setContextClassLoader(log4jdbcLoader);
res = new net.sf.log4jdbc.ConnectionSpy(joinPoint.proceed());
java.lang.Thread.currentThread().setContextClassLoader(currentLoader);
res;
</expression>
</process>
</aspects>
</configuration>
Этот аспект позволяет перехватить вызов getConnection() класса BasicDataSource из библиотеки common-dbcp и вернуть обертку ConnectionSpy для соединения с базой данных. При этом классы из log4jdbc становятся доступными в приложении за счет созданиия своего загрузчика классов из maven артефактов в локальном репозитарии. aspectj-scripting загружает артефакты com.googlecode.log4jdbc:log4jdbc:1.2 и org.slf4j:slf4j-simple:1.6.0 на основе конфигурации, которую указал выше. Это работает благодаря тому, что мы передали при старте jvm два дополнительных параметра: "-javaagent" для старта aspectj-scripting агента и -Dorg.aspectj.weaver.loadtime.configuration для передачи ему конфигурации. И aspectj агент инструментирует классы приложения при их загрузки.
В библиотеке log4jdbc доступны следующие логгеры:
- jdbc.sqlonly — Логирует только SQL
- jdbc.sqltiming — Логирует SQL и время выполнения
- jdbc.audit — Логирует все вызовы JDBC API, кроме работы с ResultSet
- jdbc.resultset — Все вызовы к ResultSet протоколируются
- jdbc.connection — Логируются открытие и закрытие соединения, полезно использовать для поиска утечек соединений
Напоследок приведу несколько примеров из логов работы SonarQube, веб часть которого написана на ruby и выполняется в jruby:
[jdbc.connection] 2. Connection opened
[jdbc.connection] 81. Connection closed
[jdbc.sqltiming] select * from schema_migrations {executed in 4 msec}
[jdbc.audit] 7. PreparedStatement.setString(1, «sonar.core.id») returned
[jdbc.audit] 9. PreparedStatement.setTimestamp(1, 2015-08-09 18:49:08.205) returned
[jdbc.audit] 9. PreparedStatement.setFetchSize(200) returned
[jdbc.audit] 14. Connection.prepareStatement(update metrics set best_value=?, delete_historical_data=?, description=?, direction=?, domain=?, enabled=?, hidden=?, short_name=?, optimized_best_value=?, origin=?, qualitative=?, val_type=?, user_managed=?, worst_value=? where id=?) returned net.sf.log4jdbc.PreparedStatementSpy@6e22c0e5
[jdbc.resultset] 2. ResultSet.getMetaData() returned rsMeta0: columns=1
[jdbc.resultset] 2. ResultSet.getType() returned 1003
[jdbc.sqlonly] select ar.id from analysis_reports ar where ar.report_status='PENDING' and not exists( select 1 from analysis_reports ar2 where ar.project_key = ar2.project_key and ar2.report_status='WORKING') order by ar.created_at asc, ar.id asc
Пример, описаный в статье, вы можете увидеть в скринкасте:
Итак, мы смогли модифицировать конфигурацию jvm SonarQube таким образом, чтобы в его лог файл записывались все вызовы приложением Jdbc API с их параметрами. Надеюсь, эта публикация вам пригодиться и вы найдете другие способы применения аспектно-ориентированного программирования для существующих java программ