内 容:
- 应用中使用slf4j的工作流程
- 简单示例
- ILoggerFactory实例化过程
- 由ILoggerFactory创建Logger实例
- slf4j 适配器实现
- 自定义适配器
- slf4j 如何选取多个Log框架
- 如何将commons-logging项目迁移到SLF4j
现如今,日志框架层出不穷,JDKLogger、Log4j、Logback等这些是最常用的了。然而现在越来越多的框架中,都会在使用日志框架的同时,还会使用到一个门面(slf4j-api.jar),使用这个门面的的最方便的地方大抵是它提供格式化字符串的功能。
在应用程序中,直接使用slf4j-api.jar就可以完成日志的记录,而不用在代码里直接使用某一种日志框架了(虽然最终记录日志还是有日志框架来完成的)。
下面是使用了slf4j时,应用程序、slf4j、slf4j-adapter.jar、日志框架之间的调用关系:
package com.fjn.frame.slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); } }
LoggerFactory.getLogger(xxx)要分为两个过程:
1、实例化ILoggerFactory, 这个步骤只是在第一次进行。
2、根据ILoggerFactory实例创建Logger实例。
下面就分别来说说这两个过程:
ILoggerFactory 实例化的过程
在使用slf4j时,并不需要指定具体使用哪种日志框架,只需要给定相关的适配器包就可以了。那么如何拿到真正的ILoggerFactory实现,并实例化的呢?
1、在LogFactory的classloader的搜索路径下查找 ” org/slf4j/impl/StaticLoggerBinder.class” 类
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; private static void singleImplementationSanityCheck() { 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); } // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set implementationSet = new LinkedHashSet(); while (paths.hasMoreElements()) { URL path = (URL) paths.nextElement(); implementationSet.add(path); } if (implementationSet.size() > 1) { Util.report("Class path contains multiple SLF4J bindings."); Iterator iterator = implementationSet.iterator(); while(iterator.hasNext()) { URL path = (URL) iterator.next(); Util.report("Found binding in [" + path + "]"); } Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } }
如果有多个就会打印:
2、将slf4j与指定的实现进行绑定,这一步的操作,通常是:
1)单实例化StaticLoggerBinder。
2)检查StaticLoggerBinder的版本,其实就是需要有一个静态的非final的变量(这个变量不是必须得有的),
StaticLoggerBinder.REQUESTED_API_VERSION
3、获取到ILoggerFactory的实例
一般来说,是返回一个static ILoggerFactory impl=new XXXLoggerFactory();
由ILoggerFactory来创建Logger实例
LoggerFactory创建Logger实例,其实由日志框架本身的LogManager创建一个Logger,然后包装成LoggerAdapter。例如Log4j的适配包中的Log4jLoggerFactory#getLogger(String name)的实现如下:
public Logger getLogger(String name) { Logger slf4jLogger = loggerMap.get(name); if (slf4jLogger != null) { return slf4jLogger; } else { org.apache.log4j.Logger log4jLogger; if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) log4jLogger = LogManager.getRootLogger(); else log4jLogger = LogManager.getLogger(name); Logger newInstance = new Log4jLoggerAdapter(log4jLogger); Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } }
自定义日志框架适配器
在一些公司,肯定还有自己的Logger框架,如果也希望通过slf4j来做日志,就需要写相关的适配器包了。通过上述的两个过程的了解,很容易就能知道如何自定义适配器了。
自定义适配器中,必须包括3个组件:
· StaticLoggerBinder
这个类需要遵守下列规约:
1) 类名必须是org.slf4j.impl.StaticLoggerBinder
2) 这个类必须是单例的,必须有getSingleton()方法
3) 尽可能的有 public static String REQUESTED_API_VERSION 字段,并且不能是final的。
4) 要实现org.slf4j.spi.LoggerFactoryBinder接口。
· LoggerFactoryImpl
这个类要实现org.slf4j.ILoggerFactory接口
· LoggerAdapter
这个类要实现org.slf4j.Logger接口。
如何选取多个Log框架
SLF4j原则上只会绑定一个适配器,所以通常我们的项目中,最好只使用一种日志框架。然而事与愿违,现在项目中通常都会引入大量第三方框架等,这些框架使用的日志框架又不尽相同,所以引入多个日志框架的情况也是不可避免的。SLF4j初始化时,如果找到了多个相关的适配器,只会使用找到的第一个适配器。
在项目中要尽量减少引入的log框架的数量,通常会使用maven exclusion:
<dependencies> <dependency> <groupId> org.apache.cassandra</groupId> <artifactId>cassandra-all</artifactId> <version>0.8.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
如何将项目从Commons-Logging迁移到SLF4j
如果项目中(或者引入的第三方jar中)已经使用了commons-logging,要迁移至SLF4j。
需要做两个事:
1、将项目中已使用commons-logging的模块、以及第三方jar。使用maven exclustions排除掉。
2、引入jcl-over-slf4j.jar。
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency>
原因:
项目(或者第三方jar)中使用的是commons-logging的API:org.apache.commons.logging.Log、org.apache.commons.logging.LogFactory。
这两个类在jcl-over-slf4j.jar中都有。不过修改了其实现。
在修改后的org.apache.commons.logging.LogFactory的实现是将原来的LogFactory查找过程完全替换成了jcl-over-slf4j.jar包中的一个SLF4jLogFactory:
package org.apache.commons.logging; import java.util.Hashtable; import org.apache.commons.logging.impl.SLF4JLogFactory; /** * <p> * Factory for creating {@link Log} instances, which always delegates to an * instance of {@link SLF4JLogFactory}. * * </p> * * @author Craig R. McClanahan * @author Costin Manolache * @author Richard A. Sitze * @author Ceki Gülcü */ public abstract class LogFactory { static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j"; static LogFactory logFactory = new SLF4JLogFactory(); public static LogFactory getFactory() throws LogConfigurationException { return logFactory; } ... ... }
而SLF4jLogFactory的实现是委托给slf4j的LoggerFactory去定位日志框架并取得Logger对象,最后将取得的Logger对象再包装成commons-logging中的Log实现:
public class SLF4JLogFactory extends LogFactory { ... public Log getInstance(String name) throws LogConfigurationException { Log instance = loggerMap.get(name); if (instance != null) { return instance; } else { Log newInstance; Logger slf4jLogger = LoggerFactory.getLogger(name); // 又委托给了Slf4j的 LoggerFactory if (slf4jLogger instanceof LocationAwareLogger) { newInstance = new SLF4JLocationAwareLog((LocationAwareLogger) slf4jLogger); } else { newInstance = new SLF4JLog(slf4jLogger); } Log oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } } }
所以迁移时,一定要完全排除commons-logging的jar,并引入 jcl-over-slf4j.jar