「Spring」3. Spring 日志 - SpringJcl 源码阅读

274 阅读4分钟

Spring5 之前是 JCL(外部jar包)

Spring5(包含) 之后是 Spring-JCL(内部module,基于jcl修改)

因为之前源码下载是 5.2.19,所以主要看下 Spring-JCL。

源码

首先看下log对象

	/** Logger used by this class. Available to subclasses. */
	protected final Log logger = LogFactory.getLog(getClass());
	/**
	 * 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) {
		// 创建具体的log对象
		return LogAdapter.createLog(name);
	}

看到这边主要是LogAdapter类去创建对象,那我们详细看下这个类。

final class LogAdapter {

	// log4j2 jar包中的一个类
	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.
				/*
				 * spring团队其实更倾向使用 log4j2,但如果你非要用slf4j,那就必须满足两个条件
				 * 1. 必须要有 log4j2 - slf4j 的桥接器
				 * 2. slf4j 的依赖
				 */
				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) {
			// log4j2
			case LOG4J:
				return Log4jAdapter.createLog(name);
			// slf4j_full
			case SLF4J_LAL:
				return Slf4jAdapter.createLocationAwareLog(name);
			// slf4j
			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}


	private static class Log4jAdapter {

		public static Log createLog(String name) {
			return new Log4jLog(name);
		}
	}


	private static class Slf4jAdapter {

		public static Log createLocationAwareLog(String name) {
			Logger logger = LoggerFactory.getLogger(name);
			return (logger instanceof LocationAwareLogger ?
					new Slf4jLocationAwareLog((LocationAwareLogger) logger) : new Slf4jLog<>(logger));
		}

		public static Log createLog(String name) {
			return new Slf4jLog<>(LoggerFactory.getLogger(name));
		}
	}


	private static class JavaUtilAdapter {

		public static Log createLog(String name) {
			return new JavaUtilLog(name);
		}
	}


	@SuppressWarnings("serial")
	private static class Log4jLog implements Log, Serializable {
        // 先不关注
        ...
	}


	@SuppressWarnings("serial")
	private static class Slf4jLog<T extends Logger> implements Log, Serializable {
        // 先不关注
        ...
	}


	@SuppressWarnings("serial")
	private static class Slf4jLocationAwareLog extends Slf4jLog<LocationAwareLogger> implements Serializable {
        // 先不关注
        ...
	}


	@SuppressWarnings("serial")
	private static class JavaUtilLog implements Log, Serializable {
        // 先不关注
        ...
	}


	@SuppressWarnings("serial")
	private static class LocationResolvingLogRecord extends LogRecord {
        // 先不关注
        ...
	}

}

去掉一些不是很重要的代码,先看下 createLog 方法。

	public static Log createLog(String name) {
		switch (logApi) {
			// log4j2
			case LOG4J:
				return Log4jAdapter.createLog(name);
			// slf4j_full
			case SLF4J_LAL:
				return Slf4jAdapter.createLocationAwareLog(name);
			// slf4j
			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);
		}
	}

这里主要是根据 logApi 这个变量去判断的,那我们看下这个变量。

可以看到这个变量是在类初始化的时候去赋值的。

	// log4j2 jar包中的一个类
	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.
				/*
				 * spring团队其实更倾向使用 log4j2,但如果你非要用slf4j,那就必须满足两个条件
				 * 1. 必须要有 log4j2 - slf4j 的桥接器
				 * 2. slf4j 的依赖
				 */
				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;
		}
	}

可以看到主要是判断一些相关的类是否存在去实例化 logApi 对象。isPresent 方法就是通过 class.forName 去判断类是否存在。

流程图大概如下:

可以看出 spring 的开发团队还是偏向 log4j2 的,原因他也在注释里写了

the latter does not have location awareness support.

slf4j 没有位置感应支持。

位置感应支持这个暂时先不深究,因为偏向于 log4j2,所以只有当你项目只要存在log4j2的依赖,就会优先考虑log4j2,除非你的项目同时存在 slf4j 和 log4j-slf4j 桥接器的依赖后,这就表明你就是想用 slf4j,这时候才会使用 slf4j。或者我本身就没有 log4j2 的依赖,这种就是 slf4j 优先,没有的话使用 JUL。