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,为后面的工作打好基础。
相关文章:

Java 日志类库
Java 日志库是最能体现 Java 库在进化中的渊源关系的,在理解时重点理解日志框架本身和日志门面,以及比较好的时间等。要关注其历史渊源和设计(比如桥接),而具体在使用时查询接口即可,否则会陷入 JUL&#x…...

【Unity3D】Particle粒子特效或3D物体显示在UGUI上的方案
目录 一、RawImage Camera RenderTexture方式 (1)扩展知识:实现射线检测RawImage内的3D物体 (2)扩展知识:实现粒子特效显示RawImage上 二、UI摄像机 Canvas(Screen Space - Camera模式)方式 &#…...

有没有检测吸烟的软件 ai视频检测分析厂区抽烟报警#Python
在现代厂区管理中,安全与规范是重中之重,而吸烟行为的管控则是其中关键一环。传统的禁烟管理方式往往依赖人工巡逻,效率低且存在监管死角,难以满足当下复杂多变的厂区环境需求。此时,AI视频检测技术应运而生࿰…...

《鸣潮》游戏运行时弹出“xinput1_3.dll文件缺失”错误的处理方法,“xinput1_3.dll文件缺失”详解!
一、xinput1_3.dll文件的重要性 xinput1_3.dll是DirectX组件中的一个重要文件,它负责处理与Xbox 360控制器相关的输入功能。尽管《鸣潮》可能并不直接依赖于Xbox控制器,但许多现代游戏和应用程序都会调用这个DLL文件来处理各种输入设备的功能。因此&…...

大模型应用—HivisionIDPhotos 证件照在线制作!支持离线、换装、美颜等
HivisionIDPhotos 证件照在线制作!支持离线、换装、美颜等 ivisionIDPhotos 是一款功能强大的开源证件照生成工具。用户只需上传一张人像照片,它就能智能裁剪为一寸、两寸等标准尺寸,同时自动去除背景并渲染新的背景颜色,例如蓝色、白色、红色,还支持渐变色和自定义颜色。…...

解决Ubuntu下无法装载 Windows D盘的问题
电脑安装了 Windows 和 Ubuntu 24.04 后,在Ubuntu系统上装载 D盘,发现无法装载错误如下: Error mounting /dev/nvme0n1p4 at /media/jackeysong/Data: wrong fs type, bad option, bad superblock on /dev/nvme0n1p4, missing codepage or h…...

一体成型电感
一体成型电感是通过铁粉模压成型而成的同封装条件下实现更大的额定电流,且更适合批量自动化生产,较传统绕线电感有成本优势。同时,一体成型电感与磁封胶结构电感相比具有更好的磁屏蔽效果,适合EMI无法调试通过的项目使用。 但一体…...

Reed-Muller(RM)码之编码
点个关注吧! 看了一些中文的博客,RM码没有很详细的资料,所以本文尝试给出推导原理。 推导 RM码由 ( r , m ) ( r , m ) (r,m...

【蓝桥杯——物联网设计与开发】基础模块8 - RTC
目录 一、RTC (1)资源介绍 🔅简介 🔅时钟与分频(十分重要‼️) (2)STM32CubeMX 软件配置 (3)代码编写 (4)实验现象 二、RTC接口…...

聚类算法DBSCAN 改进总结
目录 DBSCAN(Density-Based Spatial Clustering of Applications with Noise) 1. HDBSCAN (Hierarchical DBSCAN) 优点: 安装: 使用实例1 效果失败 使用实例2 3. DBSCAN++ (DBSCAN with Preprocessing) 4. DBSCAN with k-distance 5. Density Peaks Clustering (DP…...

uniapp开发微信小程序实现获取“我的位置”
1. 创建GetLocation项目 使用HBuilder X创建一个项目GetLocation,使用Vue3。 2. 在腾讯地图开放平台中创建应用 要获取位置,在小程序中需要使用腾讯地图或是高德地图。下面以腾讯地图为例。 (1)打开腾讯地图开放平台官方网址:腾讯位置服务 - 立足生态,连接未来 (2)注册…...

java中两个系统进行非对称加密,两个系统的公私钥可以用一套吗?
在非对称加密中,每个参与方应该拥有自己独立的一套公钥和私钥。非对称加密的基础在于公钥和私钥的配对使用:一个密钥用于加密信息,则另一个对应的密钥用于解密信息。具体来说: 如果A要发送一条保密消息给B,那么A会使用…...

无人设备遥控器之定向天线篇
一、定义与功能 定向天线,顾名思义,是通过改变天线的辐射方向,实现信号发射、接收和增强的天线。它可以让信号以更高的功率、更远的距离传输到指定区域,同时也能够降低与周围天线之间的干扰。在无人设备遥控器中,定向天…...

【电路笔记 信号】Metastability 平均故障间隔时间(MTBF)公式推导:进入亚稳态+退出亚稳态+同步器的可靠性计算
这是一个简化的电路分析模型。图2中的典型触发器包括主锁存器、从锁存器和去耦反相器(这个结构类似 主从边沿触发器)。 在亚稳态中,主锁存器的节点A、B的电压电平大致在逻辑“1”(VDD)和“0”(GND)之间。确切的电压电平…...

计算机视觉:原理、分类与应用
计算机视觉是当今科技领域中一个至关重要的分支,它赋予了计算机通过视觉感知和理解世界的能力。简单来说,计算机视觉实现了对图像、视频等视觉数据的分析、处理、识别和理解。这是一个跨学科的研究领域,涉及计算机科学、信息工程、数学、物理…...

Vue.js组件开发-使用watch进行深度观察
在Vue.js中,watch选项允许观察和响应Vue实例上数据的变化。当需要对某个数据属性进行深度观察,即在其内部嵌套的对象或数组发生变化时也能触发回调时,可以使用deep选项。 示例: new Vue({el: #app,data: {user: {name: John,age…...

明厨亮灶系统
校园食堂明厨亮灶AI分析系统通过yolov5网络模型技术,校园食堂明厨亮灶监控分析系统针对校园餐厅后厨不按要求戴口罩、不穿厨师帽、陌生人员进入后厨、厨师不穿厨师服、上班时间玩手机、老鼠识别等行为校园食堂明厨亮灶监控分析系统自动识别抓拍告警。Yolo算法&#…...

虚幻引擎结构之AActor
在虚幻引擎中,AActor 是一个核心类,作为游戏世界内所有可交互对象的基础。任何可以在关卡中放置或动态生成的对象,几乎都是从 AActor 类派生而来。这包括但不限于角色、道具、特效、静态和动态物体等。 1. AActor 的基本概念 AActor 作为基类…...

基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
基于JAVASpringBootVue的制造装备物联及生产管理ERP系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末附源码下载链接&am…...

JAVA HTTP压缩数据
/*** 压缩数据包** param code* param data* param resp* throws IOException*/protected void writeZipResult(int code, Object data, HttpServletResponse resp) throws IOException {resp.setHeader("Content-Encoding", "gzip");// write到客户端resp…...

VSCode 配置远程连接免密登录 插件
自我存档 远程连接免密登录 远程连接 在扩展界面搜remote ssh 安装完成后可以在侧边栏找到远程资源管理器 通过来添加或者点击打开ssh配置文件 点击的话以这种方式, 手动添加则按照相同格式输入即可 格式如下所示, Host后添加IP, User是登录ssh的用户, hostname是显示在…...

VIVO C++开发面试题及参考答案
面向过程与面向对象的区别,面向对象后的好处 面向过程编程主要关注的是程序的流程,它将一个问题分解为一系列的步骤,通过函数来实现这些步骤,数据和操作这些数据的函数是分离的。例如,在一个简单的计算学生成绩平均值的程序中,我们可能会有一些函数来输入成绩、计算总和、…...

Unity3D用正则判断身份证号或邮箱
系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、正则判断邮箱格式👉二、正则判断身份证号👉壁纸分享👉总结👉前言 C#正则表达式(Regex)是一种用来匹配字符串模式的强大工具。在C#中,可以使用System.Text.RegularExpressions命名空间下的Regex类来处…...

【终端工具】FinalShell v4.5.12 官方版
1.下载地址 【终端工具】FinalShell v4.5.12 官方版 2.简介 FinalShell是一款免费的跨平台远程管理工具,专为开发者和运维人员设计。它支持通过 SSH、SFTP 等方式连接到 Linux 和 Windows 服务器,提供类似于终端的操作界面。除了常规的远程登录功能&a…...

【阅读记录-章节6】Build a Large Language Model (From Scratch)
系列文章目录 【阅读记录-章节1】Build a Large Language Model (From Scratch) 【阅读记录-章节2】Build a Large Language Model (From Scratch) 【阅读记录-章节3】Build a Large Language Model (From Scratch) 【阅读记录-章节4】Build a Large Language Model (From Scr…...

面向未来的教育技术:智能成绩管理系统的开发
3.1 可行性研究 成绩管理系统开发实现分析需要从不同的角度来进行分析可行性,比如从时间角度,经济角度,甚至操作角度。从不同的角度分析可行性会让成绩管理系统开发具体化,进而达到辩证开发的正确性。 3.1.1 经济可行性 从经济方面…...

Mac系统下 IDEA配置Maven本地仓库
1.为什么需要配置本地仓库? 在软件开发过程中,使用Maven工具进行依赖管理是常见的做法。Maven通过集中管理各种依赖库,能够帮助开发者在项目中轻松地引入所需的第三方库,并确保项目能够顺利构建和部署。然而,在使用Mav…...

shell脚本定义特殊字符导致执行mysql文件错误的问题
记得有一次版本发布过程中有提供一个sh脚本用于一键执行sql文件,遇到一个shell脚本定义特殊字符的问题,sh脚本的内容类似以下内容: # 数据库ip地址 ip"127.0.0.1" # 数据库密码 cmdbcmdb!#$! smsm!#$!# 执行脚本文件(参…...

【C++ 基础】构造和析构
构造和析构 1.养成一个习惯,只要是变量,定义后要初始化 2.在C当中要完成对象的初始化工作,可以借助构造来完成,如果要完成对象的清理操作,借助析构来完成 3.在C里面对于对象的初始化有4种方式: 1、直接…...

C语言项目 天天酷跑(上篇)
前言 这里讲述这个天天酷跑是怎么实现的,我会在天天酷跑的下篇添加源代码,这里会讲述天天酷跑这个项目是如何实现的每一个思路,都是作者自己学习于别人的代码而创作的项目和思路,这个代码和网上有些许不一样,因为掺杂了…...