Java 日志框架解析:设计模式、性能

Java 日志框架解析:设计模式、性能

在平常的系统开发中,日志起到了重要的作用,日志写得好对于线上问题追踪有着很大的帮助。一个好的日志框架,既要方便易用,也要有较好的性能,减少日志输出对系统内存、CPU 的影响。

研究一款开源项目,学到的不仅仅是这个项目本身,还会学到很多设计思想,可以利用到日常工作中。这里我们主要讨论 & 描述三个问题:

  1. Java 日志框架有哪些组件?各自都是什么角色?
  2. Java 日志框架用到了哪些设计模式?
  3. 日志门面和具体实现是如何绑定的?
  4. 日志组件中异步输出的原理是什么?如果提升性能的?

Java 的日志框架

比较常见的日志组件有 slf4j、commons-logging、log4j、logback,它们之间是什么样的关系呢?大致可以分为 4类:
1. 日志门面:commons-logging-api 和 slf4j 提供了一套通用的接口,具体的实现可以由开发者自由选择。
2. 具体实现:log4j、logback、log4j2 等都是 slf4j 的具体实现,不需修改代码,通过简单的修改配置即可实现切换,十分方便。
3. 旧日志到 slf4j 的适配器:一些系统之前采用的是老的日志组件,其 API 和 slf4j 不太一样,那么如果想要切换成 slf4j + 具体实现的模式,可以使用一些适配器来嫁接到 sl4fj 上,比如 log4j-over-slf4j 可以用于替换 log4j。
4. slf4j 到旧实现的适配器:有些日志组件的出现早于 slf4j,其 API 和 slf4j 不一样,如果想要在代码中使用 slf4j 做 API,而实现才有这些老日志组件,那么就需要一个 slf4j 到旧实现的适配器,比如 slf4j-log4j12。

Java 日志框架

设计模式

门面模式

门面模式,是面向对象设计模中的结构模式,又称为外观模式。外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

使用门面模式的好处是,接口和实现分离,屏蔽了底层的实现细节。

当你使用 slf4j 作为系统的日志门面时,底层具体实现可以使用 log4j、logback、log4j2 中的任意一个。

适配器模式

将一个类的接口转换成客户希望的另外一个接口,Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

通常要组合两个不相干的类有两种方法,一种是修改各自的接口,如果不愿意(接口是别人的,他们不想改)或不方便(接口是公共 API,调用方很多,不方便单独给你改)修改,那就需要使用 Adapter 适配器,在两个接口之间做一个适配层。

适配器模式的类图

上图所示是适配器模式的类图。Adapter 适配器设计模式中有 3 个重要角色:被适配者 Adaptee,适配器 Adapter 和目标对象 Target。想要把 Client/Target 和 Adaptee 组合到一起,而它们接口又不适配时,就可以通过创建一个适配器 Adapter 将它们组合在一起。

比如想要将 slf4j 和 log4j 组合在一起,然而它们之间接口又不一致,就使用 slf4j-log4j12 来做适配器达到目的。适配器的存在,避免了对已有组件的修改。

slf4j API 和具体实现的绑定

在实际使用,一般只是在 pom.xml 文件中引入各种日志 jar 包,然后在 resources 文件夹下放一个日志配置 logback.xml 或 log4j.properties 即可,并没有显式的对 API 和其实现进行绑定。那么 slf4j API 是如何与不同实现进行绑定的呢?下面我们深入源码研究一下。

    private static final Logger log = LoggerFactory.getLogger("MyLoggerName");

一般在打日志的时候会调用 slf4j 的 LoggerFactory 创建一个 Logger,我们看看 LoggerFactory 的源码:

public final class LoggerFactory {
   
  public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
  }
}  

getLogger 方法调用 getILoggerFactory 来获取工厂类,getILoggerFactory 源码如下所示,主要调用了 performInitialization 方法进行初始化:

public final class LoggerFactory {
   
  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");
  }  

  private final static void performInitialization() {
    // 绑定日志实现
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
      versionSanityCheck();
    }
  }  
}  

performInitialization 方法进一步调用链 bind 方法,源码如下:

  private final static void bind() {
    try {
      Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
      // 如果存在多个日志实现,则使用 System.err 输出一些日志来提醒用户
      reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
      // the next line does the binding
      StaticLoggerBinder.getSingleton();
      INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
      reportActualBinding(staticLoggerBinderPathSet);
      fixSubstitutedLoggers();
    } catch (Exception e) {
      // 省略异常处理代码
    }
  }

bind 方法首先调用了 findPossibleStaticLoggerBinderPathSet 方法来找到所有的 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
    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(STATI
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值