常见日志框架介绍及Spring日志框架优先级源码解析

在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}
}

通过上面几段代码的分析,我们可以得出如下结论:

  1. Spring先查找org.apache.logging.log4j.spi.ExtendedLogger;
  2. 如果ExtendedLogger存在,那么继续查找org.apache.logging.slf4j.SLF4JProvider和org.slf4j.spi.LocationAwareLogger;
  3. 如果SLF4JProvider和LocationAwareLogger都存在,那么就启用SLF4J_LAL日志系统;
  4. 如果SLF4JProvider和LocationAwareLogger有一个不存在,就启用LOG4J日志系统;
  5. 如果ExtendedLogger不存在,就查找org.slf4j.spi.LocationAwareLogger;
  6. 如果LocationAwareLogger存在,就启用SLF4J_LAL日志系统;
  7. 如果LocationAwareLogger不存在,就继续查找org.slf4j.Logger;
  8. 如果org.slf4j.Logger存在,就启用SLF4J日志系统;
  9. 如果以上都不存在,就启用JUL日志系统。
发布了105 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/104380366