倒数第二全的常见日志框架分析

听过太多的日志框架了,比如log4j、jcl、jcl、slf4j、logback……

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

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

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

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

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

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

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-api和log4j-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
依赖:


log4j
log4j
1.2.17

配置文件:
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
依赖:


commons-logging
commons-logging
1.2

测试类:
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作为底层的日志框架,怎么办呢?只需要加一个依赖即可,如下:

依赖:


commons-logging
commons-logging
1.2


log4j
log4j
1.2.17

输出:
0 [main] INFO cn.eagleli.log.jcl.Main - hello, world!
从结果可以看出,底层日志框架已经变了,同样发现,我们的代码没有任何改动,只是加了一个依赖,由此可以看出接口的重要性。

slf4j
依赖:


org.slf4j
slf4j-api
1.7.30


ch.qos.logback
logback-classic
1.2.3

测试类:
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作为底层实现框架,怎么办呢?只需换一个依赖即可,如下:

依赖:


org.slf4j
slf4j-api
1.7.30


org.slf4j
slf4j-jcl
1.7.30

输出:
八月 11, 2021 11:18:27 下午 org.slf4j.impl.JCLLoggerAdapter info
信息: hello, world!
从上面结果可以看出,底层已经切到jcl了,而jcl默认采用的是jdk日志框架

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

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

log4j2
依赖:


org.apache.logging.log4j
log4j-api
2.14.1


org.apache.logging.log4j
log4j-core
2.14.1

测试类:
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作为底层的实现,怎么办呢?只需加一个依赖即可,如下:

依赖:


org.apache.logging.log4j
log4j-api
2.14.1


org.apache.logging.log4j
log4j-to-slf4j
2.14.1

输出:
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使用的jul的Logger,而Log4JLogger使用是log4j的Logger。

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下

从上面的图中,我们可以看出logback和slf4j-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: \n");
                for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
                    sb.append("Factory: ").append(entry.getValue().getClass().getName());
                    sb.append(", Weighting: ").append(entry.getKey()).append('\n');
                }
                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 serviceLoader = ServiceLoader.load(Provider.class, classLoader);,利用SPI获取一个特定的Provider
mosfet驱动芯片https://www.zg886.cn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值