前段时间Log4j爆出相干平安问题,各大公司都在紧急修复该问题,我所处的公司也不例外。但在排查相干问题时,发现对Java日志框架整体的认知还是比拟乱的,于是乎,就收罗各大文章对其进行理解并记录,以备后续温习或用之。

Java日志历史

  1. 最开始Java利用都是应用System.out/err.println()的形式来跟踪本人的程序运行状况;
  2. 1996年,E.U.SEMPER(欧洲平安电子市场)我的项目编写己的跟踪API,该API演变为Log4j,次要作者就有:CekiGülcü,后该我的项目退出了Apache基金会我的项目;
  3. 2002年2月,Java1.4公布,Sun推出了本人的日志库:JUL(Java Util Logging)
  4. 2002年8月,Apache推出了日志接口 JCL( Jakarta Commons Logging)(日志形象层);
  5. 因为一些起因,CekiGülcü来到了Apache,在2005年的时候推出了一套新的日志接口Slf4j(Simple Logging Facade for Java)
  6. 因为应用Slf4j,之前的日志产品都不是正统的Slf4j的实现,在2006年的时候,CekiGülcü又撸了一套日志产品LogBack,且是完满实现了Slf4j
  7. Slf4j+LogBack的模式冲击了之前的JCL+Log4j的模式,Apache就在2012年推出了本人的新我的项目Log4j2,该我的项目是齐全不兼容Lg4j1.x的。

    适配模式

    后面咱们提到, Slf4j 的呈现晚于 JUL、JCL、log4j 等日志框架,所以,这些日志框架也不可能就义掉版本兼容性,将接口革新成合乎 Slf4j 接口标准。 Slf4j 也当时思考到了这个问题,所以,它不仅仅提供了对立的接口定义,还提供了针对不同日志框架的适配器(slf4j-log4j12/slf4j-jdk14);´然而其实之前很多Java利用应该依赖的JCL,所以光有日志产品适配包还不够,于是有了日志规范的适配包(slf4j-jcl)。

定义

顾名思义,适配模式就是用做适配的,它将不兼容的接口转换为可兼容的接口,让本来因为接口不兼容而不能一起工作的类能够一起工作。

实现形式

ITarget 示意要转化成的接口定义。Adaptee 是一组不兼容 ITarget 接口定义的接口,Adaptor 将 Adaptee 转化成一组合乎 ITarget 接口定义的接口。

类适配器

// 类适配器: 基于继承public interface ITarget {  void f1();  void f2();  void fc();}public class Adaptee {  public void fa() { //... }  public void fb() { //... }  public void fc() { //... }}public class Adaptor extends Adaptee implements ITarget {  public void f1() {    super.fa();  }    public void f2() {    //...从新实现f2()...  }    // 这里fc()不须要实现,间接继承自Adaptee,这是跟对象适配器最大的不同点}

对象适配器

// 对象适配器:基于组合public interface ITarget {  void f1();  void f2();  void fc();}public class Adaptee {  public void fa() { //... }  public void fb() { //... }  public void fc() { //... }}public class Adaptor implements ITarget {  private Adaptee adaptee;    public Adaptor(Adaptee adaptee) {    this.adaptee = adaptee;  }    public void f1() {    adaptee.fa(); //委托给Adaptee  }    public void f2() {    //...从新实现f2()...  }    public void fc() {    adaptee.fc();  }}

具体应用哪种有两者判断规范

  • Adaptee 接口的个数
  • Adaptee 和 ITarget 的符合水平

若接口个数不多则两种实现形式都能够;否则则判断适配的两者之间的接口定义是否大部分都雷同,若雷同,则能够应用类适配器(基于继承)这样能够复用代码,缩小代码的冗余性;若少部分雷同,则能够应用对象适配器(基于组合)这样能够让代码更灵便。

Tips:代理模式在不扭转原始类接口的条件下,为原始类定义一个代理类,次要目标是管制拜访,而非增强性能,这是它跟装璜器模式最大的不同。

Slf4j中的实现计划举例

// slf4j对立的接口定义package org.slf4j;public interface Logger {  public boolean isTraceEnabled();  public void trace(String msg);  public void trace(String format, Object arg);  public void trace(String format, Object arg1, Object arg2);  public void trace(String format, Object[] argArray);  public void trace(String msg, Throwable t);   public boolean isDebugEnabled();  public void debug(String msg);  public void debug(String format, Object arg);  public void debug(String format, Object arg1, Object arg2)  public void debug(String format, Object[] argArray)  public void debug(String msg, Throwable t);  //...省略info、warn、error等一堆接口}// log4j日志框架的适配器// Log4jLoggerAdapter实现了LocationAwareLogger接口,// 其中LocationAwareLogger继承自Logger接口,// 也就相当于Log4jLoggerAdapter实现了Logger接口。package org.slf4j.impl;public final class Log4jLoggerAdapter extends MarkerIgnoringBase  implements LocationAwareLogger, Serializable {  final transient org.apache.log4j.Logger logger; // log4j   public boolean isDebugEnabled() {    return logger.isDebugEnabled();  }   public void debug(String msg) {    logger.log(FQCN, Level.DEBUG, msg, null);  }   public void debug(String format, Object arg) {    if (logger.isDebugEnabled()) {      FormattingTuple ft = MessageFormatter.format(format, arg);      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());    }  }   public void debug(String format, Object arg1, Object arg2) {    if (logger.isDebugEnabled()) {      FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());    }  }   public void debug(String format, Object[] argArray) {    if (logger.isDebugEnabled()) {      FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());    }  }   public void debug(String msg, Throwable t) {    logger.log(FQCN, Level.DEBUG, msg, t);  }  //...省略一堆接口的实现...}

桥接模式

然而,还有一种状况,比方援用的是第三方框架用的是JCL,并且最终应用JUL打印日志,然而你的零碎应用的是Slf4j,最终应用Log4j打印,那将会有两种输入,两种日志的输入环境不对立,但时候看日志的时候很麻烦,而后开发Slf4j的开发者为了让大家对立应用Slf4j,就又撸出了对应的桥接包(jul-to-slf4j/log4j-over-slf4j/jcl-over-slf4j)。

定义

将形象和实现解耦,让它们能够独立变动。定义中的“形象”,指的并非“抽象类”或“接口”,而是被形象进去的一套“类库”,它只蕴含骨架代码,真正的业务逻辑须要委派给定义中的“实现”来实现。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”。“形象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。

最佳实际

日志级别

  • TRACE【跟踪函数调用,不存在变量参数

    • 个别跟踪的是函数的调用,并且 TRACE 不应该含有变量参数,而仅能提醒函数的调用关系。
  • DEBUG【调试应用程序,存在变量参数

    • 个别用于细粒度级别上,对调试应用程序十分有帮忙,次要用于开发过程中打印一些运行信息。
  • INFO【利用程序运行过程,防止过多

    • INFO 音讯在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个能够用于生产环境中输入程序运行的一些重要信息,然而不能滥用,防止打印过多的日志。
  • WARN【合乎预期潜在的谬误或提醒

    • WARN 示意会呈现潜在谬误的情景,有些信息不是错误信息,然而也要给程序员一些提醒。该级别示意程序会主动调整到失常的状态,相似参数未传入,应用了默认的参数,仍合乎程序员预期之内的状况。
  • ERROR【呈现谬误,仍能运行

    • ERROR 指出尽管产生谬误事件,但依然不影响零碎的持续运行。打印谬误和异样信息,如果不想输入太多的日志,能够应用这个级别。个别在 WARN 之后的级别在打印谬误时,应该同时打印错误码。
  • FATEL【呈现谬误,不能运行

    • FATAL 指出每个重大的谬误事件将会导致应用程序的退出,这个级别比拟高,重大谬误,程序无奈复原,必须通过重启程序来解决。
    在 Log4j 中,日志级别的关系如下所示:
    ALL<TRACE<DEBUG<INFO<WARN<ERROR<FATAL<OFF
    设置了对应的级别之后,日志框架就只调用大于等于这个级别的办法。Log4j 倡议只应用如下的四个界别:
    DEBUG<INFO<WARN<ERROR

日志性能

//  记录 DEBUG 日志,并设置只记录 >=INFO 级别的日志private String slowString(String s) {    System.out.println("slowString called via " + s);    try {        TimeUnit.SECONDS.sleep(1);    } catch (InterruptedException e) {    }    return "OK";}StopWatch stopWatch = new StopWatch();stopWatch.start("debug1");log.debug("debug1:" + slowString("debug1"));stopWatch.stop();stopWatch.start("debug2");log.debug("debug2:{}", slowString("debug2"));stopWatch.stop();stopWatch.start("debug3");if (log.isDebugEnabled())    log.debug("debug3:{}", slowString("debug3"));stopWatch.stop();@Log4j2public class LoggingController {...log.debug("debug4:{}", ()->slowString("debug4"));}

测试如图所示:

间接拼接字符串,占位符都会输入日志,当时判断日志级别或通过 lambda 表达式(Log4j2 日志 API)不会输入日志,缩小性能影响。

日志配置

  • 日志须要写入磁盘上的日志文件中,所以适当的应用滚动日志并且定时革除旧文件
  • 有的时候日志须要保留一点工夫,不便排查问题时追溯。
<configuration debug="false" scan="false">  <springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>  <property name="log.path" value="logs/${spring.application.name}"/>  <!-- 黑白日志格局 -->  <property name="CONSOLE_LOG_PATTERN"    value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>  <!-- 黑白日志依赖的渲染类 -->  <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>  <conversionRule conversionWord="wex"    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>  <conversionRule conversionWord="wEx"    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>  <!-- Console log output -->  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">    <encoder>      <pattern>${CONSOLE_LOG_PATTERN}</pattern>    </encoder>  </appender>  <!-- Log file debug output -->  <appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">    <file>${log.path}/debug.log</file>    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">      <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>      <maxFileSize>50MB</maxFileSize>      <maxHistory>30</maxHistory>    </rollingPolicy>    <encoder>      <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>    </encoder>  </appender>  <!-- Log file error output -->  <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">    <file>${log.path}/error.log</file>    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">      <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>      <maxFileSize>50MB</maxFileSize>      <maxHistory>30</maxHistory>    </rollingPolicy>    <encoder>      <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>    </encoder>    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">      <level>ERROR</level>    </filter>  </appender>  <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->  <root level="INFO">    <appender-ref ref="console"/>    <appender-ref ref="debug"/>    <appender-ref ref="error"/>  </root></configuration>

总结

参考链接

13 | 日志:日志记录真没你设想的那么简略
51 | 适配器模式:代理、适配器、桥接、装璜,这四个模式有何区别?
Java日志零碎历史从入门到解体
这份Java日志格局标准,拿走不谢!