序
前段时间 Log4j 爆出相干平安问题,各大公司都在紧急修复该问题,我所处的公司也不例外。但在排查相干问题时,发现对 Java 日志框架整体的认知还是比拟乱的,于是乎,就收罗各大文章对其进行理解并记录,以备后续温习或用之。
Java 日志历史
- 最开始 Java 利用都是应用
System.out/err.println()
的形式来跟踪本人的程序运行状况; - 1996 年,E.U.SEMPER(欧洲平安电子市场)我的项目编写己的跟踪 API,该 API 演变为Log4j,次要作者就有:CekiGülcü,后该我的项目退出了 Apache 基金会我的项目;
- 2002 年 2 月,Java1.4 公布,Sun 推出了本人的日志库:JUL(Java Util Logging);
- 2002 年 8 月,Apache 推出了日志接口 JCL(Jakarta Commons Logging)(日志形象层);
- 因为一些起因,CekiGülcü来到了 Apache,在 2005 年的时候推出了一套新的日志接口Slf4j(Simple Logging Facade for Java);
- 因为应用 Slf4j,之前的日志产品都不是正统的Slf4j 的实现,在 2006 年的时候,CekiGülcü又撸了一套日志产品LogBack,且是完满实现了Slf4j;
-
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();
@Log4j2
public 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 日志格局标准,拿走不谢!