zoukankan      html  css  js  c++  java
  • 倒数第二全的常见日志框架分析

    起由

    听过太多的日志框架了,比如log4jjcljclslf4jlogback……

    之前对这些概念就很乱,不知道它们都是具体干什么的,而且也不知道想要使用一个日志框架时,要怎么引入相关的依赖

    本文就搜集了常见的日志框架,并梳理了其用法以及相关原理。

    日志的发展历程

    a. 从最早期开始,大家都是使用System.outSystem.err来打印日志;不灵活也不可以配置;要么全部打印,要么全部不打印;没有一个统一的日志级别

    b. 后来Log4j就出现了,它是Ceki Gülcü这个大佬开发的,后来Log4j成为了Apache基金会项目中的一员

    c. 后来Java也推出了自己的日志框架JUL(Java Util Logging),在package java.util.logging

    d. Apache又推出了日志接口Jakarta Commons Logging,也就是日志抽象层,你就可以很方便的在Log4jJUL之间做切换

    e. Ceki Gülcü觉得觉得JCL不好,开发了一套新的日志门面Slf4j(Simple Logging Facade for Java)、它的实现Logback以及一些桥接包:

    jcl-over-slf4j.jar :jcl ——> slf4j
    slf4j-jcl.jar :slf4j ——> jcl
    log4j-over-slf4j :log4j ——> slf4j
    slf4j-log4j12.jar :slf4j ——> log4j
    jul-to-slf4j :jul ——> slf4j
    slf4j-jdk14.jar :slf4j ——> jul

    f. 后来Apache直接推出新项目,不是Log4j1.x升级,而是新项目Log4j2,因为Log4j2是完全不兼容Log4j1.x的,它也搞了分离的设计,分化成log4j-apilog4j-core,这个log4j-api也是日志接口,log4j-core是日志实现,它也出了很多桥接包:

    log4j-jcl :jcl ——> log4j2
    log4j-1.2-api :log4j ——> log4j2
    log4j-slf4j-impl :slf4j ——> log4j2
    log4j-jul :jul ——> log4j2
    log4j-to-slf4j :log4j2 ——> slf4j

    基本用法

    log4j

    依赖:
        <dependencies>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
        </dependencies>
    
    配置文件:
    log4j.properties
    # Set root logger level to DEBUG and its only appender to A1.
    log4j.rootLogger=DEBUG, A1
    # A1 is set to be a ConsoleAppender.
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    # A1 uses PatternLayout.
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
     
    测试类:
    import org.apache.log4j.Logger;
    public class Main {
        public static void main(String[] args) {
            Logger logger = Logger.getLogger(Main.class);
            logger.info("hello, world!");
        }
    }
     
    输出:
    0    [main] INFO  cn.eagleli.log.log4j.Main  - hello, world!
    

    jul

    测试类:
    import java.util.logging.Logger;
    public class Main {
        public static void main(String[] args) {
            Logger logger = Logger.getLogger(Main.class.getName());
            logger.info("hello, world!");
        }
    }
     
    输出:
    八月 11, 2021 11:06:19 下午 cn.eagleli.log.jul.Main main
    信息: hello, world!
    

    jcl

    依赖:
        <dependencies>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
        </dependencies>
    
    测试类:
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    public class Main {
        public static void main(String[] args) {
            Log log = LogFactory.getLog(Main.class);
            log.info("hello, world!");
        }
    }
    
    输出:
    八月 11, 2021 11:08:25 下午 cn.eagleli.log.jcl.Main main
    信息: hello, world!
    

    从上面输出结果可以看出,默认会使用jul作为底层的日志框架

    如果我们想换成log4j作为底层的日志框架,怎么办呢?只需要加一个依赖即可,如下:

    依赖:
        <dependencies>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
        </dependencies>
    
    输出:
    0    [main] INFO  cn.eagleli.log.jcl.Main  - hello, world!
    

    从结果可以看出,底层日志框架已经变了,同样发现,我们的代码没有任何改动,只是加了一个依赖,由此可以看出接口的重要性。

    slf4j

    依赖:
        <dependencies>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.30</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
     
    测试类:
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class Main {
        public static void main(String[] args) {
            Logger logger = LoggerFactory.getLogger(Main.class);
            logger.info("hello, world!");
        }
    }
     
    输出:
    23:14:30.893 [main] INFO cn.eagleli.log.slf4j.Main - hello, world!
    

    上面底层日志框架使用的是logback

    那如果我们想切换成jcl作为底层实现框架,怎么办呢?只需换一个依赖即可,如下:

    依赖:
        <dependencies>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.30</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-jcl</artifactId>
                <version>1.7.30</version>
            </dependency>
        </dependencies>
    
    输出:
    八月 11, 2021 11:18:27 下午 org.slf4j.impl.JCLLoggerAdapter info
    信息: hello, world!
    

    从上面结果可以看出,底层已经切到jcl了,而jcl默认采用的是jdk日志框架

    cl-over-slf4jslf4j-jcl是不能同时使用的

    因为前一个使用jcl API桥接到slf4j,后一个是使用slf4j API桥接到jcl,如果同时引用会导致循环调用,进而导致栈溢出

    log4j2

    依赖:
        <dependencies>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>2.14.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.14.1</version>
            </dependency>
        </dependencies>
     
    测试类:
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    public class Main {
        public static void main(String[] args) {
            Logger logger = LogManager.getLogger(Main.class);
            logger.error("hello, world!");
        }
    }
    
    输出:
    23:22:12.148 [main] ERROR cn.eagleli.log.log4j2.Main - hello, world!
    

    以上我们采用的log4j2作为底层的实现,我们想要用slf4j作为底层的实现,怎么办呢?只需加一个依赖即可,如下:

    依赖:
        <dependencies>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>2.14.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-to-slf4j</artifactId>
                <version>2.14.1</version>
            </dependency>
        </dependencies>
    
    输出:
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    

    从结果看出,我们底层日志框架已经切换了,因为没有任何slf4j的实现类框架,所以没有输出日志。

    怎么做到无缝衔接的

    jcl 原理

    jcl无缝切换的核心代码如下:

    public abstract class LogFactory {
        public static Log getLog(Class clazz)
            throws LogConfigurationException {
            return (getFactory().getInstance(clazz));
        }
    }
    
    public class LogFactoryImpl extends LogFactory {
        public Log getInstance(Class clazz) throws LogConfigurationException {
            return (getInstance(clazz.getName()));
        }
    }
    

    首先获得一个LogFactory,它是可以自定义的,再从LogFactory中获得一个Log类,Log类也是可以自定义的

    LogFactory.getFactory()用来获取一个LogFactory,核心逻辑如下:

    a.从系统变量获取
    public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
    String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
    
    b.从指定文件里获取
    protected static final String SERVICE_ID =
        "META-INF/services/org.apache.commons.logging.LogFactory";
    final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
     
    c.从指定properties文件获取
    public static final String FACTORY_PROPERTIES = "commons-logging.properties"
    Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
    public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
    String factoryClass = props.getProperty(FACTORY_PROPERTY);
    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
     
    d.默认的LogFactory实现
    public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"
    factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
    

    LogFactoryImpl.discoverLogImplementation()用来获取一个Log,核心逻辑如下:

    a.从LogFactory的attributes变量中获取
    public static final String LOG_PROPERTY = "org.apache.commons.logging.Log"
    String specifiedClass = (String) getAttribute(LOG_PROPERTY);
    public Object getAttribute(String name) {
        return attributes.get(name);
    }
    
    b.从系统变量中获取
    public static final String LOG_PROPERTY = "org.apache.commons.logging.Log";
    specifiedClass = getSystemProperty(LOG_PROPERTY, null);
    
    c.默认的数组列表
    private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
    private static final String[] classesToDiscover = {
            LOGGING_IMPL_LOG4J_LOGGER,
            "org.apache.commons.logging.impl.Jdk14Logger",
            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
            "org.apache.commons.logging.impl.SimpleLog"
    };
    for(int i=0; i<classesToDiscover.length && result == null; ++i) {
        result = createLogFromClass(classesToDiscover[i], logCategory, true);
    }
    
    // 先类加载,然后利用反射创建实例
    public class LogFactoryImpl extends LogFactory {
        private Log createLogFromClass(String logAdapterClassName,
                                       String logCategory,
                                       boolean affectState) 
                throws LogConfigurationException { 
                    Class c = null;
                    try {
                        c = Class.forName(logAdapterClassName, true, currentCL);
                    } catch (ClassNotFoundException originalClassNotFoundException) {
                    }
                    constructor = c.getConstructor(logConstructorSignature);
                    Object o = constructor.newInstance(params);
        }
    }
    

    看一两个适配的Logger

    a.
    import java.util.logging.Logger;
    public class Jdk14Logger implements Log, Serializable {
        public Jdk14Logger(String name) {
            this.name = name;
            logger = getLogger();
        }
        public Logger getLogger() {
            if (logger == null) {
                logger = Logger.getLogger(name);
            }
            return logger;
        }
        public void info(Object message) {
            log(Level.INFO, String.valueOf(message), null);
        }
       private void log( Level level, String msg, Throwable ex ) {
            Logger logger = getLogger();
            if (logger.isLoggable(level)) {
              // ...
            }
        }
    }
    
    b.
    import org.apache.log4j.Logger;
    public class Log4JLogger implements Log, Serializable {
        public Log4JLogger(String name) {
            this.name = name;
            this.logger = getLogger();
        }
        public Logger getLogger() {
            Logger result = logger;
            if (result == null) {
                synchronized(this) {
                    result = logger;
                    if (result == null) {
                        logger = result = Logger.getLogger(name);
                    }
                }
            }
            return result;
        }
        public void info(Object message) {
            getLogger().log(FQCN, Priority.INFO, message, null );
        }
    }
    

    从上面的代码可以看出,这是典型的适配器模式,Jdk14Logger使用的julLogger,而Log4JLogger使用是log4jLogger

    slf4j 原理

    slf4j无缝切换的核心代码如下:

    public final class LoggerFactory {
        public static Logger getLogger(String name) {
            ILoggerFactory iLoggerFactory = getILoggerFactory();
            return iLoggerFactory.getLogger(name);
        }
    
        public static ILoggerFactory getILoggerFactory() {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                synchronized (LoggerFactory.class) {
                    if (INITIALIZATION_STATE == UNINITIALIZED) {
                        INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                        performInitialization();
                    }
                }
            }
            switch (INITIALIZATION_STATE) {
            case SUCCESSFUL_INITIALIZATION:
                return StaticLoggerBinder.getSingleton().getLoggerFactory();
            case NOP_FALLBACK_INITIALIZATION:
                return NOP_FALLBACK_FACTORY;
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
            case ONGOING_INITIALIZATION:
                // support re-entrant behavior.
                // See also http://jira.qos.ch/browse/SLF4J-97
                return SUBST_FACTORY;
            }
            throw new IllegalStateException("Unreachable code");
        }
    
        private final static void performInitialization() {
            bind();
            if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
                versionSanityCheck();
            }
        }
    
        private final static void bind() {
            try {
                Set<URL> staticLoggerBinderPathSet = null;
                // skip check under android, see also
                // http://jira.qos.ch/browse/SLF4J-328
                if (!isAndroid()) {
                    staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                    reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
                }
                // the next line does the binding
                StaticLoggerBinder.getSingleton();
                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                reportActualBinding(staticLoggerBinderPathSet);
            } catch (NoClassDefFoundError ncde) {
                String msg = ncde.getMessage();
                if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                    INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                    Util.report("Failed to load class "org.slf4j.impl.StaticLoggerBinder".");
                    Util.report("Defaulting to no-operation (NOP) logger implementation");
                    Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
                } else {
                    failedBinding(ncde);
                    throw ncde;
                }
            } catch (java.lang.NoSuchMethodError nsme) {
                String msg = nsme.getMessage();
                if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                    INITIALIZATION_STATE = FAILED_INITIALIZATION;
                    Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                    Util.report("Your binding is version 1.5.5 or earlier.");
                    Util.report("Upgrade your binding to version 1.6.x.");
                }
                throw nsme;
            } catch (Exception e) {
                failedBinding(e);
                throw new IllegalStateException("Unexpected initialization failure", e);
            } finally {
                postBindCleanUp();
            }
        }
    }
    

    从上面的代码一路走过来,其实主要就是在LoggerFactory.bind()方法里面,简单粗暴,直接调用StaticLoggerBinder.getSingleton();org.slf4j.impl.StaticLoggerBinder这个类有没有在classpath

    从上面的图中,我们可以看出logbackslf4j-jcl都有这个类,具体的StaticLoggerBinder代码就不分析了,大家可以自己看一下。

    log4j2 原理

    log4j2无缝切换的核心代码如下:

    public class LogManager {
        public static Logger getLogger(final Class<?> clazz) {
            final Class<?> cls = callerClass(clazz);
            return getContext(cls.getClassLoader(), false).getLogger(cls);
        }
    
        public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {
            try {
                return factory.getContext(FQCN, loader, null, currentContext);
            } catch (final IllegalStateException ex) {
                LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
                return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext);
            }
        }
    
        /**
         * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be
         * extended to allow multiple implementations to be used.
         */
        static {
            // Shortcut binding to force a specific logging implementation.
            final PropertiesUtil managerProps = PropertiesUtil.getProperties();
            final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
            if (factoryClassName != null) {
                try {
                    factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
                } catch (final ClassNotFoundException cnfe) {
                    LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
                } catch (final Exception ex) {
                    LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
                }
            }
    
            if (factory == null) {
                final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
                // note that the following initial call to ProviderUtil may block until a Provider has been installed when
                // running in an OSGi environment
                if (ProviderUtil.hasProviders()) {
                    for (final Provider provider : ProviderUtil.getProviders()) {
                        final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
                        if (factoryClass != null) {
                            try {
                                factories.put(provider.getPriority(), factoryClass.newInstance());
                            } catch (final Exception e) {
                                LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
                                        .getUrl(), e);
                            }
                        }
                    }
    
                    if (factories.isEmpty()) {
                        LOGGER.error("Log4j2 could not find a logging implementation. "
                                + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
                        factory = new SimpleLoggerContextFactory();
                    } else if (factories.size() == 1) {
                        factory = factories.get(factories.lastKey());
                    } else {
                        final StringBuilder sb = new StringBuilder("Multiple logging implementations found: 
    ");
                        for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
                            sb.append("Factory: ").append(entry.getValue().getClass().getName());
                            sb.append(", Weighting: ").append(entry.getKey()).append('
    ');
                        }
                        factory = factories.get(factories.lastKey());
                        sb.append("Using factory: ").append(factory.getClass().getName());
                        LOGGER.warn(sb.toString());
    
                    }
                } else {
                    LOGGER.error("Log4j2 could not find a logging implementation. "
                            + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
                    factory = new SimpleLoggerContextFactory();
                }
                LogManagerStatus.setInitialized(true);
            }
        }
    }
     
    
    public final class ProviderUtil {
    
        public static Iterable<Provider> getProviders() {
            lazyInit();
            return PROVIDERS;
        }
    
        protected static void lazyInit() {
            // noinspection DoubleCheckedLocking
            if (instance == null) {
                try {
                    STARTUP_LOCK.lockInterruptibly();
                    try {
                        if (instance == null) {
                            instance = new ProviderUtil();
                        }
                    } finally {
                        STARTUP_LOCK.unlock();
                    }
                } catch (final InterruptedException e) {
                    LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    
        private ProviderUtil() {
            for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
                try {
                    loadProviders(classLoader);
                } catch (final Throwable ex) {
                    LOGGER.debug("Unable to retrieve provider from ClassLoader {}", classLoader, ex);
                }
            }
            for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
                loadProvider(resource.getUrl(), resource.getClassLoader());
            }
        }
    
    	protected static void loadProviders(final ClassLoader classLoader) {
    		final ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class, classLoader);
    		for (final Provider provider : serviceLoader) {
    			if (validVersion(provider.getVersions()) && !PROVIDERS.contains(provider)) {
    				PROVIDERS.add(provider);
    			}
    		}
    	}
    }
    

    其实上面的核心代码就是final ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class, classLoader);,利用SPI获取一个特定的Provider

    从上图可以看出,log4j-corelog4j-to-slf4j都有自己的Provider实现类,具体的代码就不分析了,大家可以自己看一下。

    最后说说Spring中的日志框架

    Spring中的日志框架采用的是spring-jcl,我们就来看看它,如下图:

    无缝切换的核心代码如下:

    public abstract class LogFactory {
    	public static Log getLog(Class<?> clazz) {
    		return getLog(clazz.getName());
    	}
    
    	public static Log getLog(String name) {
    		return LogAdapter.createLog(name);
    	}
    }
    
    final class LogAdapter {
    	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 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 static boolean isPresent(String className) {
    		try {
    			Class.forName(className, false, LogAdapter.class.getClassLoader());
    			return true;
    		}
    		catch (ClassNotFoundException ex) {
    			return false;
    		}
    	}
    }
    

    从上面代码可以看出,spring-jcl就是根据Class.forName()来判断各个日志框架的核心类是否存在,然后创建对应的Log适配类。

    我们的项目一般都会依赖spring-boot-starter-web,它又会依赖spring-boot-starter,又会依赖spring-boot-starter-logging

    我们就来看看spring-boot-starter-logging-2.5.1,如下图:

    我们发现它没有任何代码,我们再来看看它pom文件里面的依赖:

    <dependencies>
        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.2.3</version>
          <scope>compile</scope>
        </dependency>
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-to-slf4j</artifactId>
          <version>2.14.1</version>
          <scope>compile</scope>
        </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jul-to-slf4j</artifactId>
          <version>1.7.30</version>
          <scope>compile</scope>
        </dependency>
    </dependencies>
    

    从这个依赖我们可以看出,它引用了logback-classic,也就是slf4j的实现框架

    由于log4j-to-slf4jjul-to-slf4j依赖的存在,不管你使用的是log4j2还是jul,最终都会切到slf4j

    所以我们可以说spring boot默认使用的日志框架就是logback

    总结

    说了这么多,我们总结一下这些日志接口框架是怎么做到无缝切换的:
    jcl/spring-jcl:通过类加载的机制:class.forname()
    slf4j:通过判断类是否在classpath
    log4j2:通过SPI的形式

    参考

    https://segmentfault.com/a/1190000021121882
    https://logging.apache.org/log4j/1.2/manual.html
    https://commons.apache.org/proper/commons-logging/guide.html
    https://blog.csdn.net/chengmaoning/article/details/78072670
    http://www.slf4j.org/legacy.html
    https://logging.apache.org/log4j/2.x/runtime-dependencies.html

  • 相关阅读:
    Linux curl命令详解
    php技能树---大神的进阶之路
    PHP Socket 编程之9个主要函数的使用之测试案例
    史上最全的PHP正则表达式
    php 简单使用redis 队列示例
    PHP中使用 Memcached 的测试案例
    机动车驾驶(1)--- 禁令标志汇总 by John
    闵可夫斯基和(Mincowsky sum)
    二维平面上判断点是否在三角形内
    计算任意多边形的面积
  • 原文地址:https://www.cnblogs.com/eaglelihh/p/15003410.html
Copyright © 2011-2022 走看看