Jfinal中使用日志框架输出完整sql语句信息

  在开发过程中,我们希望控制台输出一些比较有价值的信息,JFinal开启调试模式以后,可以显示如下信息,这是极好的。

JFinal action report -------- 2017-06-19 19:58:05 ------------------------------
Url         : GET /sys/terminal/getListData
Controller  : cn.ablefly.controller.sys.TerminalController.(TerminalController.java:1)
Method      : getListData
Interceptor : cn.ablefly.core.auth.interceptor.SysLogInterceptor.(SysLogInterceptor.java:1)
              cn.ablefly.core.auth.interceptor.AuthorityInterceptor.(AuthorityInterceptor.java:1)
Parameter   : sord=asc  page=1  nd=1497873485305  sidx=  rows=10  _search=false

可惜的是,在显示sql语句方面,自带的调试输出效果就比较一般了,只会简单的显示出输出的sql语句,占位符也是用?号代替的。需要开启下面的注释掉的第二行代码

ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
//arp.setShowSql(true);
//arp.setDevMode(true);

显示效果如下:

Sql: select * from `evnet_terminal` where `id` = ?

显然,这种简陋的显示对我们调试复杂的sql语句和排错是没有太多价值的。那么我们就来动手改造一下吧!改造之后,我们的显示效果应该是这个样子的!完整的显示了执行的sql,占位符也被实际参数更换,更显示出了sql执行所花费的时间。

20:08:45.903 [qtp1803817267-27] DEBUG jdbc.sqltiming -  com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2714)
10. select * from `evnet_terminal` where `id` = 14 
 {executed in 0 msec}

1、确定所需要的jar包依赖。

不同项目所用的日志框架可能有所不同,所以项目中和日志有关的jar包也不尽相同,这里我只列出我发现的两种情况。

首先,必须引入的jar包包括如下三个:

		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.16</version>
		</dependency>		
		<dependency>
		  <groupId>org.slf4j</groupId>
		  <artifactId>slf4j-api</artifactId>
		  <version>1.7.7</version>
		</dependency>			
		<dependency>
		  <groupId>com.googlecode.log4jdbc</groupId>
		  <artifactId>log4jdbc</artifactId>
		  <version>1.2</version>
		</dependency>	

其中真正帮助我们显示完整sql数据的jar是log4jdbc-1.2.jar,该框架的详细介绍如下:

https://github.com/arthurblake/log4jdbc/blob/wiki/ProjectHome.md


还有一个jar包依赖,需要2选1,如果你的项目中已经引入了其中任何一个,就不需要再引入另一个了。2选1的jar包如下:

<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>log4j-over-slf4j</artifactId>
   <version>1.7.7</version>
</dependency>

<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.7.7</version>
</dependency>

jar包的版本不是必须使用1.7.7的,其他版本也可以尝试。同时引入以上两个包会产生堆栈溢出问题,详情可参考这篇文档:

http://blog.csdn.net/kxcfzyk/article/details/38613861

2、修改数据库配置信息中的jdbcUrl配置和driverClass配置,示例如下:

jdbcUrl = jdbc:log4jdbc:mysql://localhost:3306/evnet?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull

driverClass=net.sf.log4jdbc.DriverSpy

注意,jdbcUrl中的"jdbc:"与":mysql"之间的"log4jdbc"就是我们对url的改变之处,其他的数据库描述符的配置,也是一样的思路,在适当的位置加上"log4jdbc"即可。而driverClass的配置是固定的。

3、修改Jfinal核心配置类,用包含四个参数的构造函数配置数据库连接池。

在大部分情况下,我们在项目的核心配置类中,配置连接池,不管是c3p0还是druid或者使用其他的连接池,我们都会用三个参数的构造方法,如下示例为建立一个druid连接池的封装方法:

public static DruidPlugin createDruidPlugin() {
   DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"));
   dp.addFilter(new StatFilter());
   dp.addFilter(new Slf4jLogFilter());
   WallFilter wall = new WallFilter();
   wall.setDbType("mysql");
   dp.addFilter(wall);
   return dp;
}

我们只需要将这一行关键代码

DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"));

后面多加一个参数,即可指定所使用的driverClass,即代码改成

DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"),PropKit.get("driverClass"));

4、修改日志输出配置文件

这里也分为两种情况,比较老的项目可能使用log4j.properties做配置文件比较多,现在的大部分项目都是用logback,也就是使用logback.xml做配置文件。两种情况下配置文件的主要配置如下:

1)使用log4j.properties:

log4j.rootLogger=WARN, stdout, file
log4j.rootLogger=ERROR, stdout, file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n

# Output to the File
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.file.File=./jfinal_demo.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n


log4j.logger.jdbc.sqltiming=INFO,console  
#log4j.logger.jdbc.sqlonly=DEBUG,console
#log4j.logger.jdbc.connection=INFO,console
#log4j.appender.console=org.apache.log4j.ConsoleAppender 
#log4j.appender.console.layout=org.apache.log4j.PatternLayout
#log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %m%n%n

以上配置来自于jfinal-demo工程,其实我们只需要注意新增的两行配置

log4j.logger.jdbc.sqltiming=INFO,console  
#log4j.logger.jdbc.sqlonly=DEBUG,console

第一行即表示输出sql的同时,也输出sql的执行所耗费时间,第二行只会输出执行的sql,不显示执行耗费时间。

2)使用logback.xml:

因为对logback的配置方式并不熟悉,所以目前我为了实现控制台实现完整sql,过滤掉其他次要日志信息的配置方式也许不是最优的。仅供各位参考,这是我项目本身的日志配置:

logback.xml的内容:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <property name="ABSOLUTE_PATH"
      value="/logs/evnet-web" />

   <include resource="logback-application.xml" />

   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>

   <appender name="launchAppender"
      class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/launch-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>

      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>

   <appender name="applicationAppender"
      class="ch.qos.logback.core.rolling.RollingFileAppender">

      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/application-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>

      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>

   <appender name="sysapiAppender"
           class="ch.qos.logback.core.rolling.RollingFileAppender">

      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/sysapi-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>

      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <appender name="terminalAppender"
           class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/terminal-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>

      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <logger name="cn.ablefly" level="DEBUG" additivity="false">
      <appender-ref ref="applicationAppender" />
   </logger>
   <logger name="cn.ablefly.evnet" level="DEBUG" additivity="false">
      <appender-ref ref="terminalAppender" />
   </logger>
   <logger name="cn.ablefly.controller.sysapi" level="DEBUG" additivity="false">
      <appender-ref ref="sysapiAppender" />
   </logger>
   <root level="DEBUG">
      <appender-ref ref="console" />
   </root>
   
   <logger  level="debug" additivity="false">
   </logger>
</configuration>

关键的代码是最后面几行:

   <root level="DEBUG">
      <appender-ref ref="console" />
   </root>
   
   <logger  level="debug" additivity="false">
   </logger>

如果不加任何级别过滤,控制台的输出信息将十分巨大。所以在配置文件头部引入的logback-application.xml文件中,加入了如下日志级别过滤配置:

logback-application.xml的内容:

<?xml version="1.0" encoding="UTF-8"?>
<included>
   <logger name="jdbc.sqltiming" level="DEBUG" />
   <logger name="jdbc" level="WARN" />
   <logger name="org.eclipse" level="WARN" />
   <logger name="druid" level="WARN" />
   <logger name="io.netty" level="WARN" />
   <logger name="net.sf.ehcache" level="WARN" />
</included>

关键是前两个logger,让log4jdbc中除了sqltiming之外的所有logger全部只显示warn级别以上的日志。其他的过滤可以根据自身项目的需要配置。但是我觉得这个不是最优解。因为时间关系,没有深入去研究logback的配置原理和技巧。如果大家有更优雅的配置方式,希望不灵赐教。

好了,完成了以上几部以后,我们的目的就已经实现了,注意,这个时候我们就不需要在jfinal的主配置类中再开启ActiveRecord插件的showSql功能了!最后看下jfinal调试模式开启下的搭配使用效果:

21:10:48.440 [qtp1632409556-33] DEBUG jdbc.sqltiming -  com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2714)
9. select * from evnet_terminal limit 0, 10 
 {executed in 1 msec}
21:10:48.451 [qtp1632409556-33] DEBUG jdbc.sqltiming -  com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeUpdate(FilterChainImpl.java:2723)
9. insert into `sys_log`(`uid`, `err_msg`, `err_code`, `method_name`, `start_time`, `spend_time`, 
`from`, `class_name`, `url`, `ip`) values(1, '', 0, 'getListData', '2017-06-19 21:10:48', 14, 
'http://localhost:8080/sys/rate', 'cn.ablefly.controller.sys.RateController', '/sys/rate/getListData', 
'0:0:0:0:0:0:0:1') 
 {executed in 1 msec}

JFinal action report -------- 2017-06-19 21:10:48 ------------------------------
Url         : GET /sys/rate/getListData
Controller  : cn.ablefly.controller.sys.RateController.(RateController.java:1)
Method      : getListData
Interceptor : cn.ablefly.core.auth.interceptor.SysLogInterceptor.(SysLogInterceptor.java:1)
              cn.ablefly.core.auth.interceptor.AuthorityInterceptor.(AuthorityInterceptor.java:1)
Parameter   : sord=asc  page=1  nd=1497877848420  sidx=  rows=10  _search=false  
--------------------------------------------------------------------------------

完美配合!

最后给大家一个彩蛋吧,也是从别的地方搬运过来的,可以作为数据库方法测试的基类使用。

JFinalModelCase.java类

package cn.ablefly.test.evnet.sysapi;

/**
 * Created by xyzx1 on 2017-05-18.
 */

import cn.ablefly.model.EvnetTerminal;
import cn.ablefly.model._MappingKit;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallFilter;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
import com.jfinal.plugin.druid.DruidPlugin;
import org.junit.After;
import org.junit.BeforeClass;

/**
 * @author ilgqh
 * JFinal的Model测试用例
 *
 */
public class JFinalModelCase{

    protected static DruidPlugin dp;
    protected static ActiveRecordPlugin activeRecord;

    /**
     * 数据连接地址
     */
    private static String  URL = "jdbc:mysql://localhost:3306/evnet?useUnicode=true&characterEncoding=UTF-8";
    //private static final String  URL = "jdbc:mysql://10.10.120.204:3306/evnet?useUnicode=true&characterEncoding=UTF-8";

    /**
     * 数据库账号
     */
    private static String  USERNAME = "root";

    /**
     * 数据库密码
     */
    private static  String  PASSWORD = "root";

    /**
     * 数据库驱动
     */
    private static final String DRIVER = "com.mysql.jdbc.Driver";

    /**
     * 数据库类型(如mysql,oracle)
     */
    private static final String DATABASE_TYPE = "mysql";

    /**
     * @throws java.lang.Exception
     */
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        PropKit.use("a_little_config.txt");
        URL=PropKit.get("jdbcUrl");
        USERNAME=PropKit.get("user");
        PASSWORD=PropKit.get("password");
        dp=new DruidPlugin(URL, USERNAME,PASSWORD,DRIVER);

        dp.addFilter(new StatFilter());

        dp.setInitialSize(3);
        dp.setMinIdle(2);
        dp.setMaxActive(5);
        dp.setMaxWait(60000);
        dp.setTimeBetweenEvictionRunsMillis(120000);
        dp.setMinEvictableIdleTimeMillis(120000);

        WallFilter wall = new WallFilter();
        wall.setDbType(DATABASE_TYPE);
        dp.addFilter(wall);

        dp.getDataSource();
        dp.start();

        activeRecord = new ActiveRecordPlugin(dp);
        activeRecord.setDialect(new MysqlDialect())
        //.setDevMode(true)
        //.setShowSql(true)  //是否打印sql语句
        ;

        //映射数据库的表和继承与model的实体
        //只有做完该映射后,才能进行junit测试
        _MappingKit.mapping(activeRecord);
        activeRecord.start();
    }

    /**
     * @throws java.lang.Exception
     */
    @After
    public void tearDown() throws Exception {
        activeRecord.stop();
        dp.stop();
    }

}

当你需要为数据库的CURD方法做测试时,只要继承一下这个类,如写一个DBTest.java

package cn.ablefly.test.evnet.sysapi;

import cn.ablefly.model.DictData;
import cn.ablefly.model.EvnetTerminal;
import org.junit.Test;

/**
 * Created by xyzx1 on 2017-05-18.
 */
public class DBTest extends JFinalModelCase {
    @Test    public void testQueryTerminal(){
        EvnetTerminal terminal= EvnetTerminal.dao.findTerminalByTerminalId("075586511588001");
        System.out.println(terminal.toJson());
}
    @Test    public void testQueryDictName(){
        String name=DictData.dao.queryDataName(1,101003);
        System.out.println(name);
    }
}

然后在这个类里面写上任意方法调用你想测试的方法即可。加上@Test注解,即可即时运行,不需要启动web应用。

最后感谢在解决上述问题中波总给予的指导!希望在大家的努力下,JFinal能越来越强大,成为我们手中的神兵利器~~解决各种开发中的问题~~从而升职CEO,迎娶白富美,走向人生巅峰(误)!


评论区

JFinal

2017-06-19 21:29

这是目前最简单快捷的在输出 sql 的同时输出 sql 参数的方案,感谢你的分享,点赞并收藏之 ^_^

我要做菜鸟

2017-07-06 09:58

友情提示,DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"),PropKit.get("driverClass")); 这个driverClass可以不用配置,druid会自动判断你的jdbcUrl,如果是log4jdbc,那么就是你写的这个driver。当然,配置了会减少那么一丢丢的启动时间。

弯道加速跑

2017-07-07 11:25

@我要做菜鸟 谢谢,其实我也不想写的,哈哈

l745230

2017-07-07 11:39

@我要做菜鸟 其实应该去写,然后配置文件中找不到driverClass,druid就不会用上这个参数

l745230

2017-07-07 11:41

@我要做菜鸟 DruidPlugin line 114 if(this.driverClass != null) {
this.ds.setDriverClassName(this.driverClass);
}

我要做菜鸟

2017-07-24 15:46

@l745230 你这是plugin的代码,判断你自己有没有driverClass。但druid本身在初始化的时候会检测driverclass赋值了没有,如果没赋值,那么就会根据jdbcurl推断。代码在druid的初始化部分。

jambolleo

2017-08-03 16:57

driverClass=net.sf.log4jdbc.DriverSpy 这个DRIVERCLASS目前报错 not support oracle driver 1.0,原来我以为是OJDBC的问题,现在换成OJDBC6了也还是报错,干脆把OJDBC从MAVEN库里去掉,也还是报错,网上搜这个错误的解决方法是把OJDBC放到JRE/LIB/EXT下面,也是没法解决。

弯道加速跑

2017-08-06 13:48

@jambolleo 以前使用oracle数据库的项目也用了这个框架没有问题,使用的是objdbc14来着~~其他的版本没试过

pfjia

2017-09-15 15:28

@弯道加速跑

你好,我使用的是log4j.properties作为日志配置文件,但是不知如何设置不同级别过滤,导致控制台输出的日志非常多,尝试使用logback,将logback.xml和logback-application.xml放在resources目录下,导入logback-core,logback-classic,删除log4j相关依赖,但是日志如下:
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#noProviders for further details.
SLF4J: Class path contains SLF4J bindings targeting slf4j-api versions prior to 1.8.
SLF4J: Ignoring binding found at [jar:file:/F:/pocketpiano/web-api/target/web-api/WEB-INF/lib/slf4j-log4j12-1.7.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#ignoredBindings for an explanation.
log4j:WARN No appenders could be found for logger (org.apache.commons.beanutils.converters.BooleanConverter).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
也没有任何日志输出.
是因为JFinal不直接支持logback吗?如果要使JFinal支持logback应该怎么做?

a772856947

2017-11-15 10:42

@pfjia 我这边试了下也是这样,输出的信息过多,有没有解决的办法

a772856947

2017-11-15 10:43

@JFinal 波哥下个版本整合一下,加一个这样的功能咯

pfjia

2017-11-15 15:42

@a772856947 有,最后还是找到解决方案了,但是和文中提到的不太一样
1.日志解决方案
我最后换了日志框架,因为log4j最新版本是2012年,听说log4j2性能会好一点,所以便将日志框架改成slf4j+log4j2,使用log4j2.xml文件配置,除了配置Append之外还可以配置Logger,通过配置Logger限定某个包下的打印日志级别,解决了该问题
另:遇到了不少坑,大概搞了3天,最终配置完成
2.打印sql语句
在更换日志框架的过程中我发现DruidDatasource自带sql语句打印功能,无需修改jdbcUrl,只要配置文中提到的Slf4jLogFilter即可,具体内容可以看https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_LogFilter,此外,Druid的统计监控功能也非常强大
如果有时间你也可以搞下,遇到问题可以在这边交流

pfjia

2017-11-22 18:56

@a772856947 可以看下http://www.jfinal.com/share/491,http://www.jfinal.com/share/492

say意

2017-12-15 19:59

log4j打印日志过多,可以添加配置信息
log4j.logger.jdbc.sqltiming=INFO
log4j.logger.jdbc.sqlonly=OFF
log4j.logger.jdbc.audit=OFF
log4j.logger.jdbc.resultset=OFF
log4j.logger.jdbc.connection=OFF
这样就OK了

dev3932

2018-01-24 10:57

我这边是使用log4j.properties配置,jdbcUrl配置和driverClass配置,Slf4jLogFilter添加了 还是打印不了参数

JFinal9527

2018-01-25 12:58

log4j:ERROR Failed to rename [d://log//***] to [d://log//***_2018-01-25.log]. 问一下,这个怎么解决的?难道必须要修改源码吗?据说linux不存在这个问题,但是我们公司东西跑在windows上,所以咨询一下大神

feynman61

2018-05-18 17:12

不用开启ActiveRecord插件的showSql也可以打印sql,那么这种方法下如何关闭打印sql?

纵横

2018-06-08 15:03

伤心,怎么调试sql都打印不出,不知道为什么?

Anley

2018-06-19 19:33

按照配置sql语句打印不出来呀

lizq

2018-07-20 15:29

使用logback 需要slf4j-api

ch.qos.logback
logback-core
1.2.3


ch.qos.logback
logback-classic
1.2.3
test


org.slf4j
slf4j-api
1.7.21



ch.qos.logback
logback-access
1.2.3

热门分享

扫码入社