日志框架 logback & log4j2

Logback project

log4j的延续

划分为3个模块:logback-core, logback-classic, logback-access

logback-core是其他两个模块的基础. logback-classic模块可以看作是log4j的升级版.提供了SLF4J API, 以便轻易在在其他日志框架间切换. logback-access整合了servlet容器,如tomcat和jetty,以提供HTTP-access日志

官网: https://logback.qos.ch/index.html

quick-start: https://wiki.base22.com/btg/how-to-setup-slf4j-and-logback-in-a-web-app-fast-35488048.html

一个看起来更舒服的网站: https://www.baeldung.com/logback

详解:

e.g.

1. 根节点<configuration>

属性:

scan: true/false; default:true 配置文件发生改变时重新加载

scanPeriod: 数字; 设置检测配置文件是否有修改的时间间隔, 默认单位为毫秒, scan=true时生效,默认间隔为1分钟.

debug: true/false; default:false 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态.

2. <perproty> 配置全局变量 在下文可使用${}引用

<property name="encoding" value="UTF-8"/>

3. <root>

根logger,是所有<logger>的上级。参见<logger>
level: 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL和OFF,不能设置为INHERITED或者同义词NULL。 默认是DEBUG.

4. <logger>

属性:

name: logger名称,可以获取logger的属性

addtivity: 是否向上级loger传递打印信息。默认是true。

level: 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL和OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。

         如果未设置此属性,那么当前loger将会继承上级的级别。

appender-ref: 可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个loger。参考<appender>标签.

获取logger的常用方法:

static final Logger LOG = LoggerFactory.getLogger(XXX.class);

static final Logger LOG = LoggerFactory.getLogger("logger_name");

注:

通过logger名称获取logger,通常在logback.xml中已定义.

若使用未定义的logger_name,则将在logger"树形结构"中成一个新的logger.

这个logger本身不持有任何appender,将向上级传递打印信息.

若使用XXX.class,且类名未在xml中定义,则按照包分割符'.'和类分隔符'$'的路径生成一个树枝的logger,并返回叶子节点的logger

#ch.qos.logback.classic.LoggerContext 

 public final Logger getLogger(final Class clazz) {
    return getLogger(clazz.getName());
  }

  public final Logger getLogger(final String name) {

    if (name == null) {
      throw new IllegalArgumentException("name argument cannot be null");
    }

    // if we are asking for the root logger, then let us return it without
    // wasting time
    if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
      return root;
    }

    int i = 0;
    Logger logger = root;

    // check if the desired logger exists, if it does, return it
    // without further ado.
    Logger childLogger = (Logger) loggerCache.get(name);
    // if we have the child, then let us return it without wasting time
    if (childLogger != null) {
      return childLogger;
    }

    // if the desired logger does not exist, them create all the loggers
    // in between as well (if they don't already exist)
    String childName;
    while (true) {
      int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
      if (h == -1) {
        childName = name;
      } else {
        childName = name.substring(0, h);
      }
      // move i left of the last point
      i = h + 1;
      synchronized (logger) {
        childLogger = logger.getChildByName(childName);
        if (childLogger == null) {
          childLogger = logger.createChildByName(childName);
          loggerCache.put(childName, childLogger);
          incSize();
        }
      }
      logger = childLogger;
      if (h == -1) {
        return childLogger;
      }
    }
  }

5. <appender>

logback把日志时间写出到组件的任务成为appender。 所有的appender都必须实现appender接口。 appender中的泛型E,在logback-classic中为ILoggingEvent,在logback-access中为AccessEvent。

事件实际被写出时,格式化的步骤将交给encoder/Layout。(区别及对比见下)

抽象类AppenderBase中的doAppend方法被声明为synchronized,是同步,即,在多线程情况下,该方法是阻塞的。 抽象类UnsynchronizedAppenderBase中的doAppend方法则不是同步的,并采用将成员guard声明为ThreadLocal。即在多线程下,该方法是异步非阻塞的。

常用appender: ConsoleAppender FileAppender RollingFileAppender DailyRollingFileAppender

上述常用appender均继承自UnsynchronizedAooenderBase。

虽然上述appender都有一些独有的属性。详情见参考。

共通原理:

filter用于判断事件是否可以被append。

事件被append后,调用subAppend方法来写出。写出时,OutputStreamAppender中存在锁,需要同步。

写出时,调用encoder。encoder的核心功能为:将事件转化为字符串,并写出到对应输出流。

配置时,encoder节点中若包含layout和charset节点,则在将事件转化为字符串的过程中,用调用layout的方法来格式化字符串,指定对应字符集。

参考: https://www.jianshu.com/p/5f79f69fa24e https://logback.qos.ch/manual/appenders.html

6. <encoder> & <layout>

encoder负责将事件转化为字符串的字符数组,并且将其写出到输出流。

encoder在0.9.19后才被引入logback。

在之前的版本,logback使用layout来将事件转化为String,然后用java.io.Writer写出。

且,在FileAppender中,则必须使用PatternLayout。

  1. 9.19后,尽可能的在使用encoder,layout则不再被推荐使用。

layout仅能将事件转化为String,而encoder既可以控制对事件的转化,又可以控制其写出。

对于0.9.19之前的版本,提供了LayoutWrappingEncoder,内持有Layout作为成员变量。 来弥补layout和encoder使用上的衔接。

更常用的类是PatternLoyoutEncoder。为LayoutWriapping的子类。

p.s: layout可以用于自定义样式。

参考: https://logback.qos.ch/manual/encoders.html https://logback.qos.ch/manual/layouts.html

个人理解:

 

7. slf4j & log4j & logback & log4j2:

 

接口: slf4j common-logging-api 实现框架: log4j logback log4j2 common-logging-impl

使用logback依赖:

slf4j-api (slf4j接口)
    logback-classic (logback服务于slf4j的”驱动”)
    logback-core (logback日志实现)

使用log4j依赖:

slf4j-api (slf4j接口)
    slf4j-log4j (log4j 服务于slf4j的”驱动”)
    log4j (log4j 日志实现)

如果系统有依赖log4j日志体系,想统一对接到logback,则需要依赖:log4j-over-slf4j

logback性能优于log4j

https://stackoverflow.com/questions/39562965/what-is-the-difference-between-log4j-slf4j-and-logback https://blog.csdn.net/butingnal/article/details/53946841

log4j2性能由于logback? logback vs log4j2: https://blog.csdn.net/yjh1271845364/article/details/70888262

log4j2个人理解:

如何控制项目里的第三方依赖打印出的日志? 日志桥接? 参考spring文档介绍

p.s. 其实还写了ppt。没上传。