日志(一)从Maven依赖文件说起

对于开发者来说,日志是非常重要的,是我们定位问题的法宝。但是在之前的开发中一直都没有太多的关注,只是学会了使用,我们队知识不能只停留在会用的层面,那样的话,我们只会增加编码经验,但是没法获得技术的增加。
我们先看看我们在项目中使用的日志相关的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&uuml;lc&uuml;
 */
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&uuml;lc&uuml;
 */
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源码分析的时候会回来补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值