Java 日志类库

Java 日志库是最能体现 Java 库在进化中的渊源关系的,在理解时重点理解日志框架本身和日志门面,以及比较好的时间等。要关注其历史渊源和设计(比如桥接),而具体在使用时查询接口即可,否则会陷入 JUL(Java Util Log),JCL(Commons Logging),Log4j,SLF4J,Logback,Log4j2 傻傻分不清楚的境地。

1. 日志库简介

理解日志库可以从下面三个角度去理解:

  • 最重要的一点是:区分日志系统日志门面
  • 其次是日志库的使用,包含配置与 API 使用;配置侧重于日志系统的配置,API 使用侧重于日志门面;
  • 最后是选型,改造和最佳实践等。

2. 日志库之日志系统

2.1 java.util.logging(JUC)

​ JDK1.4 开始,通过 java.util.logging 提供日志功能,虽然是官方自带的 log lib,JUL 的使用却不广泛。主要原因:

  • JUL 从 JDK1.4 才开始加入(2002 年),当时各种第三方 log lib 以及被广泛使用了;
  • JUL 早期存在性能问题,到 JDK1.5 才有了不错的进步,但现在和 Logback/Log4j2 相比还是有所不如;
  • JUL 的功能不如 Logback/Log4j2 等完善,比如 Output Handler 就没有 Log’back/Log4j2 的丰富,有时候需要自己来集成定制,又比如默认没有从 ClassPath 里加载配置文件的功能。

2.2 Log4j

​ Log4j 是 apache 的一个开源项目,创始人 Ceki Gulcu。Log4j 应该说是 Java 领域资格最老,应用最广的日志工具。Log4j 是高度可配置的,并可通过在运行时的外部文件配置。它根据记录的优先级别,并提供机制,以指示记录信息到许多的目的地,注入:数据库,文件,控制台,UNIX 系统日志等。

​ Log4j 中有三个主要组成部分:

  • loggers:负责捕获记录信息。
  • appenders:负责发布日志信息,以不同的首选目的地。
  • layouts:负责格式化不同风格的日志信息。

​ 官网地址:Apache Log4j :: Apache Log4j

​ Log4j 的短板在于性能,在 Logback 和 Log4j2 出来之后,Log4j 的使用也减少了。

2.3 Logback

​ Logback 是由 log4j 创始人 Ceki Gulcu 设计的又一个开源日志组件,是作为 Log4j 的继承者来开发的,提供了性能更好的实现,异步 logger,Filter 等更多的特性。

​ logback 当前分成三个模块:logback-core、logback-classic 和 logback-access。

  • logback-core:是其它两个模块的基础模块。
  • logback-classic:是 log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API 使你可以很方便地更换成其它日志系统如 log4j 或 JDK14 Logging。
  • logback-access:访问模块与 Servlet 容器集成提供通过 Http 来访问日志的功能。

​ 官网地址:Logback Home

2.4 Log4j2

​ 维护 Log4j 的人为了性能又高出了 Log4j2。

​ Log4j2 和 Log4j1.x 并不兼容,设计上很大程度上模仿了 SLF4J/Logback,性能上也获得了很大的提升。

​ Log4j2 也做了 Facade/Implementation 分离的设计,分成了 log4j-api 和 log4j-core。

​ 官网地址:Apache Log4j :: Apache Log4j

2.5 Log4j vs Logback vs Log4j2

从性能上 Log4j2 要强,但从生态上 Logback + SLF4J 优先。

初步对比

logback 和 log4j2 都宣称自己是 log4j 的后代,一个是出于同一个作者,另一个则是在名字上根正苗红。

​ 比较 log4j2 和 logback:

  • log4j2 比 logback 更新:log4j2 的 GA 版在 2014 年底才推出,比 logback 晚了好几年,这期间 log4j2 确实吸收了 slf4j 和 logback 的一些优点(比如日志模板),同时应用了不少的新技术;
  • 由于采用了更先进的锁机制和 LMAX Disruptor 库,log4j2 的性能优于 logback,特别是在多线程环境下和使用异步日志的环境下;
  • 二者都支持 Filter(应该说是 log4j2 借鉴了 logback 的 Filter),能够实现灵活的日志记录规则(例如仅对一部分用户记录 debug 级别的日志);
  • 二者都支持对配置文件的动态更新;
  • 二者都能够适配 slf4j,logback 与 slf4j 的适配应该会更好一些,毕竟省掉了一层适配库;
  • logback 能够自动压缩/删除旧日志;
  • logback 提供了对日志的 HTTP 访问功能;
  • log4j2 实现了"无垃圾"和"低垃圾"模式。简单地说,log4j2 在记录日志时,能够重用对象(如 String 等),尽可能避免实例化新的临时对象,减少因日志记录产生的垃圾对象,减少垃圾回收带来的性能下降;
  • log4j2 和 logback 各有所长, 总体来说,如果对性能要求比较高的话,log4j2 相对还是较优的选择。

性能对比

附上 log4j2 与 logback 性能对比的 benchmark,这份 benchmark 是 Apache Logging 出的,仅供参考。

​ 同步写文件日志的 benchmark:

在这里插入图片描述

​ 异步写日志的 benchmark:

在这里插入图片描述

​ 当然,这些 benchmark 都是在日志 Pattern 中不包含 Location 信息(如日志代码行号,调用者信息,Class 名/源码文件名等)时测定的,如果输出 Location 信息的话,性能谁也拯救不了:

在这里插入图片描述

3. 日志库之日志门面

3.1 common-logging

common-logging 是 apache 的一个开源项目。也称 Jakarta Commons Logging,缩写 JCL。

​ common-logging 的功能是提供日志功能的 API 接口,本身并不提供日志的具体实现(当然,common-logging 内部有一个 Simple logger 的简单实现,但是功能很弱,直接忽略),而是在运行时动态绑定日志实现组件来工作(如 log4j、java.util.logging)。

​ 官网地址:Apache Commons Logging – Overview

3.2 slf4j

全称为 Simple Logging Facade for Java,即 java 简单日志门面。

作者也是 Ceki Gulcu!

​ 类似于 Common-Logging,slf4j 是对不同日志框架提供的一个 API 封装,可以在部署的时候不修改配置即可接入一种日志实现方案。但是,slf4j 在编译时静态绑定真正的 Log 库。使用 SLF4J 时,如果你需要使用某一种日志实现,那么你必须选择正确的 SLF4J 的 jar 包的集合(各种桥接包)。

​ 官网地址:SLF4J

在这里插入图片描述

3.3 common-logging vs slf4j

slf4j 库类似于 Apache Common-Logging。但是,它在编译时静态绑定真正的日志库。这点似乎很麻烦,其实也不过是导入桥接 jar 包而已。

​ sjf4j 一大亮点是提供了更方便的日志记录方式:

​ 不需要使用 logger.isDebugEnabled() 来解决日志因为字符拼接产生的性能问题。slf4j 的方式是使用 {} 作为字符串替换符,形式如下:

logger.debug("id:{},name:{}", id, name);

4. 日志库使用方案

​ 使用日志解决方案基本可分为三步:

  • 引入 jar 包
  • 配置
  • 使用 API

​ 常见的各种日志解决方案的第 2 步和第 3 步基本一样,实施上的差别主要在第 1 步,也就是使用不同的库。

4.1 日志库 jar 包

​ 这里首选推荐使用 slf4j + logback 的组合。

​ 如果你习惯了 common-logging,可以选择 common-logging + log4j。

​ 强烈建议不要直接使用日志实现组件(logback、log4j、java.util.logging),理由前面也说过,就是无法灵活替换日志库。

​ 还有一种情况:你的老项目使用了 common-logging,或是直接使用日志实现组件。如果修改老的代码,工作量太大,需要兼容处理。在下文,都将看到各种应对方法。

slf4j 直接绑定日志组件

  • slfj + loback

​ 添加依赖到 pom.xml 即可。

​ logback-classic-1.0.13 jar 会自动将 slf4j-api-1.7.21.jar 和 logabck-core-1.0.13.jar 也添加到你的项目中。

<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.0.13</version>
</dependency>
  • slf4j + log4j

​ 添加依赖到 pom.xml 中即可。

​ slf4j-log4j12-1.7.21.jar 会自动将 slf4j-api-1.7.21.jar 和 log4j-1.2.17.jar 也添加到你的项目中。

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.21</version>
</dependency>
  • slf4j + java.util.logging

​ 添加依赖到 pom.xml 中即可。

​ slf4j-jdk14-1.7.21.jar 会自动将 slf4j-api-1.7.21.jar 也添加到你的项目中。

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.21</version>
</dependency>

slf4j 兼容非 slf4j 日志组件

​ 在介绍解决方案前,先提一个概念 一一 桥接

  • 什么是桥接?

​ 假如你正在开发应用程序所调用的组件当中已经使用了 common-logging,这时你需要 jcl-over-slf4j.jar 把日志信息输出重定向到 slf4j-api,slf4j-api 再去调用 slf4j 实际依赖的日志组件。这个过程称为桥接。下图是官方的 slf4j 桥接策略图:

在这里插入图片描述

​ 从图中应该可以看出,无论你的老项目中使用的是 common-logging 或是直接使用 log4j、java.util.logging,都可以使用对应的桥接 jar 包来解决兼容问题。

  • slf4j 兼容 common-logging
<dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.12</version>
</dependency>
  • slf4j 兼容 log4j
<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.12</version>
</dependency>
  • slf4j 兼容 java.util.logging
<dependency><groupId>org.slf4j</groupId><artifactId>jul-to-slf4j</artifactId><version>1.7.12</version>
</dependency>
  • spring 集成 slf4j

​ 做 java web 开发,基本离不开 spring 框架。很遗憾,spring 使用的日志解决方案是 common-logging + log4j。

​ 所以,你需要一个桥接 jar 包:logback-ext-spring。

<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.1.3</version>
</dependency>
<dependency><groupId>org.logback-extensions</groupId><artifactId>logback-ext-spring</artifactId><version>0.1.2</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.12</version>
</dependency>

common-logging 绑定日志组件

  • common-logging + log4j
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>

4.2 日志库配置 - 针对于日志框架

log4j2 配置

​ log4j2 基本配置形式如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration><Properties><Property name="name1">value</Property><Property name="name2" value="value2"/></Properties><Filter type="type" ... /><Appenders><Appender type="type" name="name"><Filter type="type" ... /></Appender>...</Appenders><Loggers><Logger name="name1"><Filter type="type" ... /></Logger><Root level="level"><AppenderRef ref="name"/></Root></Loggers>
</Configuration>

​ 配置示例:

<?xml version="1.0" encoding="UTF-8" ?>
<Configuration><Properties><Property name="filename">target/test.log</Property></Properties><!-- 配置全局过滤器,只记录 trace 及以上级别的日志 --><Filte type="ThresholdFilter" level="trace"/><!-- 定义日志输出方式 --><Appenders><!-- 控制台输出 --><Appender type="Console"  name="STDOUT"><!-- 日志格式 --><Layout type="PatternLayout" pattern="%m MDC%X%n"/><!-- 过滤器:拒绝标记为 FLOW 的日志,接收标记为 EXCEPTION 的日志 --><Filters><Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismaatch="NEUTRAL"/><Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismaatch="ACCEPT"/></Filters></Appender><Appender type="Console" name="FLOW"><Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><Filters><Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismaatch="NEUTRAL"/><Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismaatch="DENY"/></Filters></Appender><!-- 文件输出 --><Appender type="File" name="File" fileName="${filename}"><!-- 日志格式 --><Layout type="PatternLayout"><Pattern>%d %p $C{1.} [%t] %m%n</Pattern></Layout></Appender></Appenders><!-- 定义日志记录器 --><Loggers><!-- 名为 org.apache.logging.log4j.test1 的日志记录器 --><Logger name="org.apache.logging.log4j.test1" level="DEBUG" additivity="FALSE"><!-- 过滤器:只有当 ThreadContext 中包含键值对 "test=123" 时才记录日志 --><Filter type="ThreadContextMapFilter"><KeyValuePair key="test" value="123"/></Filter></Logger><!-- 名为 org.apache.logging.log4j.test2 的日志记录器 --><Logger name="org.apache.logging.log4j.test2" level="DEBUG" additivity="FALSE"> <!--5--><!-- 引用名为 file 的 Appender --><AppenderRef ref="file"/></Logger><!-- 根日志记录器,所有未指定记录器的日志都会到这里 --><Root level="trace"><!-- 引用名为 STDOUT 的 Appender --><AppenderRef ref="STDOUT"/></Root></Loggers>
</Configuration>

Logback 配置

<?xml version="1.0" encoding="UTF-8" ?>
<!-- logback 中一共有 5 种有效级别,分别是 TRACE、DEBUG、INFO、WARN、ERROR,优先级依次从低到高 -->
<configuration scan="true" scanPeiriod="30 seconds" debug="false"><property name="DIR_NAME" value="spring"/><!-- 将记录日志打印到控制台 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><!-- RollingFilterAppender begin --><appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 根据时间来制定滚动策略 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><!-- 根据文件大小来制定滚动策略 --><trggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>30MB</maxFileSize></trggeringPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 根据时间来制定滚动策略 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><!-- 根据文件大小来制定滚动策略 --><trggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>10MB</maxFileSize></trggeringPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 根据时间来制定滚动策略 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><!-- 根据文件大小来制定滚动策略 --><trggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>10MB</maxFileSize></trggeringPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>WARN</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 根据时间来制定滚动策略 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><!-- 根据文件大小来制定滚动策略 --><trggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>10MB</maxFileSize></trggeringPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 根据时间来制定滚动策略 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><!-- 根据文件大小来制定滚动策略 --><trggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>10MB</maxFileSize></trggeringPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>DEBUG</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><appender name="SPRING" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 根据时间来制定滚动策略 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><!-- 根据文件大小来制定滚动策略 --><trggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>10MB</maxFileSize></trggeringPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern></encoder></appender><!-- RollingFilterAppender end--><!-- logger begin --><!-- 本项目的日志记录,分级打印 --><logger name="io.zhanbo.log" level="TRACE" additivity="false"><appender-ref ref="STDOUT"/><appender-ref ref="ERROR"/><appender-ref ref="WARN"/><appender-ref ref="INFO"/><appender-ref ref="DEBUG"/><appender-ref ref="TRACE"/></logger><!-- SPRING 框架日志 --><logger name="org.springframework" level="WARN" additivity="false"><appender-ref ref="SPRING"/></logger><logger level="TRACE"><appender-ref ref="ALL"/></logger><!-- logger end -->
</configuration>

log4j 配置

​ 完整的 log4j.xml 参考示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern"value="%d{yyyy-MM-dd HH:mm:ss,SSS\} [%-5p] [%t] %c{36\}.%M - %m%n"/></layout><!--过滤器设置输出的级别--><filter class="org.apache.log4j.varia.LevelRangeFilter"><param name="levelMin" value="debug"/><param name="levelMax" value="fatal"/><param name="AcceptOnMatch" value="true"/></filter></appender><appender name="ALL" class="org.apache.log4j.DailyRollingFileAppender"><param name="File" value="${user.dir}/logs/spring-common/jcl/all"/><param name="Append" value="true"/><!-- 每天重新生成日志文件 --><param name="DatePattern" value="'-'yyyy-MM-dd'.log'"/><!-- 每小时重新生成日志文件 --><!--<param name="DatePattern" value="'-'yyyy-MM-dd-HH'.log'"/>--><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern"value="%d{yyyy-MM-dd HH:mm:ss,SSS\} [%-5p] [%t] %c{36\}.%M - %m%n"/></layout></appender><!-- 指定logger的设置,additivity指示是否遵循缺省的继承机制--><logger name="io.zhanbo.log" additivity="false"><level value="error"/><appender-ref ref="STDOUT"/><appender-ref ref="ALL"/></logger><!-- 根logger的设置--><root><level value="warn"/><appender-ref ref="STDOUT"/></root>
</log4j:configuration>

4.3 日志库 API - 针对于日志门面

slf4j 用法

​ 使用 slf4j 的 API 很简单。使用LoggerFactory 初始化一个 Logger 实例,然后调用 Logger 对应的打印等级函数就行了。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class Test {private static final Logger logger = LoggerFactory.getLogger(Test.class);public static void main(String[] args) {String msg = "print log, current level: {}";logger.trace(msg, "trace");logger.debug(msg, "debug");logger.info(msg, "info");logger.warn(msg, "warn");logger.error(msg, "error");}
}

common-logging 用法

​ common-logging 用法和 slf4j 几乎一样,但是支持的打印等级多了一个更高的级别:fatal。

​ 此外,common-logging 不支持 {} 替换参数,你只能选择拼接字符串这种方式了。

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;public class Test {private static final Log log = LogFactory.getLog(Test.class);public static void main(String[] args) {String msg = "print log, current level:";log.trace(msg + "trace");log.debug(msg + "debug");log.info(msg + "info");log.warn(msg + "warn");log.error(msg + "error");log.fatal(msg + "fatal");}
}

5. 日志库选型与改造

5.1 对 Java 日志组件选型的建议

​ slf4j 已经成为了 Java 日志组件的明星选手,可以完美代替 JCL,使用 JCL 桥接库也能完美兼容一切使用 JCL 作为日志门面的类库,现在的新系统已经没有不使用 slf4j 作为日志 API 的理由了。

​ 日志记录服务方面,log4j 在功能上输于 logback 和 log4j2,在性能方面 log4j2 则全面超越 log4j 和 logback。所以新系统应该在 logback 和 log4j2 中做出选择,对于性能有很高要求的系统,应优先考虑 log4j2。

5.2 对日志架构使用比较好的实践

总是使用 Log Facade,而不是具体 Log Implementation

​ 正如之前所说的,使用 Log Facade 可以方便的切换具体的日志实现。而且,如果依赖多个项目,使用了不同的 Log Facade,还可以方便的通过 Adapter 转接到同一个实现上。如果依赖项目使用了多个不同的日志实现,就麻烦的多了。

​ 具体来说,现在推荐使用 Log4j-API 或者 SLF4J,不推荐继续使用 JCL。

只添加一个 Log Implementation 依赖

​ 毫无疑问,项目中应该只使用一个具体的 Log Implementation,建议使用 Logback 或者 Log4j2。 如果有依赖的项目中,使用的 Log Facade 不支持直接使用当前的 Log Implementation,就添加合适的桥接器依赖。具体的桥接关系可以看上一节的图。

具体的日志实现依赖应该设置为 optional 和使用 runtime scope

​ 在项目中,Log Implementation 的依赖强烈建议设置为 runtime scope,并且设置为 optional。例如项目中使用了 SLF4J 作为 Log Facade,然后想使用 Log4j2 作为 Implementation,那么使用 maven 添加依赖的时候这样设置:

<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j.version}</version><scope>runtime</scope><optional>true</optional>
</dependency>
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>${log4j.version}</version><scope>runtime</scope><optional>true</optional>
</dependency>

​ 设为 optional,依赖不会传递,这样如果你是个 lib 项目,然后别的项目使用了你这个 lib,不会被引入不想要的 Log Implementation 依赖;

​ Scope 设置为 runtime,是为了防止开发人员在项目中直接使用 Log Implementation 中的类,而不使用 Log Facade 中的类。

如果有必要,排除依赖的第三方库中的 Log Impementation 依赖

​ 这是很常见的一个问题,第三方库的开发者未必会把具体的日志实现或者桥接器的依赖设置为 optional,然后你的项目继承了这些依赖 一一 具体的日志实现未必是你想使用的,比如他依赖的 Log4j,你想使用 Logback,这时就很尴尬。另外,如果不同的第三方依赖使用了不同的桥接器和 Log 实现,也极容易形成环。

​ 这种情况下,推荐的处理方法,是使用 exclude 来排除所有的这些 Log 实现和桥接器的依赖,只保留第三方库里面对 Log Facade 的依赖。

​ 比如阿里的 JStorm 就没有很好的处理这个问题,依赖 jstorm 会引入对 Logback 和 log4j-over-slf4j 的依赖,如果你想在自己的项目中使用 Log4j 或者其它 Log 实现的话,就需要加上 excludes:

<dependency><groupId>com.alibaba.jstorm</groupId><artifactId>jstorm-core</artifactId><version>2.1.1</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId></exclusion><exclusion><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></exclusion></exclusions>
</dependency>

避免为不会输出的 log 付出代价

​ Log 库都可以灵活的设置输出界面,所以每一条程序中的 log,都是有可能不会被输出的。这时候要注意不要额外的付出代价。

​ 先看两个有问题的写法:

logger.debug("start process request, url: " + url);
logger.debug("receive request: {}", toJson(request));

​ 第一条是直接做了字符串拼接,所以即使日志级别高于 debug 也会做一个字符串连接操作;第二条虽然用了 SLF4J/Log4j2 中的懒求值方式来避免不必要的字符串拼接开销,但是 toJson() 这个函数却是都会被调用并且开销很大。

​ 推荐的写法如下:

logger.debug("start process request, url:{}", url); // SLF4J/LOG4J2
logger.debug("receive request: {}", () -> toJson(request)); // LOG4J2
logger.debug(() -> "receive request: " + toJson(request)); // LOG4J2
if (logger.isDebugEnabled()) { // SLF4J/LOG4J2logger.debug("receive request: " + toJson(request)); 
}

日志格式中最好不要使用行号,函数名等字段

​ 原因是:为了获取语句所在的函数名,或者行号,log 库的实现都是获取当前的 stacktrace,然后分析取出这些信息,而获取 stacktrace 的代价是很昂贵的。如果有很多的日志输出,就会占用大量的 CPU。在没有特殊需要的情况下,建议不要在日志中输出这些字段。

​ 最后,log 中不要输出稀奇古怪的字符!

​ 部分开发人员为了方便看到自己的 log,会在 log 语句中加上醒目的前缀,比如:

logger.debug("========================start process request=============");

​ 虽然对于自己来说是方便了,但是如果所有人都这样来做的话,那 log 输出就没法看了!正确的做法是使用 grep 来看自己只关心的日志。

5.3 对现有系统日志架构的改造建议

​ 如果现有系统使用 JCL 作为日志门面,又确实面临着 JCL 的 ClassLoader 机制带来的问题,完全可以引入 slf4j 并通过桥接库将 JCL api 输出的日志桥接至 slf4j,再通过适配库配置现有的日志输出服务(如 log4j),如下图:

在这里插入图片描述

​ 这样做不需要任何代码级的改造,就可以解决 JCL 的 ClassLoader 带来的问题,但没有办法享受日志模板等 slf4j 的 api 带来的优点。不过之后在现有系统上开发的新功能就可以使用 slf4j 的 api 了,老代码也可以分批进行改造。

​ 如果现有系统使用 JCL 作为日志门面,又头疼 JCL 不支持 logback 和 log4j2 等新的日志服务,也可以通过桥接库以 slf4j 代替 JCL,但同样无法直接享受 slf4j api 的优点。

​ 如果想要使用 slf4j 的 api,那么就不得不进行代码改造了,当然改造也可以参考1中提到的方法逐步进行。

​ 如果现有系统面临着 log4j 的性能问题,可以使用 Apache Logging 提供的 log4j 到 log4j2 的桥接库 log4j-1.2-api,把通过 log4j api 输出的日志桥接至 log4j2.这样可以最快地使用上 log4j2 的先进性能,但组件中缺失了 slf4j,对后续进行日志架构改造的灵活性有影响。另一种办法是先把 log4j 桥街知 slf4j,再使用 slf4j 到 log4j2 的适配库。这样做稍微麻烦了一点,但可以逐步将系统中的日志输出标准化为使用 slf4j 的 api,为后面的工作打好基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/890585.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聚类之轮廓系数

Silhouette Score&#xff08;轮廓系数&#xff09;是用于评估聚类质量的指标之一。它衡量了数据点与同簇内其他点的相似度以及与最近簇的相似度之间的对比。 公式 对于一个数据点 i&#xff1a; a(i): 数据点 i 到同簇内其他点的平均距离&#xff08;簇内不相似度&#xff…

问题小记-达梦数据库报错“字符串转换出错”处理

最近遇到一个达梦数据库报错“-6111: 字符串转换出错”的问题&#xff0c;这个问题主要是涉及到一条sql语句的执行&#xff0c;在此分享下这个报错的处理过程。 问题表现为&#xff1a;一样的表结构和数据&#xff0c;执行相同的SQL&#xff0c;在Oracle数据库中执行正常&…

【电路笔记 信号】Metastability 平均故障间隔时间(MTBF)公式推导:进入亚稳态+退出亚稳态+同步器的可靠性计算

这是一个简化的电路分析模型。图2中的典型触发器包括主锁存器、从锁存器和去耦反相器(这个结构类似 主从边沿触发器)。 在亚稳态中&#xff0c;主锁存器的节点A、B的电压电平大致在逻辑“1”&#xff08;VDD&#xff09;和“0”&#xff08;GND&#xff09;之间。确切的电压电平…

【C++】B2066救援题目分析和解决讲解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af; 题目&#x1f4af; 题目分析每个屋顶计算的元素 &#x1f4af; 思路解析1. **读取输入**2. **计算屋顶时间**3. **结果精确取整** &#x1f4af; 完整解决代码&#x1f4a…

springboot创建web项目

一、创建项目 二、导入依赖&#xff08;pom.xml&#xff09; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schem…

RAID5原理简介和相关问题

1、RAID5工作原理 2、RAID5单块硬盘的数据连续吗&#xff1f; 3、RAID5单块硬盘存储的是原始数据&#xff0c;还是异或后的数据&#xff1f; 4、RAID5的分块大小 ‌RAID5的分块大小一般选择4KB到64KB之间较为合适‌。选择合适的分块大小主要取决于以下几个考量因素&#xff1…

重温设计模式--模板方法模式

文章目录 一、模板方法模式概述二、模板方法模式UML图三、优点1代码复用性高2可维护性好3扩展性强 四、缺点五、使用场景六、C 代码示例1七、 C 代码示例2 一、模板方法模式概述 定义&#xff1a;定义一个操作中的算法骨架&#xff0c;而降一些步骤延迟到子类中。模板方法使得…

Websocket客户端从Openai Realtime api Sever只收到部分数据问题分析

目录 背景 分析 解决方案 背景 正常情况下&#xff0c;会从Openai Realtime api Sever收到正常的json数据,但是当返回音频数据时&#xff0c;总会返回非json数据。这是什么问题呢&#xff1f; 分析 期望的完整响应数据如下&#xff1a; {"session": {"inp…

运动控制卡网络通讯的心跳检测之C#上位机编程

本文导读 今天&#xff0c;正运动小助手给大家分享一下如何使用C#上位机编程实现运动控制卡网络通讯的心跳检测功能。 01 ECI2618B硬件介绍 ECI2618B经济型多轴运动控制卡是一款脉冲型、模块化的网络型运动控制卡。控制卡本身最多支持6轴&#xff0c;可扩展至12轴的运动控制…

SpringBoot状态机

Spring Boot 状态机&#xff08;State Machine&#xff09;是 Spring Framework 提供的一种用于实现复杂业务逻辑的状态管理工具。它基于有限状态机&#xff08;Finite State Machine, FSM&#xff09;的概念&#xff0c;允许开发者定义一组状态、事件以及它们之间的转换规则。…

基于图注意力网络的两阶段图匹配点云配准方法

Two-stage graph matching point cloud registration method based on graph attention network— 基于图注意力网络的两阶段图匹配点云配准方法 从两阶段点云配准方法中找一些图匹配的一些灵感。文章提出了两阶段图匹配点云配准网络&#xff08;TSGM-Net&#xff09; TSGM-Ne…

uniapp跨平台开发---webview调用app方法

1.app端实现 注意:为了实现实时通信,app端页面是.nvue 代码实现 <template><view class"content"><view class"web-view"><web-view class"web-view" :src"url" ref"webview" onPostMessage"o…

【专题】2024年悦己生活消费洞察报告汇总PDF洞察(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p38654 在当今时代背景下&#xff0c;社会发展日新月异&#xff0c;人们的生活方式与消费观念正经历深刻变革。MoonFox 月狐数据的《2024 年悦己生活消费洞察报告》聚焦于这一充满活力与变化的消费领域。随着就业、婚姻等社会压力的…

生产看板管理系统涵盖哪些方面

嘿&#xff0c;各位搞生产管理的朋友&#xff0c;肯定都碰到过些麻烦事儿吧。我就寻思着&#xff0c;能不能弄出个 “明明白白” 的工作场地呢&#xff1f;让员工和管理人员都能随时查查生产进度&#xff0c;一发现生产里有啥问题就能立马知道。 生产进度不好追踪生产过程不清…

密码学期末考试笔记

文章目录 公钥加密之前的部分 (非重点&#xff0c;关注工具怎么用&#xff0c;和性质)一、对称加密 (symmetric ciphers)1. 定义 二、PRG (伪随机数生成器)1. 定义2. 属性 三、语义安全 (Semantic Security)1. one-time key2. 流密码是语义安全的 四、分组密码 (Block Cipher)1…

workman服务端开发模式-应用开发-vue-element-admin挂载websocket

一、项目根目录main.js添加全局引入 import /utils/websocket 二、在根目录app.vue 中初始化WebSocket连接 <template><div id"app"><router-view /></div> </template><script>import store from ./store export default {n…

我的 2024 年终总结

2024 年&#xff0c;我离开了待了两年的互联网公司&#xff0c;来到了一家聚焦教育机器人和激光切割机的公司&#xff0c;没错&#xff0c;是一家硬件公司&#xff0c;从未接触过的领域&#xff0c;但这还不是我今年最重要的里程碑事件 5 月份的时候&#xff0c;正式提出了离职…

信创源代码加密的答案:信创沙箱

在信息化与工业化融合创新&#xff08;信创&#xff09;的背景下&#xff0c;企业面临着前所未有的数据安全挑战。SDC沙盒技术以其独特的隔离和保护机制&#xff0c;为信创环境提供了强有力的支持。以下是SDC沙盒在信创支持方面的优势&#xff0c;这些优势体现了其在保护企业数…

Leecode刷题C语言之根据第k场考试的分数排序

执行结果:通过 执行用时和内存消耗如下&#xff1a; int gk 0;int compare(const void* a, const void* b) {int* ua *(int**)a;int* ub *(int**)b;return ub[gk] - ua[gk]; }int** sortTheStudents(int** score, int scoreSize, int* scoreColSize, int k, int* returnSiz…

Linux 下SVN新手操作手册

下面来介绍Linux 下 SVN操作方法&#xff1a; 1、SVN的安装 Centos 7 安装Subversion sudo yum -y install subversion Ubuntu 安装Subversion sudo apt-get install subversion 自定义安装&#xff0c;官方地址&#xff1a;https://subversion.apache.org/ 2、SVN的使用…