关于java:java日志相关介绍

0次阅读

共计 6532 个字符,预计需要花费 17 分钟才能阅读完成。

java 日志相干介绍

一、初期日志记录

回忆一下,本人在刚接触 java 的时候是怎么记录日志信息的。通常咱们会应用 System.out.println() 输入调试日志信息,应用 System.err.println() 输入谬误日志信息,应用 e.printStackTrace() 来输入异样堆栈信息。

实际上,在日志框架呈现之前,大家都是这样应用的;而当初则会被前辈们千叮咛万嘱咐,不要应用这些来记录日志信息。而为什么有了日志框架就不能应用这些原始的记录形式了,大家有没有认真思考过?

其实 sonar 中有一条规定很具体地为咱们解释了起因:

Standard outputs should not be used directly to log anything

  • 异味
  • 次要
  • 次要代码

When logging a message there are several important requirements which must be fulfilled:

  • The user must be able to easily retrieve the logs
  • The format of all logged message must be uniform to allow the user to easily read the log
  • Logged data must actually be recorded
  • Sensitive data must only be logged securely

If a program directly writes to the standard outputs, there is absolutely no way to comply with those requirements. That’s why defining and using a dedicated logger is highly recommended.

即,规范输入流曾经无奈满足咱们对于日志记录的需要了,这时日志记录框架便诞生了。

二、日志框架演进概述

通常咱们目前应用的框架是 slf4j 和 logback 联合的形式,你是否有过纳闷,记录日志为什么要是用两个日志框架呢,他们的职责又别离是什么?

1.log4j

早年,应用 log4j 框架来记录日志,应用如下代码来输入日志

import org.apache.log4j.Logger;
省略
Logger logger = Logger.getLogger(Test.class);
logger.trace(“trace”);
省略

2. J.U.L

然而后续,JDK 退出了J.U.L(java.util.logging),推出了官网的日志记录框架。这是一部分人想要把本人我的项目中的日志记录框架改为 J.U.L,须要怎么做呢?找出所有应用 log4j api 的代码,将其手动一行一行改为 J.U.L 的 api,即

import java.util.logging.Logger;
省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest(“finest”);
省略

3. J.C.L

能够看出,切换日志记录框架非常不不便,日后再呈现更优良的日志记录框架该怎么办呢。这时候 Apache 的JCL(commons-logging)诞生了。JCL 是一个 Log Facade(即日志门面框架),只提供 Log API,不提供实现,而后有 Adapter 来应用 Log4j 或者 JUL 作为 Log Implementation。真正干事的还是前方的日志记录框架(log4j、J.U.L)。目标就是之后再呈现更优良的日志记录框架的时候,开发者不必改代码(即不必切换 log api),只需改配置就可平滑切换到新的日志记录框架中。

4. SLF4J & Logback

事件到这里仿佛曾经尘埃落定了,实现了 Log Facade + Log 两层框架的体系。这套框架也具备了下面所说的日志记录的四项能力。

然而咱们目前应用的日志门面框架通常是 SLF4J,而不是最后的 JCL。那 SLF4J 是具备了哪些更优良的品质,而让咱们去抉择了它呢?其中比拟重要的一点是 SLF4J 中的占位符模式,可能肯定水平上缩小字符串拼接的开销。

JCL 举荐写法:

if (logger.isDebugEnabled()) {
logger.debug(“start process request, url:” + url);
}

SLF4J 写法:

logger.debug(“start process request, url:{}”, url);

logback 则是继承自 log4j,减少了异步 logger、filter 等个性。

5. Log4j2

包含日志门面框架 log4j-api 和日志记录框架 log4j-core,由保护 log4j 的人开发而来,不兼容 log4j

上面放两个各框架间的关系图来感触下:

上面以 SLF4J 和 Logback 为例来讲一下日志框架应用:

三、Logback

Logback is intended as a successor to the popular log4j project

这里就不从头说 logback 的所有配置了,logback 的外围配置都在 logback.xml 文件中,这里讲几个罕用的配置:

<?xml version=”1.0″ encoding=”UTF-8″?>
<configuration debug=”false” scan=”true” scanPeriod=”30 seconds”>
<statusListener class=”ch.qos.logback.core.status.NopStatusListener” />
<appender name=”stdout” class=”ch.qos.logback.core.ConsoleAppender”>
<filter class=”ch.qos.logback.classic.filter.ThresholdFilter”>
<level>${logback.stdout.level}</level>
</filter>
<Target>System.out</Target>
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS}%-5level[%logger{20}:%L] %msg%n
</pattern>
</encoder>
</appender>
<appender name=”R”
class=”ch.qos.logback.core.rolling.RollingFileAppender”>
<File>${LOG_HOME}/${SYSTEM_NAME}_stdout_${ip}_${port}.log</File>
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS}%-5level[%logger{20}:%L] %msg%n
</pattern>
</encoder>
<rollingPolicy class=”ch.qos.logback.core.rolling.TimeBasedRollingPolicy”>
<fileNamePattern>${LOG_HOME}/${SYSTEM_NAME}_stdout_${ip}_${port}.%d.log
</fileNamePattern>
</rollingPolicy>
</appender>
<logger name=”org.springframework” level=”error” />
<logger name=”org.apache” level=”error” />
<root level=”${logback.root.level}”>
<appender-ref ref=”stdout” />
<appender-ref ref=”R” />
</root>
</configuration>

  • 配置文件语法:

    • 构造:<configuration>元素、0 个或多个 <appender> 元素、0 个或多个 <logger> 元素、最多一个 <root> 元素
    • 标签名大小写敏感
  • <configuration>:logback 将写入日志事件的工作委托给一个名为 appender 的组件。

    • debug:管制是否输入 logback 自身的一些状态信息
    • scan="true"当配置文件更改时,主动加载配置,默认扫描距离 1min
    • scanPeriod="30 seconds"每 30s 主动扫描一次配置看是否有更改
  • <statusListener>:状态信息监听器

    • OnConsoleStatusListener:同debug=true,将状态信息打印到控制台中
    • protected PrintStream getPrintStream() {
      return System.out;
      }
    • NopStatusListener:抛弃所有状态信息
    • public void addStatusEvent(Status status) {
      // nothing to do
      }
    • OnErrorConsoleStatusListener:在控制台打印错误信息
    • protected PrintStream getPrintStream() {
      return System.err;
      }
  • <appender>

    • name:appender 的名字,用于下文 logger 或 root 援用
    • class:appender 所采纳的具体类

      • ch.qos.logback.core.ConsoleAppender:将日志事件附加到控制台,就是通过 System.out 或者 System.err 来进行输入。默认通过前者。
      • ch.qos.logback.core.rolling.RollingFileAppender:将日志事件输入到文件中。通过 file 来指定指标文件。在满足了特定的条件之后,将日志输入到另外一个文件。
    • filter

      • ch.qos.logback.classic.filter.ThresholdFilter:基于给定的临界值来过滤事件。如果事件的级别等于或高于给定的临界值,当调用 decide() 时,ThresholdFilter 将会返回 NEUTRAL。然而事件的级别低于临界值将会被回绝。
      • ch.qos.logback.classic.filter.LevelFilter:事件的级别与配置的级别相等,会调用 onMatch 属性配置,不相等会调用 OnMismatch 属性配置
    • Target

      • ConsoleAppender应用,设置为_System.out_ 或 System.err_。默认为 _System.out
    • encoderencoder 将日志事件转换为字节数组,同时将字节数组写入到一个 OutputStream 中。

      • patternPatternLayoutEncoder
    • rollingPolicy

      • TimeBasedRollingPolicy
      • SizeAndTimeBasedRollingPolicy
  • <logger>

    • name
    • level:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,INHERITED,NULL。当 level 的值为 INHERITED 或 NULL 时,将会强制 logger 继承上一层的级别
    • <appender-ref>:应用的 appender,会继承上一级的 appender
  • <root>:与 <logger> 类似,根 logger,不能设置 name、additivity,level 不能设置为 INHERITED,NULL。

四、SLF4J

  • 创立 Logger

    • Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    • 集成 lombok 插件,能够在类上应用 @Slf4j 创立
  • 应用

    • logger.info("some message");
    • logger.error("some message", e)
    • 占位符模式:logger.debug("some message: {}", message, e)
    • Fluent Logging API(slf4j-api 2.0.0 版本以上):

      • logger.atInfo().log("some message")
      • logger.atDebug().addArgument(newT).addArgument(oldT).log("Temperature set to {}. Old temperature was {}.");
      • logger.atDebug().addKeyValue("oldT", oldT).addKeyValue("newT", newT).log("Temperature changed."); == logger.debug("oldT={} newT={} Temperature changed.", newT, oldT);

五、日志规约(《阿里代码规约》)

1.【强制】利用中不可间接应用日志零碎(Log4j、Logback)中的 API,而应依赖应用日志框架 SLF4J 中的 API,应用门面模式的日志框架,有利于保护和各个类的日志解决形式对立。

2.【强制】在日志输入时,字符串变量之间的拼接应用占位符的形式。

_阐明_:因为 String 字符串的拼接会应用 StringBuilder 的 append()形式,有肯定的性能损耗。应用占位符仅是替换动作,能够无效晋升性能。

_正例_:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

3.【强制】对于 trace/debug/info 级别的日志输入,必须进行日志级别的开关判断。

阐明:尽管在 debug(参数)的办法体内第一行代码 isDisabled(Level.DEBUG_INT)为真时(Slf4j 的常见实现 Log4j 和 Logback),就间接 return,然而参数可能会进行字符串拼接运算。此外,如果 debug(getName())这种参数内有 getName()办法调用,无谓节约办法调用的开销。

如果参数内有办法调用或者字符串拼接,则必须进行日志级别的开关判断,否则不须要强求

_正例_:

// 如果判断为真,那么能够输入 trace 和 debug 级别的日志
if (logger.isDebugEnabled()) {
logger.debug(“Current ID is: {} and name is: {}”, id, getName());
}

4.【强制】异样信息应该包含两类信息:案发现场信息和异样堆栈信息。如果不解决,那么通过关键字 throws 往上抛出。

_正例_:logger.error(param.toString() + "_" + e.getMessage(), e);

5.【举荐】审慎地记录日志。生产环境禁止输入 debug 日志;有选择地输入 info 日志;如果应用 warn 来记录刚上线时的业务行为信息,肯定要留神日志输出量的问题,防止把服务器磁盘撑爆,并记得及时删除这些察看日志。

阐明:大量地输入有效日志,不利于零碎性能晋升,也不利于疾速定位谬误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来益处?

6.【举荐】能够应用 warn 日志级别来记录用户输出参数谬误的状况,防止用户投诉时,莫衷一是。如非必要,请不要在此场景打出 error 级别,防止频繁报警。

阐明:留神日志输入的级别,error 级别只记录零碎逻辑出错、异样或者重要的错误信息。

六、后记

规定究竟是死的,记日志的时候须要联合具体场景多做思考,在哪里记日志能帮忙后续排查问题?哪里的日志又是重复记录乃至有效日志?联合异样解决来看,哪些异样应该抛给下层调用方,依据下层利用不同场景抉择不同的解决形式;哪些异样应该就地给解决了,解决异样的时候肯定要记录一下异样信息。

附:参考链接 & 文档

  1. Standard outputs should not be used directly to log anything
  2. Java 日志框架解析(上) – 历史演进
  3. 架构师必备,带你弄清凌乱的 JAVA 日志体系!
  4. 13 | 日志:日志记录真没你设想的那么简略
  5. logback 中文手册
  6. SLF4J user manual
  7. 《阿里代码规约》
正文完
 0