对于开发者来说,日志是非常重要的,是我们定位问题的法宝。但是在之前的开发中一直都没有太多的关注,只是学会了使用,我们队知识不能只停留在会用的层面,那样的话,我们只会增加编码经验,但是没法获得技术的增加。
我们先看看我们在项目中使用的日志相关的jar包。
<!-- 日志文件管理包 -->
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
我们看到使用了log4j和slf4j(Simple Logging Facade for Java)。其实我们知道,其实我们只用log4j也是可以的,但是这里为什么又加了slf4j呢?
我们看slf4j的英文Simple Logging Facade for Java,直译就是简单日志门面(Facade,是不是很熟悉,对,Facade模式,一种常见的设计模式),其实slf4j是日志的接口,供用户使用,而没有提供实现。在我们常见的与日志相关的类,比如commons-logging,slf4j,logback,log4j,java.util.logging等等。
其中slf4j(Log4j开发)和commons-logging(Apache开发)是接口。
其他的是相关实现
slf4j通过简单的实现就能找到符合自己接口的实现类,如果不是满足自己标准的日志,可以通过一些中间实现比如上面的slf4j-log4j12.jar来进行适配。
package com.wangcc.test.logger;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LoggerTest.class);
logger.info("hello {}", new Date());
}
}
我们用一个测试类来debug看看,用slf4j日志接口和log4j日志实现,对比log4j的好处。
我们先看下这个类的执行结果。
[com.wangcc.test.logger.LoggerTest] - hello Sat Sep 23 22:05:25 CST 2017
为什么会有这样的结果,因为slf4j的一个很重要的特性,占位符!—— {} 可以任意的拼接字符串,自动的填入字符串中!这个到底怎么实现的呢,我们通过debug来探究。
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
LoggerFactory工厂中获取logger实例的代码如下,中间这里用一个getILoggerFactory()方法来嫁入一层接口,所有的需要桥接到slf4j日志框架都需要实现ILoggerFactory接口。
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
//ILoggerFactory 接口只定义了一个方法,getLogger
return iLoggerFactory.getLogger(name);
}
我们接下来来看下 getILoggerFactory()获取ILoggerFactory工厂的方法
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();
}
}
终于,我们到了slf4j最核心的代码部分了。
private final static void bind() {
try {
//首先获取实现日志的加载路径,查看路径是否合法,再初始化StaticLoggerBinder的对象,寻找合适的实现方式使用。
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstitutedLoggers();
} 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.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(),顾名思义,是找到那些实现类的路径
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
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
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()) {
URL path = (URL) paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
通过类加载器来查找所有实现了这个类”org/slf4j/impl/StaticLoggerBinder.class”的资源集合,所以这里也是用了一个Set集合,因为可能有很多实现,因为我们这里是有slf4j-log4j12一种实现,所以就定位到这里,如果还有slf4j-simple等实现的话,就得寻找默认的实现(与加载顺序相j关?)。
其实也就是说只要实现了这个类(org/slf4j/impl/StaticLoggerBinder.class)的日志实现系统,都可以作为一种slf4j实现方式。
回过头来,我们看getILoggerFactory()方法中 performInitialization();之后的执行
//初始化成功
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
我们看看StaticLoggerBinder这个类,使用了单例模式的实现
/**
* Copyright (c) 2004-2011 QOS.ch
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j.impl;
import org.apache.log4j.Level;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.Util;
import org.slf4j.spi.LoggerFactoryBinder;
/**
* The binding of {@link LoggerFactory} class with an actual instance of
* {@link ILoggerFactory} is performed using information returned by this class.
*
* @author Ceki Gülcü
*/
public class StaticLoggerBinder implements LoggerFactoryBinder {
/**
* The unique instance of this class.
*
*/
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
/**
* Return the singleton of this class.
*
* @return the StaticLoggerBinder singleton
*/
public static final StaticLoggerBinder getSingleton() {
return SINGLETON;
}
/**
* Declare the version of the SLF4J API this implementation is compiled
* against. The value of this field is usually modified with each release.
*/
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.6.99"; // !final
private static final String loggerFactoryClassStr = Log4jLoggerFactory.class
.getName();
/**
* The ILoggerFactory instance returned by the {@link #getLoggerFactory}
* method should always be the same object
*/
private final ILoggerFactory loggerFactory;
private StaticLoggerBinder() {
loggerFactory = new Log4jLoggerFactory();
try {
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");
}
}
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
public String getLoggerFactoryClassStr() {
return loggerFactoryClassStr;
}
}
该类使用的单例模式是属于饿汉模式,在类初始化的时候就已经得到了单例实例对象了,不存在线程安全问题。
我们可以清楚的看到这个得到的Factory实现类是Log4jFactory
* Copyright (c) 2004-2011 QOS.ch
package org.slf4j.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.LogManager;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
/**
* Log4jLoggerFactory is an implementation of {@link ILoggerFactory} returning
* the appropriate named {@link Log4jLoggerAdapter} instance.
*
* @author Ceki Gülcü
*/
public class Log4jLoggerFactory implements ILoggerFactory {
// key: name (String), value: a Log4jLoggerAdapter;
ConcurrentMap<String, Logger> loggerMap;
public Log4jLoggerFactory() {
loggerMap = new ConcurrentHashMap<String, Logger>();
}
/*
* (non-Javadoc)
*
* @see org.slf4j.ILoggerFactory#getLogger(java.lang.String)
*/
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;
}
}
}
我们注意看getLogger的实现,注意到
Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
也就是说
Logger logger = LoggerFactory.getLogger(LoggerTest.class);
这句代码得到的Logger是Log4jLoggerAdapter对象,见到以Adapter为结尾的类是不是很熟悉,对,这是适配器模式的运用。这一点,我们在debug接下来拿句就可以很清晰的看到。
public void info(String format, Object arg) {
if (logger.isInfoEnabled()) {
FormattingTuple ft = MessageFormatter.format(format, arg);
logger.log(FQCN, Level.INFO, ft.getMessage(), ft.getThrowable());
}
}
具体FormattingTuple 的实现,也就说如何做到通配符{}实现的作用,我们这里不讨论。但是我们可以清楚的知道这种通配符作用的实现(在原始的Log4j里面做不到的)是通过什么方法实现了,是通过适配器模式来实现的。Log4的设计开发者为了不破坏原有的Log4j实现代码,而且又想新增这种通配符的功能,为了符合开闭原则,采用了适配器模式,可以说是非常的有灵性了。
总结:
slf4j这个日志接口框架的设计主要是运用了Facade模式和适配器模式。
通过Facade模式,将不同的实现日志框架的具体实现差异对用户透明,提供给用户一个公共可用的接口。这里是不是似曾相识呢,是的,很多Java框架的设计可能都会用到这个设计模式,比如Servlet的设计,我们再阅读Servlet源码的时候,我们会看到有HttpServletRequestFacade等字样的类,这是为了让Servlet能够有统一的request,response方法可以使用,而不用去关心到底request对象到底是怎么得到Header信息的,response对象又是怎样把这些对象返回给浏览器的,把这些需要web容器如tomcat ,jetty等需要实现的东西对Servlet透明(不同的web容器对这些会有不同的实现,但这不是Servlet需要关心的,Servlet需要的就是统一可用的接口,Facade模式就很好的实现了这一点,让Servlet可以专心做自己该做的部分)。
适配器模式,前面有说过。Log4的设计开发者为了不破坏原有的Log4j实现代码,而且又想新增这种通配符的功能,为了符合开闭原则,采用了适配器模式。适配器模式在框架的设计中也是非常常见,在SpringMVC中就有使用,这个我们最近会讲,讲到SpringMVC源码分析的时候会回来补充。