当前位置: 首页 > news >正文

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 库在进化中的渊源关系的&#xff0c;在理解时重点理解日志框架本身和日志门面&#xff0c;以及比较好的时间等。要关注其历史渊源和设计&#xff08;比如桥接&#xff09;&#xff0c;而具体在使用时查询接口即可&#xff0c;否则会陷入 JUL&#x…...

【python】银行客户流失预测预处理部分,独热编码·标签编码·数据离散化处理·数据筛选·数据分割

数据预处理 通过网盘分享的文件&#xff1a;银行流失预测数据和代码 链接: https://pan.baidu.com/s/1loiB8rMvZArfjJccu4KW6w?pwdpfcs 提取码: pfcs 非数值特征处理 目的&#xff1a;将非数值特征转换为数值型&#xff0c;以便模型能够处理。方法&#xff1a; 地理位置&am…...

Linux | scp指令基于WSL在Windows/Ubuntu系统间传输文件

. 背景 在Windows系统里&#xff0c;使用WSL连接远程Linux&#xff08;Ubuntu&#xff09;服务器是如今一个很常见的操作流程&#xff08;有利于WFH哈哈&#xff09;。 在使用远程机器的时候&#xff0c;通常需要将本地的文件上传、或将远程的文件下载。 问题&#xff1a;如…...

类设计者的核查表

核查表 第一篇 如何设计类你的类需要复制构造函数吗何时不需要自定义复制构造函数何时需要自定义复制构造函数总结 什么时候需要将构造函数和赋值运算符设置为私有&#xff1f;1. 单例模式&#xff08;Singleton Pattern&#xff09;2. 禁止复制和赋值3. 工厂模式&#xff08;F…...

深入解析:Python中的决策树与随机森林

在这个数据驱动的时代&#xff0c;机器学习技术已经成为许多企业和研究机构不可或缺的一部分。其中&#xff0c;决策树和随机森林作为两种强大的算法&#xff0c;在分类和回归任务中表现尤为出色。本文将带领大家深入了解这两种算法在Python中的实现&#xff0c;从基础到实战&a…...

umi : 无法加载文件 D:\software\nodejs\node_global\umi.ps1,因为在此系统上禁止运行脚本。

问题详情 2、解决方法 1.使用命令 get-ExecutionPolicy查看 显示Restricted&#xff1a;限制 所以要给权限 2. 使用命令&#xff1a;Set-ExecutionPolicy -Scope CurrentUser 3. 会提示为参数提供值 4. 输入&#xff1a; RemoteSigned 具体如下图所示&#xff0c;成功解决。 报…...

十四、从0开始卷出一个新项目之瑞萨RZN2L之栈回溯(Default_Handler/hartfault)

目录 一、概述 二、参考资料 三、代码 四、日志 五、定位函数调用 六、README和工具 一、概述 软件开发中常见的比较棘手的问题就是hartfault/Default_Handler/dump&#xff0c;俗称跑飞了。 参考cmbacktrace&#xff0c;在瑞萨RZN2L/T2M实现栈回溯&#xff0c;串口打印…...

CTFHub disable_functions通关

LD_PRELOAD 来到首页发现有一句话直接就可以用蚁剑连接 根目录里有/flag但是不能看;命令也被ban了就需要绕过了 绕过工具在插件市场就可以下载 如果进不去的话 项目地址: #本地仓库;插件存放 antSword\antData\plugins 绕过选择 上传后我们点进去可以看到多了一个绕过的文件;…...

什么是 DevOps 自动化?

DevOps 自动化是一种现代软件开发方法&#xff0c;它使用工具和流程来自动化任务并简化工作流程。它将开发人员、IT 运营和安全团队聚集在一起&#xff0c;帮助他们有效协作并交付可靠的软件。借助 DevOps 自动化&#xff0c;组织能够处理重复性任务、优化流程并更快地将应用程…...

创建Instagram合作广告方法与注意事项

将Instagram作为宣传阵地的品牌和营销人员一定对它的Branded content ads品牌内容广告很熟悉&#xff0c;Instagram在测试并推广创作者市场功能之后&#xff0c;创作者和品牌协作变得更加便利。其中的Partnership ads合作广告能结合品牌和UGC、KOL的力量&#xff0c;帮助品牌提…...

Elasticsearch

什么是elasticsearch 根据维基百科的定义&#xff1a;Elasticsearch是一个基于Lucene库的搜索引擎。它提供了一个分布式、支持多租户的全文搜索引擎&#xff0c;具有HTTP Web接口和无模式JSON文档。 为啥要用elasticsearch 高性能&#xff0c;近实时&#xff0c;大数据&…...

YOLO11改进-注意力-引入级联组注意力机制(Cascaded Group Attention, CGA)

在 Vision Transformers 面临计算成本高、推理速度慢的背景下&#xff0c;级联组注意力&#xff08;CGA&#xff09;机制应运而生&#xff0c;它通过将输入特征拆分为不同部分输入各注意力头计算自注意力并级联输出&#xff0c;解决了多头自注意力中注意力头冗余导致的计算效率…...

电磁兼容(EMC):一文解读磁芯复合材料——塑磁

目录 01 塑磁的定义 02 塑磁的常见规格型号 03 塑磁材料的优点 04 塑磁的应用 塑磁,也称为注塑磁,是一种将磁性粉末注入到塑料基体中制成的复合磁体材料。以下是塑磁的定义、应用和材料特性的总结: 01 塑磁的定义 塑磁是以塑料为基体,通过特殊工艺在其中加入磁性粒子(…...

第十四章 C++ 数字

通常&#xff0c;当我们需要用到数字时&#xff0c;我们会使用原始的数据类型&#xff0c;如 int、short、long、float 和 double 等等。这些用于数字的数据类型&#xff0c;其可能的值和数值范围&#xff0c;我们已经在 C 数据类型一章中讨论过。 C 定义数字 我们已经在之前…...

虚幻引擎结构之UObject

一. UObject 的介绍 UObject 是虚幻引擎中的核心基础类,所有其他游戏对象和资源类都直接或间接地继承自它。作为虚幻引擎的基石,UObject 提供了多项关键功能,包括内存管理、序列化、反射(introspection)、垃圾回收以及元数据支持。在虚幻引擎中,UObject 类的实例通常被称…...

2002 - Can‘t connect to server on ‘192.168.1.XX‘ (36)

参考:2002 - Can‘t connect to server on ‘192.168.1.XX‘ (36) ubantu20.04&#xff0c;mysql5.7.13 navicat 远程连接数据库报错 2002 - Can’t connect to server on ‘192.168.1.61’ (36) 一、查看数据库服务是否有启动&#xff0c;发现有启动 systemctl status mysql…...

怎麼在模擬器中實現換IP

方法一&#xff1a;使用代理伺服器 獲取代理伺服器資訊需要一個可用的代理伺服器地址和端口。 設置代理 如果模擬器有內置的網路設置&#xff0c;可以直接在網路設置中輸入代理伺服器的地址和端口。對於不支持直接設置代理的模擬器&#xff0c;可以在應用內設置代理。例如&am…...

【信号滤波 (上)】傅里叶变换和滤波算法去除ADC采样中的噪声(Matlab/C++)

目录 一、ADC采样的噪声简介1.1 常见的ADC噪声来源 二、信号的时域到频域转换2.1 傅里叶变换巧记傅里叶变换 三、傅里叶变换和滤波算法工程实现3.1 使用Matlab计算信号时域到频域的变换3.2 使用Matlab去除特定频点噪声寻找峰值算噪声频率构建陷波滤波器滤除噪声频点陷波滤波器与…...

将多个 Touchstone 文件导入 ANSYS Electronics Desktop

概述 本博客说明了如何将 N 端口标准文件列表导入 ANSYS 电路和 HFSS 3D 布局工具。N端口模型可以引用解决方案文件数组&#xff0c;而不是引用单个文件。下面简要概述了添加多文件 N 端口模型所需的步骤&#xff0c;视频链接中提供了完整的演示。 创建多文件 N 端口模型 要…...

GFPS扩展技术原理(八)-可听设备控制

Hearable Controls 可听设备控制就是手机通过Message Stream去配置影响听感的设置&#xff0c;目前只有一个ANC可供配置&#xff0c;Hearable controls的Message Group的值为0x8。 Active noise control Active noise control也就是主动降噪&#xff08;ANC&#xff09;&…...

对称二叉树

本节判断一棵二叉树是否为对称二叉树,用深度优先算法和广度优先搜索算法均可以实现. 问题描述: 给定一棵二叉树,判断该二叉树是否为对称二叉树. 广度优先思路解析: 如果所有镜像对称位置上两节点都相同,就说明这棵树一定是对称的.那么如何对比对称位置上的两个节点比较方便呢…...

K8s 无头服务(Headless Service)

在Kubernetes中&#xff0c;服务&#xff08;Service&#xff09;是一个抽象层&#xff0c;它定义了一组Pod的访问策略。通常情况下&#xff0c;服务会分配一个集群内的IP地址&#xff0c;并通过这个IP地址和端口来路由流量到后端Pod。然而&#xff0c;Kubernetes还提供了一种特…...

ArcGIS+MIKE21 洪水淹没分析、溃坝分析,洪水淹没动态效果

洪水淹没分析过程&#xff1a; 一、所需数据&#xff1a; 1.分析区域DEM数据 二、ArcGIS软件 1.提取分析区域DEM&#xff08;水库坝下区域&#xff09; 2.DEM栅格转点 3.计算转换后几何点的x和y坐标值&#xff08;精度20、小数位3&#xff09; 4.导出属性表&#xff0c;形式…...

WordPress File Upload 插件 任意文件读取漏洞复现(CVE-2024-9047)

0x01 产品简介 WordPress File Upload插件是一款功能强大的WordPress站点文件上传插件,它允许用户在WordPress站点中的文章、页面、侧边栏或表单中轻松上传文件到wp-contents目录中的任何位置。该插件使用最新的HTML5技术,确保在现代浏览器和移动设备上都能流畅运行,同时也…...

MySQL purged gtid是如何生成和维护的

目录 1. GTID的基本概念2. GTID的生成3. GTID的清除3.1 手动清除二进制日志3.2 自动清除二进制日志3.3 重置主库 在MySQL中&#xff0c;gtid_purged表示已清除的GTID集合。 gtid_purged的生成和维护过程如下&#xff1a; 1. GTID的基本概念 GTID&#xff08;Global Transact…...

vulhub log4j2漏洞复现攻略

前期准备&#xff1a;在安全选项添加端口规则如下 进入靶场环境 cd vulhub/ cd log4j/ cd CVE-2021-44228/ 启动容器 docker-compose up -d docker ps 得到端口号为8983&#xff0c;浏览器访问 先在⾃⼰搭建的DNSLOG平台上获取⼀个域名来监控我们注⼊的效果 可以发现 /sol…...

Android修行手册 - 移动端几种常用动画方案对比

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…...

springboot484基于springboot的扶贫助农系统(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装扶贫助农系统软件来发挥其高效地信息处理的作用&#xff0c…...

windows调整鼠标速度

参考:https://baijiahao.baidu.com/s?id1791659684803021646&wfrspider&forpc 鼠标灵敏度&#xff0c;亦称为指针速度或DPI&#xff08;每英寸点数&#xff09;设置&#xff0c;对用户的电脑操作流畅度和精准度至关重要。本篇文章将深入解析如何在Windows操作系统环境…...

专业的内外网数据交换方案 可解决安全、效率、便捷3大问题

内外网数据交换是很多企业和行业都会面临的场景&#xff0c;既然隔离了内外网&#xff0c;重中之重就是要确保数据的安全性&#xff0c;其次在数据流转交换过程中&#xff0c;不能太繁琐复杂&#xff0c;需要让用户快速、便捷的进行数据交换。首先我们来看看&#xff0c;在进行…...

ECharts关系图-关系图11,附视频讲解与代码下载

引言&#xff1a; 关系图&#xff08;或称网络图、关系网络图&#xff09;在数据可视化中扮演着至关重要的角色。它们通过节点&#xff08;代表实体&#xff0c;如人、物体、概念等&#xff09;和边&#xff08;代表实体之间的关系或连接&#xff09;的形式&#xff0c;直观地…...

在已有vue cli项目中添加单元测试配置

使用的是vue cli ^4.0.0的脚手架&#xff0c;项目采用的vue2进行编写&#xff0c;项目本身是没有使用单元测试的。应该挺多项目还是使用的vue2的项目进行开发的&#xff0c;自己在开发中过程中&#xff0c;还是发生了挺多需要记录原来功能的情况&#xff0c;这个时候去翻文档明…...

计算机网络B重修班-期末复习

[TOC] (计算机网络B重修班-期末复习&#xff09; 一、单选 &#xff08;20题&#xff0c;1分/题&#xff0c;共20分&#xff09; 二、判断 &#xff08;10题&#xff0c;1分/题&#xff0c;共10分&#xff09; 三、填空 &#xff08;10题&#xff0c;1分/题&#xff0c;共10…...

常见排序算法

目录 冒泡排序&#xff08;Bubble Sort&#xff09; 选择排序&#xff08;Selection Sort&#xff09; 插入排序&#xff08;Insertion Sort&#xff09; 希尔排序&#xff08;Shell Sort&#xff09; 快速排序&#xff08;Quick Sort&#xff09; 堆排序&#xff08;Hea…...

开源轮子 - Logback 和 Slf4j

spring boot内置&#xff1a;Logback 文章目录 spring boot内置&#xff1a;Logback一&#xff1a;Logback强在哪&#xff1f;二&#xff1a;简单使用三&#xff1a;把 log4j 转成 logback四&#xff1a;日志门面SLF4J1&#xff1a;什么是SLF4J2&#xff1a;SLF4J 解决了什么痛…...

redis数据类型:list

数据结构 源码版本&#xff1a;7.2.2路径&#xff1a;src/adlist.h 关于list的 头文件中涉及到的这三个结构体如下 /* Node, List, and Iterator are the only data structures used currently. */ # 节点 typedef struct listNode {struct listNode *prev; # 前元素的指针s…...

聚类之轮廓系数

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

时钟芯片入门指南:从原理到实践

DS1302时钟 实时时钟芯片&#xff0c;精度高、 DS1302芯片可以对年、月、日、周、时、分、秒进行计时&#xff0c;并且具有闰年补偿等多种功能。 采用三线接口与CPU进行同步通信(采用串行数据传送方式简单SPI 3线接口)&#xff0c;并可采用突发方式一次传送多个字节的时钟信号…...

【Java笔记】第十七章:反射

一、反射 1. 反射(Reflection)&#xff1a; 允许在程序运行状态中&#xff0c;可以获取任意类中的属性和方法&#xff0c;并且可以操作任意对象内部的属性和方法&#xff0c;这种动态获取类的信息及动态操作对象的属性和方法对应的机制称为反射机制。 2. 类对象 和 类的对象(实…...

Vue:实现输入框不能输负数功能

1、使用v-model指令 <input type"number" v-model"value" min"0" input"checkInput"> checkInput() {this.value Math.max(0, parseInt(this.value)); } 2、使用计算属性 <template><div><input type"…...

GamePlay UE网络同步

基本同步方式: ①未复制:函数仅在本机运行,不对任何人造成影响 ②在服务器上运行:当函数在客户端上调用时才能生效。客户端会通知服务器:“请在服务器上执行这个事件”,事件的具体内容会被在服务器上执行。 ③组播(多播,Multicast):当函数在服务器上调用时才能生效…...

iLoveIMG:强大的在线图片编辑工具分享

在数字化时代&#xff0c;图片处理已成为日常工作中不可或缺的一部分。无论是优化网页图片、调整尺寸、压缩处理还是格式转换&#xff0c;高效且免费的工具总是令人向往。今天&#xff0c;我要为大家介绍一个非常实用的在线图片编辑工具——iLoveIMG。它不仅功能强大&#xff0…...

重温设计模式--工厂模式(简单、工厂、抽象)

文章目录 工厂模式定义工厂模式通常可以细分为以下几种类型1、简单工厂模式&#xff08;Simple Factory Pattern&#xff09;2、工厂方法模式&#xff08;Factory Method Pattern&#xff09;3、抽象工厂模式&#xff08;Abstract Factory Pattern) UML 图1、简单工厂模式UML2、…...

人工智能ACA(六)--计算机视觉基础

一、计算机视觉概述 1. 计算机视觉定义 人工智能&#xff08;AI&#xff09;的一个重要分支旨在使计算机和系统能够从图像或多维数据中“理解”和“解释”视觉世界通过模拟人类视觉系统&#xff0c;计算机视觉技术能够自动执行诸如识别、分类、检测和跟踪等任务。 2. 计算机…...

WPF+MVVM案例实战与特效(四十六)- 打造动态背景时钟控件,轻松提升界面美感

文章目录 1、引言2、案例效果2、时钟控件封装1、创建用户控件2、依赖属性3、代码解释4、时钟图片资源3、控件使用4、源代码获取5、总结1、引言 在开发WPF应用程序时,创建一个美观且功能丰富的用户控件可以大大提升用户体验。今天,我们将深入探讨如何构建一个好看的时钟控件,…...

【读书笔记】《论语别裁》爱与罪

一、内容摘要 《论语别裁》第01章讨论了孔子关于孝悌的思想&#xff0c;以及其在中国文化中的重要性和复杂性。文中引用了有子的观点&#xff0c;强调孝弟是为人之本。然而&#xff0c;随着历史的发展&#xff0c;孔子的思想也被误解或被用作维护专制统治的工具。通过司马迁的…...

Log4j2漏洞

输入systemctl start docker启动docker 进入到CVE-2021-44228 输入docker-compose up -d开启环境 输入docker ps查看开启环境的端口 去访问靶场 打开dnslog平台&#xff0c;获取一个域名来监控我们所获得的内容 访问http://8.155.8.255:8983/solr/admin/cores?action${jndi:ld…...

ds刷题DAY1|66.加一、485. 最大连续 1 的个数

66. 加一 - 力扣&#xff08;LeetCode&#xff09; 从数组尾部开始遍历&#xff0c;遇到不是9的直接加一并返回&#xff1b;遇到等于9的变成0&#xff0c;并且继续判断下一位。如果全部为9&#xff0c;创建一个新数组&#xff0c;长度为原长度加一&#xff0c;首位为1&#xff…...

合合信息:探索视觉内容安全新前沿

2024年12月13日-15日&#xff0c;中国图象图形学学会在杭州召开。大会期间&#xff0c;来自合合信息的图像算法研发总监郭丰俊进行了主题为“视觉内容安全技术的前沿进展与应用”的演讲&#xff0c;介绍了视觉内容安全问题&#xff0c;并总结了现今的技术发展&#xff0c;对我很…...

C++23新特性解析:[[assume]]属性

1. 引言 在C的发展历程中&#xff0c;性能优化一直是一个核心主题。C23引入的[[assume]]属性为开发者提供了一个强大的工具&#xff0c;允许我们直接向编译器传达程序的不变量&#xff08;invariant&#xff09;&#xff0c;从而实现更好的代码优化。 1.1 为什么需要assume&a…...