springboot换log4j2写日志源码分析

     上一篇文章(springboot默认日志框架选择解析)从源码阶段分析了springboot的默认日志框架为logback,spring-boot包中,logging.logback下面有默认的日志配置xml;若使用logback做为日志框架,则添加相关配置即可;那么怎么样用log4j2来作为springboot的日志框架呢?为什么要选择log4j2作为日志框架,搜索一下就能知道;

      还是要从LoggingSystem入手,如下代码所示,我们需要在程序启动前添加参数(第二代码块);这样springboot启动时,就会使用配置的Log4J2LoggingSystem;当然,还需要在grdle中引入log4j2相关的包,否则会报找不到Class的异常;compile "org.apache.logging.log4j:log4j-api:2.14.0";compile "org.apache.logging.log4j:log4j-core:2.14.0" 引用加入gradle

 

public static LoggingSystem get(ClassLoader classLoader) {
		String loggingSystemClassName = System.getProperty(SYSTEM_PROPERTY);
		if (StringUtils.hasLength(loggingSystemClassName)) {
			if (NONE.equals(loggingSystemClassName)) {
				return new NoOpLoggingSystem();
			}
			return get(classLoader, loggingSystemClassName);
		}
		LoggingSystem loggingSystem = SYSTEM_FACTORY.getLoggingSystem(classLoader);
		Assert.state(loggingSystem != null, "No suitable logging system located");
		return loggingSystem;
	}

  

  1,启动函数添加system property     

 public static void main(String[] args) {
        System.setProperty("org.springframework.boot.logging.LoggingSystem", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
        SpringApplication.run(Bootstrap.class, args);
    }

  

  2,添加log4j2.xml到resources目录下,log4j2的名称不能改:注意设置logPath,logFile

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Properties>
        <property name="logPath">/Users/dengyouhua/logs</property>
        <property name="logFile">com-fenghua-util</property>
    </Properties>
    <Appenders>
        <Console name="console_log" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <RollingFile name="file_log" fileName="${logPath}/${logFile}.log" append="true"
              filePattern="${logPath}/${logFile}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="file_log"/>
            <AppenderRef ref="console_log"/>
        </Root>
    </Loggers>
</Configuration>

  

  3,gradle中需要排掉以下包,该方式是gradle直接全局排掉包;单独去排包,会漏排,特别是在使用spring家族的包时

configurations {
    compile.exclude module: "spring-boot-starter-logging"
}

 

      到此,你的日志就是真的在使用log4j2打印了。

 

      接下来我们就从源码层面来解释一下,为啥需要上面那么多步; 第一步中,添加系统参数,这个在上一篇以及开头中讲到,要手动设置springboot的日志框架;这样才不会走默认的logback的选项; 第二步添加log4j2.xml的配置,这个也没什么好说的,用来配置日志打印的相关参数。

      第三步为什么需要排掉包,org.apache.commons.logging.LogAdapter 这个类决定了必须把spring-boot-starter-logging包给排掉;因为该包会自动引入logback,slf4j相关的包;

重点就是LogAdapter的表态构造里面的逻辑的; 若不排掉上面包,则isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)必为真,那么logApi,则为LogApi.SLF4J_LAL

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:
				return JavaUtilAdapter.createLog(name);
		}
	}

  

  这样在createLog时,会调用Slf4jAdapter.createLocationAwareLog,该类是LogAdapter的一个内部类,如下所示:

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));
		}
	}

 最终会调用到LoggerFactory中的findPossibleStaticLoggerBinderPathSet方法,该方法加载了一个指定类org/slf4j/impl/StaticLoggerBinder.class;如下所示:

   private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

  StaticLoggerBinder类中指定了LoggerContext为ch.qos.logback.classic.LoggerContext,所以还是使用的logback为写日志的组件;该类位于org.slf4j.impl,logback-classic包中;

所以第三步排掉包为必须的步骤,否则还是走默认的logback日志框架;

 

      总结:

             springboot 设置log4j2为日志框架,需要以上三步即可完成;简单总结如下:

1,启动函数添加System.setProperty("org.springframework.boot.logging.LoggingSystem", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");

2,添加log4j2.xml的配置

3,添加log4j2的compile "org.apache.logging.log4j:log4j-api:2.14.0";compile "org.apache.logging.log4j:log4j-core:2.14.0"依赖包,并全局排除spring-boot-starter-logging

 

posted @ 2021-03-13 13:42  dengyouhua  阅读(476)  评论(0编辑  收藏  举报