logback VS log4j2 那些你注意不到的性能差距...

一、简介

logback, log4j2 等都是非常优秀的日志框架, 在日常使用中,我们很少会关注去使用哪一个框架, 但其实这些日志框架在性能方面存在明显的差异。 尤其在生产环境中, 有时候日志的性能高低,很可能影响到机器的成本, 像一些大企业,如阿里、腾讯、字节等,一点点的性能优化,就能节省数百万的支出。 再次, 统一日志框架也是大厂常有的规范化的事情, 还可以便于后续的ETL流程, 因此,我们选一个日志框架,其实还是比较重要的。

浅谈与slfj4、log4j, logback的关系

笼统的讲就是slf4j是一系列的日志接口,而log4j logback是具体实现了这些接口的日志框架,也可以简单理解为 slf4j 是接口, logback 和log4j是slf4j的具体实现, slf4j 具备很高的易用性和很好的抽象性。 使用SLF4J编写日志消息非常简单。首先需要调用 LoggerFactory 上的 getLogger 方法来实例化一个新的 Logger 对象。一共有两种方法:

方法1: 使用lombok (推荐)

直接在类上打上lombok的注解, 这个方法是最简单,代码量最小,编程效率最高的, 而且lombok组件在很多场景都很好用,

 

java

复制代码

@Slf4j public class Main {}

方法2: 直接使用

使用 org.slf4j.LoggerFactory 的 getLogger 方法获取logger实例,注意推荐 private static final

 

java

复制代码

private static final Logger LOG = LoggerFactory.getLogger(Main.class);

二、性能测试对比

性能对比图

compare.png

从上图可以得出两个结论:

  1. log4j2 全面优于 logback, log4j2性能是 logback的两倍
  2. 随着线程数量的增加, 日志输出能力并不会线性增加,在增加到约两倍于CPU核数的时候, 日志性能达到比较高的一个值。

tips:

已知的影响效率的是,打出方法名称和行号都会显著降低日志输出效率, 如我们单单去掉 行号,在单线程情况下, log4j2 的性能相差一倍多. 见下图:

format.png

附:测试环境:

1. 硬件环境:

 

yaml

复制代码

CPU AMD Ryzen 5 3600 6-Core Processor Base speed: 3.95 GHz Memory 32.0 GB Speed: 2666 MHz

2. jvm 信息

 

复制代码

JDK版本: semeru-11.0.20 JVM 参数:-Xms1000m -Xmx1000m

3. log4j2 和logback的版本

 

xml

复制代码

<log4j.version>2.22.1</log4j.version> <logback.version>1.4.14</logback.version>

4. 测试线程数和测试方式

 

bash

复制代码

线程数: 1 8 32 128 测试方式: 统一预热, 跑三次,取预热后的正式跑的平均值

4. 日志格式 日志格式对于log的效率会有非常大的影响, 有些时候则是天差地别。

 

xml

复制代码

<!-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>

5. 日志长度

长度大约 129个字符,常见长度 输出到文件 app.log, 格式统一, 一模一样

 

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!

三、 使用方法, 有需要的可以拿去.

1. logback在springboot项目中的使用

pom 文件, 不需要做任何事情, spring官方默认使用logback, 非spring项目可以直接引入下面的xml, 同时包含logback 和slf4j

 

xml

复制代码

<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency>

配置文件放置位置: src/main/resource/logback.xml, 样例如下:

 

xml

复制代码

<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <charset>utf-8</charset> </encoder> <file>log/output.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>log/output.log.%i</fileNamePattern> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>1MB</MaxFileSize> </triggeringPolicy> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </root> </configuration>

2. log4j2 在spring项目中的使用

由于spring官方默认使用logback,因此我们需要对spring默认的依赖进行排除然后再引入以下依赖:

 

xml

复制代码

<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

复制代码

<?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>

最佳实践:

  1. 滚动日志,永远不让磁盘满

    • 根据运行环境要求, 配置最大日志数量
    • 根据运行环境要求, 配置日志文件最大大小
  2. 日志如何使用才方便统计和定位问题

    • 统一日志格式,比如统一先打印方法名称,再打印参数列表
    • 写好要打印参数的 toString方法
  3. 日志如何配置性能才比较高

    • 日志配置应该遵循结构清晰,尽量简化的原则,能不让框架计算的,尽量不让框架计算, 比如方法名,行号等
  4. 全公司,或者个人使用习惯统一,这样有助于后续的日志收集、分析和统计

四、 附录:

1. 测试代码:

 

java

复制代码

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.*; @Slf4j public class 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); } }); } }

 

xml

复制代码

<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>

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值