一、简介
logback、log4j2 等都是非常优秀的日志框架。在日常使用中,我们很少会关注去使用哪一个框架, 但其实这些日志框架在性能方面存在明显的差异。 尤其在生产环境中, 有时候日志的性能高低,很可能影响到机器的成本, 像一些大企业,如阿里、腾讯、字节等,一点点的性能优化,就能节省数百万的支出。 再次, 统一日志框架也是大厂常有的规范化的事情, 还可以便于后续的 ETL 流程, 因此,我们选一个日志框架,其实还是比较重要的。
1.1 浅谈 slfj4、log4j 与 logback 的关系
笼统的讲就是 slf4j 是一系列的日志接口,而 log4j、logback 是具体实现了这些接口的日志框架。也可以简单理解为 slf4j 是接口, logback 和 log4j 是 slf4j 的具体实现。slf4j 具备很高的易用性和很好的抽象性。 使用 slf4j 编写日志消息非常简单。首先需要调用 LoggerFactory 上的 getLogger 方法来实例化一个新的 Logger 对象。
有两种方法:
方法1: 使用 lombok (推荐)
直接在类上打上 lombok 的注解, 这个方法是最简单,代码量最小,编程效率最高的, 而且 lombok 组件在很多场景都很好用。
@Slf4jpublic Main {}方法2: 直接使用
使用 org.slf4j.LoggerFactory 的 getLogger 方法获取 logger 实例,注意推荐 private static final。
private static final Logger LOG = LoggerFactory.getLogger(Main.class);二、性能测试对比
2.1 性能对比图
从上图可以得出两个结论:
log4j2 全面优于 logback。log4j2 性能是 logback 的两倍;随着线程数量的增加, 日志输出能力并不会线性增加,在增加到约两倍于 CPU 核数的时候, 日志性能达到比较高的一个值。Tips: 已知的影响效率的是,打出方法名称和行号都会显著降低日志输出效率, 如我们单单去掉 行号,在单线程情况下, log4j2 的性能相差一倍多。见下图:
2.2 测试环境
1. 硬件环境:
CPU AMD Ryzen 5 3600 6-Core Processor Base speed: 3.95 GHzMemory 32.0 GB Speed: 2666 MHz2. JVM 信息
JDK版本:semeru-11.0.20JVM参数:-Xms1000m -Xmx1000m3. log4j2 和 logback 版本
<log4j.version>2.22.1</log4j.version><logback.version>1.4.14</logback.version>4. 测试线程数和测试方式
线程数:1 8 32 128测试方式:统一预热, 跑三次,取预热后的正式跑的平均值5. 日志格式
日志格式对于 log 的效率会有非常大的影响, 有些时候则是天差地别。
<!-log4j2 的配置 --><Property name="log.pattern">[%d{yyyyMMdd HH:mm:ss.SSS}] [%t] [%level{length=4}] %c{1.}:%L %msg%n</Property><!-logback 的配置 --><pattern>[%date{yyyyMMdd HH:mm:ss.SSS}] [%thread] [%-4level] %logger{5}:%line %msg%n</pattern>6. 日志长度长度大约 129 个字符,常见长度 输出到文件 app.log, 格式统一。
[20240125 16:24:27.716] [thread-3] [INFO] c.w.d.Main:32 main - info level ...this is a demo script, pure string log will be used![20240125 16:24:27.716] [thread-1] [INFO] c.w.d.Main:32 main - info level ...this is a demo script, pure string log will be used!三、 使用方法
3.1 logback 在 SpringBoot 项目中的使用
pom 文件, 不需要做任何事情, Spring 官方默认使用 logback, 非 Spring 项目可以直接引入下面的 XML, 同时包含 logback 和 slf4j。
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version></dependency>配置文件放置位置: src/main/resource/logback.xml 样例如下:
<?xml version="1.0" encoding="UTF-8"?><configuration><appender name="CONSOLE">3.2 log4j2 在 Spring 项目中的使用由于 Spring 官方默认使用l ogback,因此我们需要对 Spring 默认的依赖进行排除然后再引入以下依赖:
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>${log4j.version}</version></dependency>配置文件放置位置: src/main/resource/log4j2.xml 样例如下:
<?xml version="1.0" encoding="UTF-8"?><Configuration><Properties><!-- 定义日志格式 --><Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property><!-- 定义文件名变量 --><Property name="file.err.filename">log/err.log</Property><Property name="file.err.pattern">log/err.%i.log.gz</Property></Properties><!-- 定义Appender,即目的地 --><Appenders><!-- 定义输出到屏幕 --><Console name="console" target="SYSTEM_OUT"><!-- 日志格式引用上面定义的log.pattern --><PatternLayout pattern="${log.pattern}" /></Console><!-- 定义输出到文件,文件名引用上面定义的file.err.filename --><RollingFile name="err" bufferedIO="true" fileName="${file.err.filename}" filePattern="${file.err.pattern}"><PatternLayout pattern="${log.pattern}" /><Policies><!-- 根据文件大小自动切割日志 --><SizeBasedTriggeringPolicy size="1 MB" /></Policies><!-- 保留最近10份 --><DefaultRolloverStrategy max="10" /></RollingFile></Appenders><Loggers><Root level="info"><!-- 对info级别的日志,输出到console --><AppenderRef ref="console" level="info" /><!-- 对error级别的日志,输出到err,即上面定义的RollingFile --><AppenderRef ref="err" level="error" /></Root></Loggers></Configuration>3.3 最佳实践
1. 滚动日志,永远不让磁盘满
根据运行环境要求, 配置最大日志数量与日志文件最大大小。
2. 日志如何使用才方便统计和定位问题
统一日志格式,比如统一先打印方法名称,再打印参数列表。写好要打印参数的 toString() 方法。
3. 日志如何配置性能才比较高
日志配置应该遵循结构清晰,尽量简化的原则,能不让框架计算的,尽量不让框架计算, 比如方法名、行号等。
4. 全公司或者个人使用习惯统一
这样有助于后续的日志收集、分析和统计。
四、测试代码
package com.winjeg.demo;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.concurrent.BasicThreadFactory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.ArrayList;import java.util.List;import java.util.concurrent.*;@Slf4jpublic Main {private static final Logger LOG = LoggerFactory.getLogger(Main.class);private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(128, 256, 1L, TimeUnit.MINUTES, new ArrayBlockingQueue<>(512),new BasicThreadFactory.Builder().namingPattern("thread-%d").daemon(true).build());public static void main(String[] args) {long start = System.currentTimeMillis(); execute(8, 160_000);long first = System.currentTimeMillis(); execute(8, 160_000); System.out.printf("time cost, preheat:%d\t, formal:%d\n", first - start, System.currentTimeMillis() - first); }private static void execute(int threadNum, int times) { List<Future<?>> futures = new ArrayList<>();for (int i = 0; i < threadNum; i++) { Future<?> f = EXECUTOR.submit(() -> {for (long j = 0; j < times; j++) { log.info("main - info level ...this is a demo script, pure string log will be used!"); } }); futures.add(f); } futures.forEach(f -> {try { f.get(); } catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e); } }); }}<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.winjeg.spring</groupId><artifactId>demo</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><log4j.version>2.22.1</log4j.version><logback.version>1.4.14</logback.version><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>${log4j.version}</version></dependency><!-- <dependency>--><!-- <groupId>ch.qos.logback</groupId>--><!-- <artifactId>logback-classic</artifactId>--><!-- <version>${logback.version}</version>--><!-- </dependency>--></dependencies></project>参考文档
Log4J - Performance:https://logging.apache.org/log4j/2.x/performance.htmlBenchmarking synchronous and asynchronous logging:https://logback.qos.ch/performance.htmlJava日志框架:log4j vs logback vs log4j2:https://zhuanlan.zhihu.com/p/472941897转自:winjeg,链接:juejin.cn/post/7327878308757520419