1 日志系统
在应用程序的开发中,通常会使用日志记录监视代码中变量的变化情况,输出到控制台,文件系统或者网络。记录日志目的在于线上问题追踪,作为其他系统进行统计分析的"数据".
2 Java常用日志框架
Java 的日志框架有很多,比如:JUL(Java Util Logging)、Log4j、Logback、Log4j2、Tinylog 等。除此之外,还有 JCL(Apache Commons Logging)和 SLF4J 这样的“门面日志”
2.1 门面日志
什么是“门面日志”。“门面日志”利用了设计模式中的门面模式思想,对外提供一套通用的日志记录的 API,而不提供具体的日志输出服务,如果要实现日志输出,需要集成其他的日志框架,比如 Log4j、Logback、Log4j2 等。
这种门面模式的好处在于,记录日志的 API 和日志输出的服务分离开,代码里面只需要关注记录日志的 API,通过 SLF4J 指定的接口记录日志;而日志输出通过引入 JAR 包的方式即可指定其他的日志框架。当我们需要改变系统的日志输出服务时,不用修改代码,只需要改变引入日志输出框架 JAR 包。
2.2 日志框架的历史
- 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j(作者Ceki Gülcü)。后来Log4j成为Apache基金会项目中的一员。
- 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议Sun引入Log4j到java的标准库中,但Sun拒绝了.
- 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。但是如果有人想换成其他日志组件,如log4j换成JUL,因为api完全不同,就需要改动代码。
- Apache见此,开发了JCL(Jakarta Commons Logging),即commons-logging-xx.jar。它只提供一套通用的日志接口api,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样应用程序可以在运行时选择自己想要的日志实现组件。
- 这样看上去也挺美好的,但是log4j的作者觉得JCL不好用,自己开发出slf4j,它跟JCL类似,本身不替供日志具体实现,只对外提供接口或门面。目的就是为了替代JCL。同时,还开发出logback,一个比log4j拥有更高性能的组件,目的是为了替代log4j。
- Apache参考了logback,并做了一系列优化,推出了log4j2
3 commons-logging
3.1 概述
Apache Commons Logging是一个门面日。代码里面只需要关注记录日志的 API,通过JCL指定的接口记录日志;而日志输出通过引入 JAR 包的方式实现。如果没有引入日志框架,Apache Commons Logging会使用内部默认实现 SimpleLog
JUL最后更新于2014年7月,现以停止更新。
3.2 包结构
- Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error
- LogFactory:抽象类,日志工厂,获取日志类;
- LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;
- Log4JLogger:对log4j的日志对象封装;
- Jdk14Logger:对JDK1.4的日志对象封装;
- Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;
- SimpleLog:commons-logging自带日志对象;
3.2 日志框架加载
jcl可以通过在ClassPath下创建commons-logging.properties配置文件指定加载日志实现框架。
#指定日志对象:
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
#指定日志工厂:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl
jcl如果没有指定日志实现框架则默认加载时按照顺序log4j>jul> simpleLog依次加载,如果查找到log4j包则使用log4j,如果没有依次类推。
3.3 JCL使用
引用JCL依赖包
commons-logging
commons-logging
1.2
测试代码
此时由于项目也没有放入其他日志框架依赖包,会按照顺序log4j>jul> simpleLog加载,因此此时会使用jdk自带jul实现日志打印。
public class commons_loggingDemo {
Log log= LogFactory.getLog(commons_loggingDemo.class);
@Test
public void test() throws IOException {
log.debug("Debug info.");
log.info("Info info");
log.warn("Warn info");
log.error("Error info");
log.fatal("Fatal info");
}
}
引用log4j依赖包
在不修改代码的前提下,引入log4j依赖包。在次调用测试代码,此时会使用log4j实现日志打印。
log4j
log4j
1.2.16
4 SLF4J
4.1 概述
SLF4J同样是一个门面日志。代码里面只需要关注记录日志的 API,通过SLF4J指定的接口记录日志;而日志输出通过引入 JAR 包的方式实现。
slf4j-api.jar 表示SLF4J提供的日志接口相关的jar包
natvie implementation of slf4j-api: 表示SLF4J本地底层日志框架,其中包括logback-classic.jar + logback-core.jar作为logback日志框架底层实现,SLF4J-Simple作为SLF4J简单日志框架实现,slf4j-nop.jar表示空实现。
none-natvie implementation of slf4j-api :表示非本地,外部日志框架。其中包括lo4j.jar表示log4j日志框架,JUL表示JDK自带日志框架,log4j-core.jar表示log4j2日志框架(图中没画)
Adaptation layer:表示外部日志框架的适配器
组合如下
- 使用logback作为日志框架
- slf4j-api.jar + logback-classic.jar + logback-core.jar
- 使用log4j作为日志框架
- slf4j-api.jar + slf4j-log4j12.jar + log4j.jar
- 使用jul作为日志框架
- slf4j-api.jar + slf4j-jdk14.jar
- 使用log4j2作为日志框架
- slf4j-api.jar + +log4j-slf4j-impl+log4j-core
- 只用slf4j无日志实现
- slf4j-api.jar + slf4j-nop.jar
4.2 桥接器
在实际开发过程中通常会遇到下面的问题,项目中旧模块使用的是log4j来打印日志的话,而新模块想用logback来统一打印日志的话,由于两者并不兼容,如果直接修改业务代码会存在一定风险。那么该如何解决呢?
SLFJ提供了各种日志框架的桥接器,我们只需要引用对应适配器的实现包,并去掉原始的依赖包,SLFJ会自动帮我们完成日志框架的统一。上面案例中可以去掉log4j包log4j.jar引入log4j-over-slf4j包(这个包使之前代码使用log4j不会因为找不到而报错,并会将打印日志桥接给slf4j-api.jar),并同时引入slf4j-api.jar + logback-classic.jar + logback-core.jar。
SLFJ提供的桥接器
- JCL桥接器
- 使用jcl-over-slf4j.jar
- log4j桥接器
- 使用log4j-over-slf4j.jar
- JUL桥接器
- 使用jul-to-slf4j.jar
4.3 小结
4.4 使用
springBoot默认使用logback作为日志框架,使用slf4j作为门面日志。
使用springBoot+slf4j+logback
org.springframework.boot
spring-boot-dependencies
1.5.18.RELEASE
pom
import
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-test
这里已经帮助我们添加各种类型日志桥接器,如果我们我们向将原来项目中日志框架去掉,并使用Logback框架时。只需要去掉原先依赖。
使用springBoot+slf4j+log4j
这里需要添加spring-boot-starter-log4j依赖,并将spring-boot-starter-logging从依赖中排除。
org.springframework.boot
spring-boot-dependencies
1.5.18.RELEASE
pom
import
org.springframework.boot
spring-boot-starter
spring-boot-starter-logging
org.springframework.boot
org.springframework.boot
spring-boot-starter-log4j
1.3.8.RELEASE
org.springframework.boot
spring-boot-starter-test
同样这里已经帮助我们添加各种类型日志桥接器,如果我们我们向将原来项目中日志框架去掉,并使用logb4j框架时。只需要去掉原先依赖。
使用springBoot+slf4j+log4j2
这里需要添加spring-boot-starter-log4j2依赖,并将spring-boot-starter-logging从依赖中排除。
org.springframework.boot
spring-boot-dependencies
1.5.18.RELEASE
pom
import
org.springframework.boot
spring-boot-starter
spring-boot-starter-logging
org.springframework.boot
org.springframework.boot
spring-boot-starter-log4j2
org.springframework.boot
spring-boot-starter-test
同样这里已经帮助我们添加各种类型日志桥接器,如果我们我们向将原来项目中日志框架去掉,并使用logb4j框架时。只需要去掉原先依赖。
关于其他使用日志框架可以排除spring-boot-starter-logging后自行添加就不再详诉。
测试代码
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class Sj4jTest {
Logger logger = LoggerFactory.getLogger("Sj4jTest");
@Test
public void test() throws IOException {
logger.debug("Debug info.");
logger.info("Info info");
logger.warn("Warn info");
logger.error("Error info");
}
}