在Java项目开发中,日志是必不可少的功能,日志对于快速定位问题,检查日常项目运行状态等有非常重要的作用,但是目前Java日志存在多种框架,如:Slf4j、JUL、JCL、Log4j、Log4j2、Logback等。
1. 常用日志框架
框架 | 简介 |
---|---|
Slf4j | 日志门面组件 |
JCL | Commons Logging,简称jcl,Apache基金会项目,日志门面组件 |
Log4j | Apache基金会项目,日志实现框架 |
Log4j 2 | 是Log4j的升级产品,但是与Log4j不兼容 |
Logback | 日志实现框架 |
JUL | java官方的日志实现框架 |
Java中可用的日志框架有很多,这样就导致一个选择困难问题,到底应该用哪一个框架,如果项目修改日志组件或者升级又该如何做。其实一般都会选择使用外观模式:日志门面组件+桥接器+日志实现框架,这样即使项目更换日志种类,只需更换桥接器和日志实现框架,也就是只更换Jar包就可以了,代码无需做任何改动。下图表示了日志门面组件、桥接器、日志时间框架之间的关系:
图上列举出了多种日志实现框架转换成Slf4j接口和Slf4j接口绑定多种日志实现框架所涉及到的相关Jar包。通过这些桥接包,我们可以轻松实现项目中日志框架的统一。对于哪些包需要引入/哪些包需要排除也就一目了然了。
2.SLF4J 简介
SLF4J 是一个简单易用的日志门面组件,并且提供桥接jar实现多种日志框架绑定转换成SL4J。建议选择Slf4j+logback,相比其他组合相比有以下优势: 限制较少,使用范围更广;更好的性能,更低的开销;Slf4j和Logback是同一个人开发,衔接好。官网地址:http://www.slf4j.org/manual.html
简单实用示例
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
3.Spring日志框架优先级
以下源码分析基于spring-core:5.1.6。
首先是LogFactory获取日志对象源码:
/*
* @since 5.0
*/
public abstract class LogFactory {
/**
* Convenience method to return a named logger.
* @param clazz containing Class from which a log name will be derived
*/
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
/**
* Convenience method to return a named logger.
* @param name logical name of the <code>Log</code> instance to be returned
*/
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
}
源码看出是通过LogAdapter创建的log对象,继续往下看
final class LogAdapter {
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
private LogAdapter() {
}
/**
* Create an actual {@link Log} instance for the selected API.
* @param name the logger name
*/
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
// Defensively use lazy-initializing adapter class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
return JavaUtilAdapter.createLog(name);
}
}
private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false;
}
}
private enum LogApi {LOG4J, SLF4J_LAL, SLF4J, JUL}
}
通过上面几段代码的分析,我们可以得出如下结论:
- Spring先查找org.apache.logging.log4j.spi.ExtendedLogger;
- 如果ExtendedLogger存在,那么继续查找org.apache.logging.slf4j.SLF4JProvider和org.slf4j.spi.LocationAwareLogger;
- 如果SLF4JProvider和LocationAwareLogger都存在,那么就启用SLF4J_LAL日志系统;
- 如果SLF4JProvider和LocationAwareLogger有一个不存在,就启用LOG4J日志系统;
- 如果ExtendedLogger不存在,就查找org.slf4j.spi.LocationAwareLogger;
- 如果LocationAwareLogger存在,就启用SLF4J_LAL日志系统;
- 如果LocationAwareLogger不存在,就继续查找org.slf4j.Logger;
- 如果org.slf4j.Logger存在,就启用SLF4J日志系统;
- 如果以上都不存在,就启用JUL日志系统。