springboot换log4j2写日志源码分析

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



    
        /Users/dengyouhua/logs
        com-fenghua-util
    
    
        
            
        
        
            
            
                
                
            
        
    
    
        
            
            
        
    

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 findPossibleStaticLoggerBinderPathSet() {
     // use Set instead of list in order to deal with bug #138
     // LinkedHashSet appropriate here because it preserves insertion order
     // during iteration
     Set staticLoggerBinderPathSet = new LinkedHashSet();
     try {
         ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
         Enumeration 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

你可能感兴趣的:(springboot换log4j2写日志源码分析)