java log4j 工作原理,java日志原理

前言

映像中接触或使用到的java日志系统特别多,只是停留在配置即开箱使用的阶段,心里其实一直在疑惑,这么多日志框架到底它们之间有什么区别?总共又有多少中日志框架?那么对日志框架又是怎么知道确定使用哪一种的?所以下定决定要揭秘java日志框架

日志框架: 是一种日志接口,不负责具体的日志输出形式(有点类似于JDBC),可以灵活的切换日志输出形式。常见的日志框架有slf4j、jcl,只提供Logger、LoggerFactory等接口

1、jcl 之前叫Jakarta Commons Logging,简称JCL,是Apache提供的一个通用日志API,可以让应用程序不再依赖于具体的日志实现工具。Apache commons-logging是JCL的标准实现。

commons-logging包中对其它一些日志工具,包括Log4J、Avalon LogKit、JUL等,进行了简单的包装,可以让应用程序在运行时,直接将JCL API打点的日志适配到对应的日志实现工具中。

2、SLF4J 全称 Simple Logging Facade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过Facade Pattern门面模式对外提供一些Java Logging API。这些对外提供的核心API其实就是一些接口以及一个LoggerFactory的工厂类。

日志系统:是应用实际使用的日志工具,主要有log4j,jul(java.util.logging),logback等。一般在程序中应该避免直接使用,可以保证程序具有一定的灵活性。

f7f8849e4233

image.png

Logger:日志输出实例,包含Appender和Layout

Appender:日志输出目标,如控制台,文件,数据库等。多个Appender可以被关联到任何Logger上,所以可以到多个输出文件上记录相同的信息。

Layout:定义日志输出格式:时间戳、线程名称、日志级别、日志内容、对应输出该日志的类、对应输出该日志的方法、行号及MDC信息

f7f8849e4233

常用的日志组件.png

slf4j 作用及其实现原理

我们必须清楚地知道一点:slf4j只是一个日志标准,并不是日志系统的具体实现。理解这句话非常重要,slf4j只做两件事情:

提供日志接口

提供获取具体日志对象的方法

slf4j-simple、logback都是slf4j的具体实现,log4j并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12来实现slf4j。

为了更理解slf4j,我们先看例子,再读源码,相信读者朋友会对slf4j有更深刻的认识。

slf4j应用举例

pom.xml引入jar包

org.slf4j

slf4j-api

1.7.25

ch.qos.logback

logback-classic

1.2.3

org.slf4j

slf4j-simple

1.7.25

log4j

log4j

1.2.17

org.slf4j

slf4j-log4j12

1.7.21

简单的java使用代码

@Test

public void testLog()

{

Logger logger = LoggerFactory.getLogger(AppTest.class);

logger.info("log。。。");

}

3.接着我们首先把上面pom.xml的日志框架实现的jar引用注释掉,只留下slf4j-api包,即不引入任何slf4j的实现类,运行testLog方法,我们看一下控制台的输出为:

f7f8849e4233

没有任何日志实现类.png

看到没有任何日志的输出,这验证了我们的观点:slf4j不提供日志的具体实现,只有slf4j是无法打印日志的。

接着打开logback-classic的注释,运行testLog方法,我们看一下控制台的输出为

f7f8849e4233

打开logback-classic的注释.png

看到我们只要引入了一个slf4j的具体实现类,即可使用该日志框架输出日志。

最后做一个测验,我们把所有日志打开,引入logback-classic、slf4j-simple、log4j,运行Test方法,控制台输出为:

f7f8849e4233

image.png

和上面的差别是,可以输出日志,但是会输出一些告警日志,提示我们同时引入了多个slf4j的实现,然后选择其中的一个作为我们使用的日志系统。

从例子我们可以得出一个重要的结论,即slf4j的作用:只要所有代码都使用门面对象slf4j,我们就不需要关心其具体实现,最终所有地方使用一种具体实现即可,更换、维护都非常方便。

slf4j实现原理

上面看了slf4j的示例,下面研究一下slf4j的实现,我们只关注重点代码。

slf4j的用法就是常年不变的一句Logger logger = LoggerFactory.getLogger(AppTest.class);",可见这里就是通过LoggerFactory去拿slf4j提供的一个Logger接口的具体实现而已,LoggerFactory的getLogger的方法实现为:

public static Logger getLogger(Class> clazz) {

Logger logger = getLogger(clazz.getName());

if (DETECT_LOGGER_NAME_MISMATCH) {

Class> autoComputedCallingClass = Util.getCallingClass();

if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {

Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),

autoComputedCallingClass.getName()));

Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");

}

}

return logger;

}

从第2行开始跟代码,一直跟到LoggerFactory的bind()方法:

org.slf4j.LoggerFactory#getILoggerFactory ==> org.slf4j.LoggerFactory#performInitialization ==> org.slf4j.LoggerFactory#bind

private final static void bind() {

try {

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

fixSubstituteLoggers();

replayEvents();

// release all resources in SUBST_FACTORY

SUBST_FACTORY.clear();

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

}

}

现在代码还在org.slf4j.LoggerFactory`类里`,重点关注org.slf4j.LoggerFactory#findPossibleStaticLoggerBinderPathSet``

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;

}

这个地方重点其实就是12行paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);的代码,getLogger的时候会去classpath下找STATIC_LOGGER_BINDER_PATH,STATIC_LOGGER_BINDER_PATH值为"org/slf4j/impl/StaticLoggerBinder.class",即所有slf4j的实现,在提供的jar包路径下,一定是有"org/slf4j/impl/StaticLoggerBinder.class"存在的,我们可以看一下:

f7f8849e4233

logback-classic.png

f7f8849e4233

slf4j-log4j.png

f7f8849e4233

slf4j-simple.png

我们不能避免在系统中同时引入多个slf4j的实现,所以接收的地方是一个Set。大家应该注意到,上部分在演示同时引入logback、slf4j-simple、log4j的时候会有警告:

f7f8849e4233

image.png

这就是因为有三个"org/slf4j/impl/StaticLoggerBinder.class"存在的原因,此时reportMultipleBindingAmbiguity方法控制台输出语句:

private static void reportMultipleBindingAmbiguity(Set binderPathSet) {

if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {

Util.report("Class path contains multiple SLF4J bindings.");

for (URL path : binderPathSet) {

Util.report("Found binding in [" + path + "]");

}

Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");

}

}

那网友朋友可能会问,同时存在三个"org/slf4j/impl/StaticLoggerBinder.class"怎么办?首先确定的是这不会导致启动报错,其次在这种情况下编译期间,编译器会选择其中一个StaticLoggerBinder.class进行绑定(StaticLoggerBinder.getSingleton();选择了对应的StaticLoggerBinder),这个地方sfl4j也在reportActualBinding方法中报告了绑定的是哪个日志框架:

private static void reportActualBinding(Set binderPathSet) {

// binderPathSet can be null under Android

if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {

Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");

}

}

对照上面的截图,看最后一行,确实是"Actual binding is of type..."这句。

最后StaticLoggerBinder就比较简单了,不同的StaticLoggerBinder其getLoggerFactory实现不同,拿到ILoggerFactory之后调用一下getLogger即拿到了具体的Logger,可以使用Logger进行日志输出。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值