slf4j简介

slf4j主要是为了给Java日志访问提供一个标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。本文侧重分析slf4j,也会解释门面+桥接器+实现的原理。

slf4j项目

slf4j.png

slf4j-api为项目基础

slf4j-jdk14和slf4j-log4j12分别为jdk日志框架和log4j日志框架的桥接器,负责将slf4j-api和具体的实现框架连接起来

jcl-over-slf4j, log4j-over-slf4j, osgi-over-slf4j和jul-to-slf4j分别将对应的其他框架的日志桥接到slf4j上来

slf4j-nop和slf4j-simple是slf4j提供的日志实现类,一般很少用到

slf4j-ext和slf4j-migrator为工具模块

slf4j-api

slf4j-api是slf4j的api模块,提供了日志输出的API,值得一提的是从slf4j 1.8起,slf4j使用SPI的方式寻找日志实现框架,而在此之前则是通过寻找指定类的方式发现并绑定实现框架。

本文以slf4j 1.8为例进行讲解。

先来看看平时我们是如何使用slf4j-api进行日志输出的

private Logger logger = LoggerFactory.getLogger(TestController.class);

...

logger.info("some message")

重点看看第一行代码,看似很简单,其实做了不少事情。

public static Logger getLogger(String name) {

ILoggerFactory iLoggerFactory = getILoggerFactory();

return iLoggerFactory.getLogger(name);

}

其中ILoggerFactory是slf4j提供的一个接口,因此我们可以猜测getILoggerFactory方法应该是拿到了其实现类.

public static ILoggerFactory getILoggerFactory() {

return getProvider().getLoggerFactory();

}

static SLF4JServiceProvider getProvider() {

if (INITIALIZATION_STATE == UNINITIALIZED) {

synchronized (LoggerFactory.class) {

if (INITIALIZATION_STATE == UNINITIALIZED) {

INITIALIZATION_STATE = ONGOING_INITIALIZATION;

performInitialization();

}

}

}

switch (INITIALIZATION_STATE) {

case SUCCESSFUL_INITIALIZATION:

return PROVIDER;

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_PROVIDER;

}

throw new IllegalStateException("Unreachable code");

}

其中SLF4JServiceProvider同样是slf4j-api中提供的一个接口,那么getProvider肯定是通过某种方法拿到了该接口的实现类。

private final static void performInitialization() {

bind();

if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {

versionSanityCheck();

}

}

private final static void bind() {

try {

List providersList = findServiceProviders();

reportMultipleBindingAmbiguity(providersList);

if (providersList != null && !providersList.isEmpty()) {

PROVIDER = providersList.get(0);

PROVIDER.initialize();

INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;

reportActualBinding(providersList);

fixSubstituteLoggers();

replayEvents();

// release all resources in SUBST_FACTORY

SUBST_PROVIDER.getSubstituteLoggerFactory().clear();

} else {

INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;

Util.report("No SLF4J providers were found.");

Util.report("Defaulting to no-operation (NOP) logger implementation");

Util.report("See " + NO_PROVIDERS_URL + " for further details.");

Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();

reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);

}

} catch (Exception e) {

failedBinding(e);

throw new IllegalStateException("Unexpected initialization failure", e);

}

}

注意其中一行代码

List providersList = findServiceProviders();

private static List findServiceProviders() {

ServiceLoader serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);

List providerList = new ArrayList();

for (SLF4JServiceProvider provider : serviceLoader) {

providerList.add(provider);

}

return providerList;

}

终于露出庐山真面目:原来是通过SPI的方式寻找SLF4JServiceProvider的实现类.

接下来看看SLF4JServiceProvider这个接口提供的方法(只贴了两个最重要的方法)

public interface SLF4JServiceProvider {

// 返回ILoggerFactory的实现类

public ILoggerFactory getLoggerFactory();

// 初始化,实现类中一般用于初始化ILoggerFactory

public void initialize();

}

先不着急看SLF4JServiceProvider的实现类长什么样子,先思考一个问题:如果找不到实现类或者是找到了多个实现类怎么办?

还是看bind方法。

找不到实现类:会在classpath下寻找org/slf4j/impl/StaticLoggerBinder.class类,这个类就是1.8之前slf4j寻找的Binder类,因此匹配1.8版本之前的slf4j-api的桥接器都会包含一个该类.1.8及之后的版本中即便是找到了该类,slf4j也不会使用该类完成实现框架的绑定,而是忽略,使用自带的实现类NOPLogger。该类其实啥都没做,所有的方法都是空的。因此,如果找不到SLF4JServiceProvider的实现类,系统不会报错,而是不会输出任何日志

找到多个实现类:会取第一个,但是谁是第一个呢?看官方解释:

The warning emitted by SLF4J is just that, a warning. Even when multiple bindings are present, SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and for all practical purposes should be considered random. As of version 1.6.6, SLF4J will name the framework/implementation class it is actually bound to

答案就是:取决于JVM,你可以认为是随机的。

桥接器

主要讲讲如何slf4j-api+桥接器+实现框架(以log4j为例)的工作原理

我们不妨思考下,我们希望利用这三件套做什么?我们想做的是编码中使用的是slf4j-api提供的方法,而实际运作的是log4j。也就是说我们希望使用Logger(slf4j提供),而实际运行的是Logger(log4j),其实很好办,那就是将Logger(slf4j)接收到的命令全部委托给Logger(log4j)去完成,悄悄的完成偷天换日。我们接下来去翻翻slf4j-log4j12这个桥接器的源码,看看它是怎么做的。

首先,它肯定有个SLF4JServiceProvider的实现类

public class Log4j12ServiceProvider implements SLF4JServiceProvider {

/**

* Declare the version of the SLF4J API this implementation is compiled against.

* The value of this field is modified with each major release.

*/

// to avoid constant folding by the compiler, this field must *not* be final

public static String REQUESTED_API_VERSION = "1.8.99"; // !final

private ILoggerFactory loggerFactory;

private IMarkerFactory markerFactory;

private MDCAdapter mdcAdapter;

public Log4j12ServiceProvider() {

try {

@SuppressWarnings("unused")

Level level = Level.TRACE;

} catch (NoSuchFieldError nsfe) {

Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");

}

}

@Override

public void initialize() {

loggerFactory = new Log4jLoggerFactory();

markerFactory = new BasicMarkerFactory();

mdcAdapter = new Log4jMDCAdapter();

}

public ILoggerFactory getLoggerFactory() {

return loggerFactory;

}

public IMarkerFactory getMarkerFactory() {

return markerFactory;

}

public MDCAdapter getMDCAdapter() {

return mdcAdapter;

}

public String getRequesteApiVersion() {

return REQUESTED_API_VERSION;

}

}

看看它的getLoggerFactory返回的实现类

public class Log4jLoggerFactory implements ILoggerFactory {

private static final String LOG4J_DELEGATION_LOOP_URL = "http://www.slf4j.org/codes.html#log4jDelegationLoop";

// check for delegation loops

static {

try {

Class.forName("org.apache.log4j.Log4jLoggerFactory");

String part1 = "Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. ";

String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details.";

Util.report(part1);

Util.report(part2);

throw new IllegalStateException(part1 + part2);

} catch (ClassNotFoundException e) {

// this is the good case

}

}

// key: name (String), value: a Log4jLoggerAdapter;

ConcurrentMap loggerMap;

public Log4jLoggerFactory() {

loggerMap = new ConcurrentHashMap();

// force log4j to initialize

org.apache.log4j.LogManager.getRootLogger();

}

/*

* (non-Javadoc)

*

* @see org.slf4j.ILoggerFactory#getLogger(java.lang.String)

*/

public Logger getLogger(String name) {

// 从缓存中获取Logger

Logger slf4jLogger = loggerMap.get(name);

if (slf4jLogger != null) {

return slf4jLogger;

} else {

// 如果没有就构造一个log4j的Logger

org.apache.log4j.Logger log4jLogger;

if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))

log4jLogger = LogManager.getRootLogger();

else

log4jLogger = LogManager.getLogger(name);

// 利用构造的log4j的Logger 构造出一个Log4jLoggerAdapter

Logger newInstance = new Log4jLoggerAdapter(log4jLogger);

Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);

return oldInstance == null ? newInstance : oldInstance;

}

}

}

注意Log4jLoggerAdapter这个类,它内部持有了一个log4j的Logger对象,自身又实现了slf4j的Logger接口,这是一个典型的适配器模式。其提供了slf4j-api的方法,但是具体事情全交给持有的log4j Logger去执行。这样就达到了连接slf4j-api和log4j的目的。而log4j Logger的具体实现则是交由log4j去完成的,slf4j-api无感知也不关心。

简单实现类

NOPLogger,什么都不做,在slf4j-api中

SimpleLogger,slf4j-simple提供的简单实现类,有兴趣的同学可以去看看,因为slf4j的定位是提供标准的API,而不是实现,因此其实现类算是个鸡肋,聊胜于无

反向桥接器

我之所以称之为反向桥接器是为了区分前文中提到的桥接器,前文提到的桥接器作用是将slf4j-api导到具体的实现框架上,而这部分的桥接器则是将实现框架提供的API调用导到slf4j-api上来。例如当前项目中使用log4j的Logger打印日志,想要切换到logback上来又不想改代码,怎么办呢?这时反向桥接器就能大展拳脚了。

将log4j的jar包从项目中拿掉,此时项目编译肯定没法通过,别急,到第二步

引入log4j-over-slf4j jar,该jar包中包含了log4j中主要的类,连名字都一样,这样编译不再报错

引入logback-classic和logback-core,大功告成

反向桥接器的原理就是自己写了一堆桥接来源相关jar中一样的类,偷天换日,悄悄的来了个狸猫换太子。

值得注意的是不要乱引入桥接器和反向桥接器,避免形成环,导致栈溢出。

工具

slf4j-ext

该模块中提供了一个比较好玩的功能,利用java instrument做到无代码侵入的日志输出。该模块提供了一个名为LogTransformer的类,该类会动态在类中增加一个Logger(slf4j)静态私有对象,在该类方法调用前后加上日志输出。可以通过命令行来指定日志级别,哪些类不被动态修改等。个人感觉在大型项目中实用性不太高,原因有

指定哪些类不被动态修改太繁琐,大于大型项目而言引入的类可能上千个,如果需要一一排除,实在是繁琐

不能做到方法级别的排除,不够灵活

大型项目一般在建立初期就势必会考虑日志的输出,不太可能靠这种方式输出日志

不过个人感觉如果能将其稍微修改下,倒是可以用来临时排查线上问题

将指定排除类改为指定类进行字节码修改

精细到方法级别的指定

假设需要现在需要排查线上问题,但是已经在运行的代码日志输出不够详细,无法分析某个方法的调用信息(次数、时长),又不想重启,这时可以将修改后的agent.jar动态attach到指定的JVM上,以达到日志输出的目的。待问题排查完毕后,detach下来即可(注意:动态attach和detach需要java 1.6+)

slf4j-migrator

迁移工具,暂时没有仔细研究

java 日志门面_slf4j-日志门面担当相关推荐

  1. 常用日志门面和日志实现

    一.什么是日志门面和日志实现 日志是什么?日志:说明系统实时运行状态的信息. 比如:System.out.println()语句就是一种最低级的日志. 什么是日志门面和日志实现? 日志门面:是日志实现 ...

  2. java slf4j日志框架_SLF4J - 日志框架 - 类库 - Java - 代码树

    JAVA简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现.可以在软件部署的时候决定要使用的 Log ...

  3. 日志门面和日志框架(日志实现框架log4j2)

    一.log4j2简介 Apache Log4j 2是对Log4j的升级,是最优秀的java日志框架. 二.log4j2特征 性能提升:Log4j2包含基于LMAX Disruptor库的下一代异步记录 ...

  4. 日志门面和日志框架(SpringBoot日志实现)

    一.Springboot日志实现简介 SpringBoot是现今市场上最火爆用来简化spring开发的框架,springboot日志也是开发常用的日志系统.SpringBoot默认就是使用SLF4J作 ...

  5. java slf4j日志级别_SLF4J日志级别以及使用场景

    为什么要使用日志 在项目开发的过程中, 添加合适的日志是一个必不可少的过程,给程序添加合适的日志有以下两个好处. 可以通过查看日志的输出,了解程序的运行状况,判断程序是否按预期进行运行. 程序出现bu ...

  6. Java日志框架 -- 日志框架介绍、日志门面技术、JUL日志(JUL架构、JUL入门示例、JUL日志级别、JUL日志的配置文件)

    1. 日志的概念 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史数据.诊断问题的追踪以及理解系统的活动等重要作用. 2. Java日志框架 问题: 控制日志输出的内容 ...

  7. Java日志门面- JCL和 常用日志门面SLFJ详解

    使用日志门面的原因 目前经常用的日志框架技术有:JUL.Log4j.log4j2.logback用来记录日志信息 ,之前我们讲过,我们学习不同的日志框架.他们的API是不同的,这样难以进行有效的记忆, ...

  8. java常用日志框架日志门面及实现 SLF4J 、Jboss-logging 、JCL、Log4j、Logback、Log4j2、JUL,springboot集成 log4j、log4j2

    java常用日志框架日志门面SLF4J .Jboss-logging .JCL.Log4j及实现 Logback.Log4j2.JUL,springboot集成 log4j.log4j2 .logba ...

  9. Java日志框架日志门面介绍

    文章目录 一.日志 二.常见日志框架 历史 各大框架介绍 JUL Log4j(1999-2015) Logback(2006-?) Log4j2 Logback与Log4j2对比 三.日志门面 什么是 ...

  10. Java日志框架 -- SLF4J日志门面(入门案例、SLF4J优点、SLF4J日志绑定、SL4J桥接旧的日志框架)

    1. SLF4J日志门面 JCL日志门面逐渐被淘汰了,因为他无法动态的扩展具体的日志实现框架. 简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Ja ...

最新文章

  1. 访谈实录:网管员如何踏上高薪之路(1)
  2. 分布式TCP压力测试工具 tcpcopy
  3. android开发常见的设计模式,Android开发有哪些常用设计模式?
  4. 微服务架构编码,构建
  5. GitHub 配置 SSH 连接
  6. 韩顺平 Mysql数据库优化(一) 优化概述
  7. laravel -admin 禁止某一行删除
  8. zoj 1115 Digital Roots
  9. Win8 Metro(C#)数字图像处理--2.39二值图像投影
  10. C# 中的字符串内插
  11. sql 判断条件累加_SQL 是描述性语言?
  12. (Maven配置)Failed to read artifact descriptor for xxx:jar解决方法
  13. 机器学习——CART决策树——泰坦尼克还生还预测
  14. SketchUp插件|FredoGhost幻影替身插件最新版免费下载及介绍(轻量化草图大师模型)
  15. layui重置按钮函数,支持文件
  16. 用移动硬盘当系统盘,即插即用
  17. 纪念一下学写pipeline时脑子里的坑
  18. hello heaven
  19. 骁龙AR2平台解析:分布式架构开启轻量化AR眼镜新时代
  20. Spring-Bean加载顺序控制/循环依赖控制

热门文章

  1. Partitioning by Palindromes UVA - 11584(DP)
  2. java520.1314表白_告白日表白公式 520.1314 临沂人知道怎么玩吗
  3. 计算机老师为什么不用伽卡他卡做文件服务器?
  4. CSS Cascading Style Sheets 层叠样式表:CSS了解 (一)
  5. 7-4 求奇数和 (15 分)
  6. [Cnbeta]企业与家用无线路由器的区别
  7. E_Groundhog Chasing Death(不错的数论)
  8. Vue v-modle理解
  9. UE4对接腾讯GME语音服务(实时语音一)
  10. 这样的国企,不去也罢