之前老板让实现一个日志服务器,然而我们都不知道已经有现成的日志监控包直接使用(没有站在巨人肩膀上干活,害)
本篇主要介绍 log4jdbc,在进行系统开发时,我们一般会查看执行的SQL/了解SQL执行时间,这个时候其实可以代码在sql执行前后计算时间,框架执行sql的时候进行sql输出,其实也是可以实现的,但是有更简单的干嘛不用呢~,学习了 log4jdbc 然后来记录一下
log4jdbc 是一个 Java JDBC Driver,可以记录 SQL 日志和 SQL 执行时间等信息,使用 SLF4J作为日志系统
日志系统相关内容:(一个日志门面 一个日志实现 构成日志系统)
log4jdbc 可以会加载以下驱动:
源码可看:net.sf.log4jdbc.sql.jdbcapi
Driver Class | Database Type |
---|---|
oracle.jdbc.driver.OracleDriver | Older Oracle Driver |
oracle.jdbc.OracleDriver | Newer Oracle Driver |
com.sybase.jdbc2.jdbc.SybDriver | Sybase |
net.sourceforge.jtds.jdbc.Driver | jTDS SQL Server & Sybase driver |
com.microsoft.jdbc.sqlserver.SQLServerDriver | Microsoft SQL Server 2000 driver |
com.microsoft.sqlserver.jdbc.SQLServerDriver | Microsoft SQL Server 2005 driver |
weblogic.jdbc.sqlserver.SQLServerDriver | Weblogic SQL Server driver |
com.informix.jdbc.IfxDriver | Informix |
org.apache.derby.jdbc.ClientDriver | Apache Derby client/server driver, aka the Java DB |
org.apache.derby.jdbc.EmbeddedDriver | Apache Derby embedded driver, aka the Java DB |
com.mysql.jdbc.Driver | MySQL |
org.postgresql.Driver | PostgresSQL |
org.hsqldb.jdbcDriver | HSQLDB pure Java database |
org.h2.Driver | H2 pure Java database |
log4jdbc 中使用了以下 7 种logger,可以进行相应的配置来实现需求
logger | 描述 |
---|---|
jdbc.sqlonly | 仅仅记录 SQL 语句,会将占位符?替换为实际的参数 |
jdbc.sqltiming | 记录 SQL 语句实际的执行时间 |
jdbc.audit | 除了 ResultSet 之外的所有JDBC调用信息,篇幅较长 |
jdbc.resultset | 包含 ResultSet 的信息,输出篇幅较长 |
jdbc.connection | 输出了 Connection 的 open、close 等信息 |
log4jdbc.debug | 内部调试使用,输出 log4jdbc spy 加载驱动时的信息 |
jdbc.resultsettable | 显示前滚结果集的记录器 |
一般常用的是这5个:jdbc.audit jdbc.resultset jdbc.sqlonly jdbc.sqltiming jdbc.connection
以下 基于 SpringBoot 框架的项目进行使用介绍
使用首先导包
<dependency>
<groupId>org.bgee.log4jdbc-log4j2groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1artifactId>
<version>1.16version>
dependency>
在使用 log4jdbc 时,需要进行三个配置:
(1)更改数据库连接信息
主要修改 driverClassName 与 url,以yaml 配置为例:
# 修改前
driverClassName: com.mysql.jdbc.Driver
url: mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8
# 修改后
driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
url: jdbc:log4jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8
(2)编写 log4jdbc.log4j2.properties 文件
在 resources 目录下新建 log4jdbc.log4j2.properties 文件,可配置信息如下:
默认文件名称为log4jdbc.log4j2.properties ,如果想自定义配置,需要在System properties文件中指定 log4jdbc.log4j2.properties.file ,value对应文件名称
log4jdbc.log4j2.properties.file=xxxx.properties
源码:
可配置字段:
logger | 描述 |
---|---|
log4jdbc.spylogdelegator.name | spy日志处理类,默认为 net.sf.log4jdbc.log4j2.Log4j2SpyLogDelegator |
log4jdbc.debug.stack.prefix | 项目应用程序的包的部分或全部的前缀 |
log4jdbc.sqltiming.warn.threshold | 执行SQL的时间长度(毫秒),在该时间段内,SQL需要至少运行这么长时间才能生成警告消息 |
log4jdbc.sqltiming.error.threshold | 执行SQL的时间长度(毫秒),在该时间段内,SQL需要至少运行这么长时间才能生成错误消息 |
log4jdbc.dump.booleanastruefalse | boolean 值显示为 0 和 1 ,为 true 时 boolean 值显示为 true 和 false,默认false |
log4jdbc.dump.sql.maxlinelength | SQL显示的最大长度,默认90 |
log4jdbc.dump.fulldebugstacktrace | 在调试模式下是否转储完整堆栈跟踪,默认false |
log4jdbc.statement.warn | 使用statement(不是PreparedStatement)时,在日志中是否与SQL一起显示警告,默认false |
log4jdbc.dump.sql.select | select语句是否输出,默认true |
log4jdbc.dump.sql.insert | insert语句是否输出,默认true |
log4jdbc.dump.sql.update | update语句是否输出,默认true |
log4jdbc.dump.sql.delete | delete语句是否输出,默认true |
log4jdbc.dump.sql.create | create语句是否输出,默认true |
log4jdbc.dump.sql.addsemicolon | 是否在 SQL 的行末添加一个分号,默认false |
log4jdbc.auto.load.popular.drivers | 是否自动选择最佳jdbc驱动,默认true |
log4jdbc.drivers | jdbc驱动,String类型 |
log4jdbc.trim.sql | 默认true |
log4jdbc.trim.sql.extrablanklines | 默认true |
log4jdbc.suppress.generated.keys.exception | 默认false |
简单常用配置**(直接拿去用)**:
# 以下三个都需配置
# spy日志处理类
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
# jdbc驱动(这里对应的mysql驱动版本为8.x)
log4jdbc.drivers=com.mysql.cj.jdbc.Driver
# 自动选择最佳jdbc驱动
log4jdbc.auto.load.popular.drivers=false
# 以下三个配置看自己需求了
# 设置的值为项目应用程序的包的部分或全部的前缀
log4jdbc.debug.stack.prefix=blog
# 当该值为 false 时,boolean 值显示为 0 和 1 ,为 true 时 boolean 值显示为 true 和 false
log4jdbc.dump.booleanastruefalse=true
# 是否在 SQL 的行末添加一个分号
log4jdbc.dump.sql.addsemicolon=true
(3)配置 logger
在 resources 目录下新建 logback.xml 文件(默认文件名)
如果需要自定义,可以在yaml中指定:
logging:
config: classpath:xxxx.xml
配置上述的常用的 5大logger:(看自己实际需求配置了)
<configuration scan="true" scanPeriod="30 seconds" debug="false">
<contextName>BLOGcontextName>
<property name="LOG_HOME" value="logs/%d{yyyy-MM,aux}/"/>
<property name="LOG_" value="logs/"/>
<property name="log.charset" value="utf-8"/>
<property name="log.pattern"
value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}pattern>
<charset>${log.charset}charset>
encoder>
appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd-HH}.logFileNamePattern>
<MaxHistory>60MaxHistory>
rollingPolicy>
<encoder>
<pattern>${log.pattern}pattern>
<charset>${log.charset}charset>
encoder>
appender>
<logger name="jdbc.resultsettable" level="INFO" additivity="false">
<appender-ref ref="console"/>
logger>
<logger name="jdbc.sqltiming" level="INFO" additivity="false">
<appender-ref ref="console"/>
logger>
<logger name="jdbc.sqlonly" level="OFF" additivity="false">
<appender-ref ref="console"/>
logger>
<logger name="jdbc.resultset" level="ERROR" additivity="false">
<appender-ref ref="console"/>
logger>
<logger name="jdbc.connection" level="OFF" additivity="false">
<appender-ref ref="console"/>
logger>
<logger name="jdbc.audit" level="OFF" additivity="false">
<appender-ref ref="console"/>
logger>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
root>
configuration>
上面配置好了,无需其他额外的配置,运行项目就可以直接看到相应的SQL执行情况,在控制台输出,输出的情况根据配置的情况有所不同
总结:其实底层还是调用logger(org.apache.logging.log4j.Logger / org.slf4j.Logger )来实现日志记录,它的工作只是在执行sql的前后做了一些操作,将相应的内容进行输出
代码总览:
以下面的数据源与驱动配置来介绍一下我理解的原理:
db-type: com.alibaba.druid.pool.DruidDataSource
driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
url: jdbc:log4jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:eladmin}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
以下为 debug 查看的相应执行流程:
(1)加载 DruidDataSource 数据源加载相应的驱动 net.sf.log4jdbc.sql.jdbcapi.DriverSpy,获取数据库连接
根据前缀,找到真实url的驱动:
static final private String log4jdbcUrlPrefix = "jdbc:log4";
// net.sf.log4jdbc.sql.jdbcapi.DriverSpy#getUnderlyingDriver
private Driver getUnderlyingDriver(String url) throws SQLException{
if (url.startsWith(log4jdbcUrlPrefix)) {
url = this.getRealUrl(url); // substring 前缀后的真实url
Enumeration<Driver> e = DriverManager.getDrivers();
Driver d;
while (e.hasMoreElements()) {
d = e.nextElement();
if (d.acceptsURL(url)) {
return d;
}
}
}
return null;
}
获取到 Connection / ConnectionSpy(包装JDBC连接),主要是根据5大logger是否有一个配置Error级别来决定返回类型
// net.sf.log4jdbc.sql.jdbcapi.DriverSpy#connect
@Override
public Connection connect(String url, java.util.Properties info) throws SQLException
{
Driver d = getUnderlyingDriver(url); //获取到驱动
if (d == null) {
return null;
}
// 真实url,substring前缀后的url
url = this.getRealUrl(url);
lastUnderlyingDriverRequested = d;
long tstart = System.currentTimeMillis();
// 根据log4jdbc.log4j2.properties 中指定的 log4jdbc.drivers 进行connect (com.mysql.cj.jdbc.Driver)
Connection c = d.connect(url, info);
if (c == null) {
throw new SQLException("invalid or unknown driver url: " + url);
}
if (log.isJdbcLoggingEnabled()) { // 如果5大logger有配置leverl为Error级别的就为true
ConnectionSpy cspy = new ConnectionSpy(c, System.currentTimeMillis() - tstart, log); // 包装JDBC连接并报告方法调用、返回和异常。
RdbmsSpecifics r = null;
String dclass = d.getClass().getName(); // 指定的 log4jdbc.drivers name
if (dclass != null && dclass.length() > 0){
r = rdbmsSpecifics.get(dclass);
}
// defaultRdbmsSpecifics为 net.sf.log4jdbc.sql.rdbmsspecifics.RdbmsSpecifics
if (r == null){
r = defaultRdbmsSpecifics; // 主要是封装特定关系数据库管理系统的sql格式细节,以便为该RDMBS编写准确、可用的sql。
}
cspy.setRdbmsSpecifics(r);
return cspy;
}
return c;
}
(2)执行SQL 日志处理流程
真正执行SQL的时候,会在 包装Statement并报告方法调用、返回和异常 的xxxSpy来进行相应的SQL执行逻辑
以执行一条查询语句为例:
// net.sf.log4jdbc.sql.jdbcapi.StatementSpy
@Override
public ResultSet executeQuery(String sql) throws SQLException
{
String methodCall = "executeQuery(" + sql + ")";
this.sql = sql;
reportStatementSql(sql, methodCall); // SqlOnlyLogger记录
long tstart = System.currentTimeMillis();
try
{
ResultSet result = realStatement.executeQuery(sql); // 真正执行SQL
reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall); // SqlTimingLogger记录
ResultSetSpy r = new ResultSetSpy(this, result, this.log);
return (ResultSet) reportReturn(methodCall, r); // resultsetLogger记录
}
catch (SQLException s)
{
// 如果Logger配置了Error,即记录
reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
throw s;
}
}
说明:
github log4jdbc
log4jdbc 官网