前言

  项目中的日志系统使用的是slf4j + logback。slf4j作为一个简单日志门面,为各种loging APIs(像java.util.logging, logback, log4j)提供一个简单统一的接口,有利于维护和各个类的日志处理方式统一。Logback作为一个具体的日志组件,完成具体的日志操作。

本博客旨在带领大家理清楚slf4j的绑定(logback如何绑定到slf4j的),logback是何时加载配置文件的。至于具体的配置则需要大家自己去查阅资料了。

  路漫漫其修远兮,吾将上下而求索!

  github:https://github.com/youzhibing

  码云(gitee):https://gitee.com/youzhibing

slf4j + logback的使用

  使用非常简单,引入依赖的jar即可,如下图

  pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.yzb</groupId>
<artifactId>mylog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>mylog</name>
<url>http://maven.apache.org</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
</dependencies>
</dependencyManagement> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency> <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency> </dependencies>
</project>

  测试代码

public class LogTest
{
private static Logger LOGGER = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args)
{
LOGGER.info("......info");
LOGGER.debug("......debug");
LOGGER.warn("......warn");
LOGGER.error("......error");
LOGGER.trace("......trace");
} }

 

  控制台输出结果

15:24:48.840 [main] INFO com.huawei.log.LogTest - ......info
15:24:48.842 [main] DEBUG com.huawei.log.LogTest - ......debug
15:24:48.842 [main] WARN com.huawei.log.LogTest - ......warn
15:24:48.842 [main] ERROR com.huawei.log.LogTest - ......error

  使用真的简单,也正是这种简单让我产生了一些疑问。

    问题1:大家对spring使用的比较多的话,就知道将某个实现类注给其接口的时候,都是需要明确指出的,无论是通过配置文件的方式还是注解的方式。如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd"> <!--
applicationContext.xml实际是不会存在
配置文件会报错,因为缺少spring的jar包,这里只是模拟spring的依赖注入
更详细代码请看附件
--> <bean id="daoImpl" class="com.yzb.dao.impl.DaoImpl" /> <bean id="studentService" class="com.yzb.service.StudentService">
<!-- dao对应private IDao dao; 将实现daoImpl绑定到接口dao -->
<property name="dao" ref="daoImpl"/>
</bean> </beans>

    可slf4j + logback没有其他任何的配置,工程就能跑起来,能够打印各种类型的日志,这是怎么实现的呢?

    问题2:我们加上logback的配置文件,仅仅在src/main/resources(相当于classpath)下加logback.xml,发现生成了日志文件(若没有设置日志文件路径,那么日志文件生成在当前工程下),并且控制台输出结果如下:

2017-05-13 15:57:27|INFO|......info
2017-05-13 15:57:27|WARN|......warn
2017-05-13 15:57:27|ERROR|......error

    仅仅在src/main/resources下配置logback.xml,就能达到这种效果,logback.xml是什么时候加载的呢?

源码解析

  从LogTest.java开始

public class LogTest
{
private static Logger LOGGER = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args)
{
// 下面5个方法相当于接口调用实现
LOGGER.info("......info");
LOGGER.debug("......debug");
LOGGER.warn("......warn");
LOGGER.error("......error");
LOGGER.trace("......trace");
} }

  代码非常简单,很明显我们只需要看private static Logger LOGGER = LoggerFactory.getLogger(LogTest.class)的实现。

  跟进getLogger方法 2步,来到

    /**
* Return a logger named according to the name parameter using the statically
* bound {@link ILoggerFactory} instance.
*
* @param name The name of the logger.
* @return logger
*/
public static Logger getLogger(String name) {
// 获取日志工厂
ILoggerFactory iLoggerFactory = getILoggerFactory();
// 返回日志实例
return iLoggerFactory.getLogger(name);
}

  我们跟进getILoggerFactory方法

  /**
* Return the {@link ILoggerFactory} instance in use.
* <p/>
* <p/>
* ILoggerFactory instance is bound with this class at compile time. // 编译时绑定工厂实例
*
* @return the ILoggerFactory instance in use
*/
public static ILoggerFactory getILoggerFactory() {
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://bugzilla.slf4j.org/show_bug.cgi?id=106
return TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
} 

  很显然,接着跟进performInitialization方法

  private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}

  跟进bind方法

private final static void bind() {
try {
// 从classpath获取可能的日志绑定者,就是找出所有slf4j的实现,并将它们的资源路径存放到staticLoggerBinderPathSet
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
// 若有多个(多余1个)绑定者,就是从classpath中找到了多个slf4j的实现,那么就打印警告。这个方法就不跟进了,感兴趣的自己跟进
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding 真正的绑定,将具体的实现绑定到slf4j
StaticLoggerBinder.getSingleton();
// 修改初始化状态为初始化成功
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
// 报告真实的绑定信息
reportActualBinding(staticLoggerBinderPathSet);
fixSubstitutedLoggers();
} catch (NoClassDefFoundError ncde) { // 若有多个绑定者,则会抛此异常,Java虚拟机在编译时能找到合适的类,而在运行时不能找到合适的类导致的错误,jvm不知道用哪个StaticLoggerBinder
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.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
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);
}
}

  跟进findPossibleStaticLoggerBinderPathSet方法

  // We need to use the name of the StaticLoggerBinder class, but we can't reference
// the class itself.
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; // 从classpath找出所有slf4j的实现,并记录下它们的资源路径
private 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 用LinkedHashSet能够保证插入的顺序
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()) {
// path的值 jar:file:/D:/repository/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class
URL path = (URL) paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}

  至此,问题1的答案就很明显了,slf4j会在classpath中找所有org/slf4j/impl/StaticLoggerBinder.class的资源路径,一般而言只有一个,在本博客中就在logback的jar中,如图

  

  那么logback与slf4j就关联起来了,接下来看logback对配置文件的加载。我们回到bind方法,跟进StaticLoggerBinder.getSingleton(),方法很简单

public static StaticLoggerBinder getSingleton() {
  return SINGLETON;
}

  很显然,执行此方法之前,对配置文件的加载已经执行完了,也就是说在编译器已经完成对配置文件的加载了。那么我们需要换目标跟进了,StaticLoggerBinder中只有一段静态块

    static {
SINGLETON.init();
}

  那么我们跟进init方法

    /**
* Package access for testing purposes.
*/
void init() {
try {
try {
// 上下文初始化器
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Throwable t) {
// we should never get here
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}

  接着跟进上下文初始化器的autoConfig方法

    public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
// 寻找默认配置文件
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
// 没有找到配置文件,则使用默认的配置器,那么日志只会打印在控制台
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}

  跟进findURLOfDefaultConfigurationFile方法

    public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
// 获取当前实例的类加载器,目的是在classpath下寻找配置文件
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this); // 先找logback.configurationFile文件
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
} // logback.configurationFile文件没找到,再找logback.groovy
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
} // logback.groovy没找到,再找logback-test.xml
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
} // logback-test.xml没找到,最后找logback.xml
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
} 

  自此,问题2的答案也清楚了,编译期间logback就完成了对配置文件的加载。

总结

  编译期间,完成slf4j的绑定已经logback配置文件的加载。slf4j会在classpath中寻找org/slf4j/impl/StaticLoggerBinder.class(会在具体的日志框架如log4j、logback等中存在),找到并完成绑定;同时,logback也会在classpath中寻找配置文件,先找logback.configurationFile、没有则找logback.groovy,若logback.groovy也没有,则找logback-test.xml,若logback-test.xml还是没有,则找logback.xml,若连logback.xml也没有,那么说明没有配置logback的配置文件,那么logback则会启用默认的配置(日志信息只会打印在控制台)。

  slf4j只能绑定某一个特定的日志框架,若没有绑定,则会有如下警告,说明没有找到合适的日志框架

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会发出警告,并在运行时抛出NoClassDefFoundError异常

  最后来一张图

从源码来理解slf4j的绑定,以及logback对配置文件的加载的更多相关文章

  1. Dubbo源码解析之SPI(一):扩展类的加载过程

    Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...

  2. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  3. storm源码之理解Storm中Worker、Executor、Task关系 + 并发度详解

    本文导读: 1 Worker.Executor.task详解 2 配置拓扑的并发度 3 拓扑示例 4 动态配置拓扑并发度 Worker.Executor.Task详解: Storm在集群上运行一个To ...

  4. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  5. React-setState源码的理解

    首先举一个最简单的例子: this.state={ a:1 } this.setState({ a:2 }) console.log(this.state.a)//1 可以说setState()操作是 ...

  6. 从源码角度理解Java设计模式——装饰者模式

    一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...

  7. 源码详解系列(七) ------ 全面讲解logback的使用和源码

    什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...

  8. logback.xml 不能被加载,logback不能被执行,logback.xml 无法生效,slf4j日志样式输出失败

    1. 原因 logback.xml 无法被加载, 尝试了好久还是失败,哎,最后新建工程竟然可以,所以说还是项目的问题: 原来项目依赖了两个slf4j.jar,是版本冲突了: 2. 查找原因 idea ...

  9. spring事务管理器的源码和理解

    原文出处: xieyu_zy 以前说了大多的原理,今天来说下spring的事务管理器的实现过程,顺带源码干货带上. 其实这个文章唯一的就是带着看看代码,但是前提你要懂得动态代理以及字节码增强方面的知识 ...

随机推荐

  1. 开始学习yii2第一天

    今天在朋友圈看到一条转发,内容是根据招聘网站的要求,列举了需要一个php工程师具体需要哪些技能 框架要求是yii2 出现的最多 已经出来工作了快半个月了,感觉工资还是少的可怜,而且我也好想去张江, 所 ...

  2. React虚拟DOM具体实现——利用节点json描述还原dom结构

    前两天,帮朋友解决一个问题: ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构. 刚好最近在看React,并且了解到其 ...

  3. wamp2.4.4 如何配置虚拟主机及反向代理(解决跨域问题)

    一.找到安装目录下的httpd.conf文件 1. 删除Include conf/extra/httpd-vhosts.conf前面的#号(开启虚拟主机的配置) 2. 删除LoadModule pro ...

  4. Error--解决使用Application Loader提交ipa包审核时的报错:ERROR ITMS-90168: "The binary you uploaded was invalid."

    在提交iTunes Connect审核时,使用Application Loader提交ipa包时报错:ERROR ITMS-90168: "The binary you uploaded w ...

  5. 微软在.NET官网上线.NET 架构指南频道

    微软在Visual Studio 2017 正式发布的时候也上线了一个参考应用https://github.com/dotnet/eShopOnContainers , 最近微软给这个参考应用写了完善 ...

  6. Java ClassLoader加载机制

    一.体系结构(自上向下) 1.Bootstrap ClassLoader(BootStrapClassLoader) --- 启动类加载器或者叫引导类加载器,加载jdk核心的APIs,这些APIs一般 ...

  7. 1.自定义控制器切换<一>

    一.自定义控制器切换:在同一个控制器上,展示不同的控制器,类似于tabbar一样 二.怎么做?(问题解决步骤) 1.创建若干控制器:OneViewController TwoViewControlle ...

  8. 用ElasticSearch搭建自己的搜索和分析引擎

    作者:robben,腾讯高级工程师 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. 导语:互联网产品中的检索功能随处可见.当你的项目规模是百度大搜|商搜或者微信公众号搜索这种体量的时候 ...

  9. 第一个SignalR案例

    说明:开发的案例为Hub(集线器) 一.开发环境 VS2013  ,window10 二.步骤 打开vs创建一个新的解决方案,添加一个空的WebForm项目. 使用NuGet添加引用.命令:PM> ...

  10. netcore实践:跨平台动态加载native组件

    缘起netcore框架下实现基于zmq的应用. 在.net framework时代,我们进行zmq开发由很多的选择,比较常用的有clrzmq4和NetMQ. 其中clrzmq是基于libzmq的Int ...