2019-05-10 23:14:07 +0000   |     spring gradle log   |   Viewed times   |    

Gradle中关于Logging的常见问题与常用配置

第三章中做了个总结,这里拎出来自成一篇,方便查阅。

Logging

Spring的日志系统面向Jakarta Commons Logging API (JCL),打包在spring-jcl依赖包中。JCL和slf4j一样,不是一个实际的日志系统实现,只是一个日志接口。负责将代码和具体的日志系统,如log4jjava.util.logging解耦。

JCL的优点是它有一个自动搜索类路径下可用日志实现的功能。只需要将log4j加入依赖,项目日志就可以工作。缺点是这个搜索算法会影响效率。更多的项目用的是slf4j而不是JCL。

在Spring里如果要用slf4j替代commons-logging

  1. 先要禁用spring-jcl
  2. 再添加jcl-over-slf4j包,把之前对commons-logging的调用全转到slf4j接口上来。
  3. 再加入slf4j-api -> slf4j-log4j12 -> log4j全家桶。

gradle中配置如下,

// log4j & slf4j
configurations.all { // 不禁用spring-jcl,无法嫁接到slf4j上
    exclude group: 'org.springframework', module: 'spring-jcl'
}
implementation 'org.slf4j:jcl-over-slf4j:1.7.26'
implementation "org.slf4j:slf4j-api:1.7.26"
implementation 'org.slf4j:slf4j-log4j12:1.7.26'
implementation "log4j:log4j:1.2.17"

简单说明一下,spring-jcl包依赖由spring-core包传递进来。项目只引用了spring-context包依赖,spring-core会跟随spring-context包传递进来。

+--- org.springframework:spring-context:5.1.6.RELEASE
|    +--- org.springframework:spring-aop:5.1.6.RELEASE
|    |    +--- org.springframework:spring-beans:5.1.6.RELEASE
|    |    |    \--- org.springframework:spring-core:5.1.6.RELEASE
|    |    |         \--- org.springframework:spring-jcl:5.1.6.RELEASE
...
...

简单说一下jcl-over-slf4j的原理

Spring面向commons-logging,也就是代码里会导入下面的库。代码中对LogLogFactory的调用不可逆转地会指向commons-logging的库。

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

为了劫持对org.apache.commons.logging.Log的引用,就必须写一个和commons-logging一模一样的包结构。实际上jcl-over-slf4j就是这么干的,它的包结构如下,彻底伪装成commons-logging。所以用jcl-over-slf4j的时候,必须禁掉对commons-logging的依赖,否则编译器不知道加载谁了。

org
└── apache
    └── commons
        └── logging
            ├── Log.class
            ├── LogConfigurationException.class
            ├── LogFactory.class
            └── impl
                ├── NoOpLog.class
                ├── SLF4JLocationAwareLog.class
                ├── SLF4JLog.class
                ├── SLF4JLogFactory.class
                ├── SimpleLog$1.class
                └── SimpleLog.class

jcl-over-slf4j顺利劫持原本对commons-logging的引用到slf4j之后,直接上slf4j-api -> slf4j-log4j12 -> log4j全家桶就行了。

详细过程参考下面这张图, to-use-slf4j-in-spring

解决标准输出无法在终端输出的问题

我的log4j.properties已经配置了将console扩展器定向到标准输出,但此时运行gradle构建,终端不会有输出。

# Configure stdout
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
... ...
... ...

问题出在gradle。gradle会把写到标准输出STANDARD_OUT的所有内容重定向到它的日志系统的QUIET级别中,把标准错误STANDARD_ERROR的内容重新定向到ERROR级别。gradle一共有6个级别,QUIET是仅次于ERROR的第二高级别。而gradle默认的日志级别是LIFECYCLE,比QUITE要低一级。也就是说默认情况标准输出和标准错误的内容都应该正常输出。 log-levels

但是gradle的日志容器还加了额外一道锁。负责这个日志容器配置的是test.testLogging字段, gradle-testlogging-1 gradle-testlogging-2

它是一个TestLoggingContainer类,其中的showStandardStreams参数默认为不显示标准输出的内容。 showStandardStreams

所以想要在控制台输出标准输出,还需要修改这个showStandardStreams参数。在build.gradle中可以这么修改,

test {
    testLogging {
		showStandardStreams = true    // 显示标准输出和标准错误的内容
	}
}

一般我们还会把异常跟踪栈内容显示量设置为full,以便在测试时获得尽可能多的信息。下面是一个不错的惯用配置,

test {
    testLogging {
		outputs.upToDateWhen {false}  // 就算test没有更新内容,仍然输出    
		showStandardStreams = true    // 显示标准输出和标准错误的内容
        exceptionFormat 'full'        // 显示所有异常跟踪栈内容
        events 'started', 'skipped', 'passed', 'failed'     // 记录特定测试事件
	}
}