Logger of Log4j

1.Logger

Logger是log4j日志记录功能的总入口,除了上下文的配置操作,其他功能点都是通过此类完成。Logger继承自Category,而Category又实现了AppenderAttachable接口。可以认为Logger就是Category,但是已经不建议直接使用Category了,存在Category的目的是为了保证兼容性。

Logger有成员属性:

// 日志记录器的名字
protected String name;
// 类的全路径名,遮盖了Category中的FQCN
private static final String FQCN = Logger.class.getName();
// 关联的日志级别
volatile protected Level level;
// 日志记录器的父节点
volatile protected Category parent;
// 日志记录器关联的资源文件,用于国际化或本地化
protected ResourceBundle resourceBundle;
// 日志记录器所在的管理仓库LoggerRepository
protected LoggerRepository repository;
// AppenderAttachableImpl是AppenderAttachable的内置实现,用于组织多个Appender
AppenderAttachableImpl aai;
// 默认为true,表示子日志记录器继承祖先日志记录器关联的Appender
protected boolean additive = true;

对成员属性的一些封装,setXXX、getXXX方法等:

  public final String getName() {
    return name;
  }
  public final Category getParent() {
    return this.parent;
  }
  public final Level getLevel() {
    return this.level;
  }
  public boolean getAdditivity() {
    return additive;
  }

  // AppenderAttachable接口的实现,具体委托给AppenderAttachableImpl的实现
  synchronized public Enumeration getAllAppenders() {
    if(aai == null)
      return NullEnumeration.getInstance();
    else
      return aai.getAllAppenders();
  }
  synchronized public Appender getAppender(String name) {
    if(aai == null || name == null)
      return null;
    return aai.getAppender(name);
  }

静态工具函数:

  // 获取指定名字的日志记录器和根RootLogger,具体使用了LogManager和LoggerRepository
  static public Logger getLogger(String name) {
    return LogManager.getLogger(name);
  }
  static public Logger getLogger(Class clazz) {
    return LogManager.getLogger(clazz.getName());
  }
  public static Logger getRootLogger() {
    return LogManager.getRootLogger();
  }

核心功能接口,这里以warn、debug为例子,其他日志级别如info、error等是完全类似的:

  // 如果满足日志级别条件,将消息给到Appender,使用WARN日志级别,将Throwable实例作为参数调用仅输出Throwable名字而非异常栈
  public void warn(Object message) {
    if(repository.isDisabled( Level.WARN_INT))
      return;
    if(Level.WARN.isGreaterOrEqual(this.getEffectiveLevel()))
      forcedLog(FQCN, Level.WARN, message, null);
  }

  // 如果满足日志级别条件,将消息给到Appender,使用WARN日志级别,Throwable实例可作为第二个参数,输出异常栈
  public void warn(Object message, Throwable t) {
    if(repository.isDisabled(Level.WARN_INT))
      return;
    if(Level.WARN.isGreaterOrEqual(this.getEffectiveLevel()))
      forcedLog(FQCN, Level.WARN, message, t);
  }

  public void debug(Object message) {
    if(repository.isDisabled(Level.DEBUG_INT))
      return;
    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
      forcedLog(FQCN, Level.DEBUG, message, null);
    }
  }

  public void debug(Object message, Throwable t) {
    if(repository.isDisabled(Level.DEBUG_INT))
      return;
    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel()))
      forcedLog(FQCN, Level.DEBUG, message, t);
  }

  // forcedLog中根据类全限定名、日志级别、待渲染日志消息对象、可能的异常构造LoggingEvent对象并交付callAppenders
  protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
    callAppenders(new LoggingEvent(fqcn, this, level, message, t));
  }

  // callAppenders中遍历所有可能的Appender,并调用相应的appender.doAppend(event);方法,具体LoggingEvent如何、何时到达目的端(文件、Socket等)由Appender控制;具体LoggingEvent的序列化方式,由LoggingEvent持有关联的Layout控制
  public void callAppenders(LoggingEvent event) {
    int writes = 0;
    for(Category c = this; c != null; c=c.parent) {
      // Protected against simultaneous call to addAppender, removeAppender,...
      synchronized(c) {
        if(c.aai != null) {
          writes += c.aai.appendLoopOnAppenders(event);
        }
        // 根据additive标志位,判断是否到祖先日志记录器中找寻可能的Appender
        if(!c.additive) {
          break;
        }
      }
    }
    if(writes == 0) {
      repository.emitNoAppenderWarning(this);
    }
  }

AppenderAttachableImpl中的appendLoopOnAppenders实现:

  public int appendLoopOnAppenders(LoggingEvent event) {
    int size = 0;
    Appender appender;
    if(appenderList != null) {
      size = appenderList.size();
      for(int i = 0; i < size; i++) {
        appender = (Appender) appenderList.elementAt(i);
        appender.doAppend(event);
      }
    }    
    return size;
  }

其他一些情况:

  • l7dlog系列的2歌方法,用于处理本地化日志消息,具体根据key查找资源文件获取日志消息message,如果有params参数,则使用java.text.MessageFormat.format进行格式化。
  public void l7dlog(Priority priority, String key, Throwable t) {
    if(repository.isDisabled(priority.level)) {
      return;
    }
    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
      String msg = getResourceBundleString(key);
      // if message corresponding to 'key' could not be found in the
      // resource bundle, then default to 'key'.
      if(msg == null) {
        msg = key;
      }
      forcedLog(FQCN, priority, msg, t);
    }
  }

  public void l7dlog(Priority priority, String key,  Object[] params, Throwable t) {
    if(repository.isDisabled(priority.level)) {
      return;
    }
    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
      String pattern = getResourceBundleString(key);
      String msg;
      if(pattern == null)
        msg = key;
      else
        msg = java.text.MessageFormat.format(pattern, params);
      forcedLog(FQCN, priority, msg, t);
    }
  }
  • isDebugEnabled的目的是为了在DEBUG日志级别下可能的计算复杂度,一个典型的对比例子是:不管日志级别是否满足,都会进行字符串拼接,字符串是不可变类有性能损耗,如下:
cat.debug("This is entry number: " + i );

log4j提供的优化思路是先判断DEBUG日志级别是否满足,再考虑是否字符串拼接,但是如果DEBUG日志级别满足时,会判断2次DEBUG日志级别是否满足,因为在cat.debug调用中还有一次。但是多一次日志级别判断毕竟比字符串构造效率高,另外毕竟生产环境中绝大多数情景也是不需要debug日志(一次判断的情景占绝大多数)的。

if(cat.isDebugEnabled()) {
  cat.debug("This is entry number: " + i );
}

public boolean isDebugEnabled() {
  if(repository.isDisabled( Level.DEBUG_INT))
    return false;
  return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
}

2.RootLogger

RootLogger处于日志记录器等级关系的顶端,RootLogger继承自Logger,具有Logger相关的所有功能,但是有约束如下:

  • 日志级别不能设置为null。
  • RootLogger没有祖先日志记录器,即getChainedLevel直接返回RootLogger持有的日志级别即可。
public final class RootLogger extends Logger {
  /**
     The root logger names itself as "root". However, the root
     logger cannot be retrieved by name.
  */
  public RootLogger(Level level) {
    super("root");
    setLevel(level);
  }

  /**
     Return the assigned level value without walking the logger
     hierarchy.
  */
  public final Level getChainedLevel() {
    return level;
  }

  /**
     Setting a null value to the level of the root logger may have catastrophic
     results. We prevent this here.
  */
  public final void setLevel(Level level) {
    if (level == null) {
      LogLog.error(
        "You have tried to set a null level to root.", new Throwable());
    } else {
      this.level = level;
    }
  }
}

3.LoggerRepository

LoggerRepository提供了log4j中日志记录器Logger的注册、检索、事件监听相关的功能,可以看做是Logger统一管理的仓库,获取日志记录器程序代码如

Logger.getLogger("smtp")

,其最终都是在LoggerRepository创建和登记的。

LoggerRepository中的日志记录器一般实现规则有:

  • 同一个名字的记录器Logger在LoggerRepository中只存储一份,在getLogger获取时如果当前不存在会进行创建并登记在LoggerRepository。
  • 注册在LoggerRepository中的日志记录器之间一般维护为父子关系,父子关系是通过记录器的名字体现的,比如日志记录器a.b.c,父日志记录器为a.b。

LoggerRepository是一个接口,需要实现的方法有:

  // 添加事件监听
  public void addHierarchyEventListener(HierarchyEventListener listener);
  // 判断对于指定的日志级别是否通过
  boolean isDisabled(int level);
  // 设置日志级别,默认为 Level.ALL
  public void setThreshold(Level level);
  // 设置日志级别,默认为 Level.ALL
  public void setThreshold(String val);
  // 打印没有关联Appender相关的警告信息
  public void emitNoAppenderWarning(Category cat);
  // 获取LoggerRepository设置的日志级别
  public Level getThreshold();
  // 获取指定名字的日志记录器
  public Logger getLogger(String name);
  // 获取指定名字的日志记录器,如果目前不存在则使用LoggerFactory创建一个
  public Logger getLogger(String name, LoggerFactory factory);
  // 获取根Logger
  public Logger getRootLogger();
  // 判断指定名字的日志记录器释放在LoggerRepository管理中
  public abstract Logger exists(String name);
  // 关闭LoggerRepository,一般会关闭释放Appender持有的资源
  public abstract void shutdown();
  // 获取LoggerRepository管理的所有日志记录器
  public Enumeration getCurrentLoggers();
  // 触发添加Appender相关的事件,监听器可以做适当的处理
  public abstract void fireAddAppenderEvent(Category logger, Appender appender);
  // 重置LoggerRepository相关的配置
  public abstract void resetConfiguration();

目前log4j提供了2种基础实现,分别是Hierarchy和NOPLoggerRepository。

  • NOPLoggerRepository的目的是在获取LoggerRepository异常失败时,返回一个接口实现为空的NOPLoggerRepository。
  • NOPLoggerRepository即是No-operation implementation of LoggerRepository。对于Hierarchy,顾名思义其就是将日志记录器维护成等级父子关系的Hierarchy实现。

3.1Hierarchy

Hierarchy类签名如下,实现了LoggerRepository、RendererSupport(处理日志信息渲染)、ThrowableRendererSupport(处理异常信息渲染)等接口。

public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport

Hierarchy有成员属性如下:

  
  // 日志记录器创建工厂,在获取日志记录器而当前仓库中不存在时,会使用此工厂进行创建日志记录器
  private LoggerFactory defaultFactory;
  // 登记相关的监听者,在出现相关事件时,会循环遍历listeners中的所有监听者并调用监听者的方法
  private Vector listeners;
  // 具体维护了仓库中的所有日志记录器,在ht相关存取时有同步操作
  Hashtable ht;
  // 表示根日志记录器,即rootLogger
  Logger root;
  // RendererMap维护了待渲染的对象和渲染器之间的映射关系
  RendererMap rendererMap;
  // 日志级别,int类型
  int thresholdInt;
  // 日志级别,Level类型
  Level threshold;
  // 是否打印过没有Appender相关警告信息,打印后设置为true,即只会打印一次
  boolean emittedNoAppenderWarning = false;
  // 异常信息渲染器
  private ThrowableRenderer throwableRenderer = null;

Hierarchy的构造函数如下,只有一个根rootLogger形参:

  public Hierarchy(Logger root) {
    ht = new Hashtable();
    listeners = new Vector(1);
    this.root = root;
    // Enable all level levels by default.
    setThreshold(Level.ALL);
    this.root.setHierarchy(this);
    rendererMap = new RendererMap();
    defaultFactory = new DefaultCategoryFactory();
  }

Hierarchy的几个常规接口实现都是对成员属性的简单封装,仅举几个例子如下:

  // 设置日志级别
  public void setThreshold(Level l) {
    if(l != null) {
      thresholdInt = l.level;
      threshold = l;
    }
  }
  // 添加一个事件监听器
  public void addHierarchyEventListener(HierarchyEventListener listener) {
    if(listeners.contains(listener)) {
      LogLog.warn("Ignoring attempt to add an existent listener.");
    } else {
      listeners.addElement(listener);
    }
  }
  // 通知调用监听器,处理事件回调
  public void fireAddAppenderEvent(Category logger, Appender appender) {
    if(listeners != null) {
      int size = listeners.size();
      HierarchyEventListener listener;
      for(int i = 0; i < size; i++) {
        listener = (HierarchyEventListener) listeners.elementAt(i);
        listener.addAppenderEvent(logger, appender);
      }
    }
  }
  // 获取根rootLogger
  public Logger getRootLogger() {
    return root;
  }

Hierarchy的一个核心方法是getLogger,如果对应名字的日志记录器在Hierarchy不存在则使用对应的LoggerFactory创建一个日志记录器,新创建的Logger会维护在Hierarchy的HashTable ht成员属性中,并维护好日志记录器的父子等级关系。例如:

  • 如果a.b.c日志记录器逻辑上的父节点a.b存在,则更新a.b.c日志记录器的parent属性值为a.b,维护好父子关系。
  • 如果a.b.c记录器逻辑上的父节点a.b不存在,则创建一个ProvisionNode节点且名字为a.b,将a.b.c维护在此ProvisionNode容器中,但a.b.c的父节点等级为根节点rootLogger。
  • ProvisionNode继承自Vector,是一个维护子日志记录器的容器,针对这里的a.b.c和a.b,方便找寻a.b名字上的子节点,这样在增加实际的日志记录器a.b后,可以方便的更新子节点如a.b的父节点(现在是rootLogger,需要更正为a.b)。

一份日志记录器等级状态图:

Hierarchy

具体有2个重载实现如下:

  // 委托给另一个重载,使用默认的defaultFactory
  public Logger getLogger(String name) {
    return getLogger(name, defaultFactory);
  }

  public Logger getLogger(String name, LoggerFactory factory) {
    CategoryKey key = new CategoryKey(name);
    Logger logger;
    synchronized(ht) {
      Object o = ht.get(key);
      // 如果指定名字的日志记录器不存在,则使用factory创建一个实例,并维护等级关系
      if(o == null) {
        logger = factory.makeNewLoggerInstance(name);
        logger.setHierarchy(this);
        ht.put(key, logger);
        // 更新当前节点的父节点
        updateParents(logger);
        return logger;
      } else if(o instanceof Logger) {
        // 如果存在指定名字的日志记录器,返回即可
        return (Logger) o;
      } else if (o instanceof ProvisionNode) {
        //System.out.println("("+name+") ht.get(this) returned ProvisionNode")
        logger = factory.makeNewLoggerInstance(name);
        logger.setHierarchy(this);
        ht.put(key, logger);
        // 当前节点是ProvisionNode,现在变为实际日志记录器节点,需要更新子节点对应的父节点
        updateChildren((ProvisionNode) o, logger);
        // 更新当前节点的父节点
        updateParents(logger);
        return logger;
      } else {
        // 不存在此情况,仅为通过编译检查
        return null;  // but let's keep the compiler happy.
      }
    }
  }

  final private void updateParents(Logger cat) {
    String name = cat.name;
    int length = name.length();
    boolean parentFound = false;
    // if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z"
    for(int i = name.lastIndexOf('.', length-1); i >= 0; i = name.lastIndexOf('.', i-1))  {
      String substr = name.substring(0, i);
      CategoryKey key = new CategoryKey(substr);
      Object o = ht.get(key);
      if(o == null) {
        // 父节点不存在,则创建ProvisionNode节点代替未来可能的父节点,尝试下一次循环
        ProvisionNode pn = new ProvisionNode(cat);
        ht.put(key, pn);
      } else if(o instanceof Category) {
        // 找到最近祖先,可以放弃循环
        parentFound = true;
        cat.parent = (Category) o;
        break; 
      } else if(o instanceof ProvisionNode) {
        // 此次循环找到节点为ProvisionNode节点,将cat登记到其中
        ((ProvisionNode) o).addElement(cat);
      } else {
        Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht.");
        e.printStackTrace();
      }
    }
    // 如果未找到父节点,则将父节点设置为根日志记录器
    if(!parentFound)
      cat.parent = root;
  }


  final private void updateChildren(ProvisionNode pn, Logger logger) {
    // 更新ProvisionNode中登记的所有日志记录器的父节点为logger,如果其父节点还未更新正确的话
    final int last = pn.size();
    for(int i = 0; i < last; i++) {
      Logger l = (Logger) pn.elementAt(i);
      // Unless this child already points to a correct (lower) parent,
      // make cat.parent point to l.parent and l.parent to cat.
      // 需要更新logger的父节点,这里其实是logger的父节点更新为rootLogger
      if(!l.parent.name.startsWith(logger.name)) {
        logger.parent = l.parent;
        l.parent = logger;
      }
    }
  }

3.2LogManager

Hierarchy等LoggerRepository实现一般不会由log4j的用户直接使用。log4j也是在LoggerRepository基础之上封装了LogManager。LogManager持有RepositorySelector的实例根据上下文环境选择合适的LoggerRepository返回,LoggerRepository接口定义如下:

public interface RepositorySelector {
  /**
     Returns a {@link LoggerRepository} depending on the
     context. Implementors must make sure that a valid (non-null)
     LoggerRepository is returned.
  */
  public LoggerRepository getLoggerRepository();
}

LogManager使用RepositorySelector决策使用的LoggerRepository,并封装LoggerRepository的相关方法,举例如下:

  static public LoggerRepository getLoggerRepository() {
    if (repositorySelector == null) {
        repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
        guard = null;
        Exception ex = new IllegalStateException("Class invariant violation");
        String msg = "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
        if (isLikelySafeScenario(ex)) {
            LogLog.debug(msg, ex);
        } else {
            LogLog.error(msg, ex);
        }
    }
    return repositorySelector.getLoggerRepository();
  }

  /**
     Retrieve the appropriate {@link Logger} instance.  
  */
  public static Logger getLogger(final String name, final LoggerFactory factory) {
     // Delegate the actual manufacturing of the logger to the logger repository.
    return getLoggerRepository().getLogger(name, factory);
  } 

4.全景洞察

TODO一份Logger、Appender、Layout等的完整调用时序图,TODO IS NOT TO DO...

标签: none
评论列表
  1. 不错的文章,内容维妙维肖.禁止此消息:nolinkok@163.com

  2. 不错的文章,内容横扫千军.禁止此消息:nolinkok@163.com

  3. 不错的文章,内容出类拔萃.禁止此消息:nolinkok@163.com

  4. 好文章,内容才高八斗.禁止此消息:nolinkok@163.com

  5. 不错的文章,内容维妙维肖.禁止此消息:nolinkok@163.com

添加新评论