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

标签: logback vs log4j2 | 发表时间:2024-01-25 17:44 | 作者:winjeg
出处:https://juejin.cn/backend

一、简介

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

浅谈与slfj4、log4j, logback的关系

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

方法1: 使用lombok (推荐)

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

  @Slf4j
public class Main {}

方法2: 直接使用

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

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

二、性能测试对比

性能对比图

compare.png

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

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

tips:

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

format.png

附:测试环境:

1. 硬件环境:

  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的版本

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

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

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

4. 日志格式 日志格式对于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>

5. 日志长度

长度大约 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!

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

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" 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默认的依赖进行排除然后再引入以下依赖:

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

最佳实践:

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

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

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

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

四、 附录:

1. 测试代码:

  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);
            }
        });
    }
}

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

2. 参考资料

这些参考资料有可能不太对, 但是为了方便大家查阅, 我还是给出了一些官方的和比较受欢迎的资料

相关 [logback vs log4j2] 推荐:

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

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

你还在用 Logback?Log4j2 的异步性能已经无敌了,还不快试试

- - SegmentFault 最新的文章
Logback 算是 JAVA 里一个老牌的日志框架,从 06 年开始第一个版本,迭代至今也十几年了. 不过 logback 最近一个稳定版本还停留在 2017 年,好几年都没有更新;logback 的兄弟 slf4j 最近一个稳定版也是 2017 年,有点凉凉的意思. 而且 logback 的异步性能实在拉跨,功能简陋,配置又繁琐,远不及 Apache 的新一代日志框架 -.

从Log4j迁移到LogBack的理由

- - 博客 - 伯乐在线
英文原文: Reasons to prefer logback over log4j,编译: oschina. 无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进. 不过尽管难以一一细数,这里还是列举部分理由为什么选择logback而不是log4j. 牢记logback与log4j在概念上面是很相似的,它们都是有同一群开发者建立.

Logback日志文件的配置

- - BlogJava-首页技术区
日志每天归档,同时目录名包含相应的年份和月份. 注意其中aux的使用,在fileNamePatter中如果出现多个%d的情况下,只能有一个为主配置,其他都需要使用aux标记为附属配置. 其中的%i请参看下节的介绍. 文件同时根据日期和大小滚动创建. fileNamePattern在上一节已经介绍,这里主要介绍timeBasedFileNamingAndTriggeringPolicy,此处配置对文件大小的限定,由fileNamePattern的%i在确定下标在文件名中的位置.

GIF vs APNG vs WebP

- - JayXon
GIF 是一个非常古老的格式,1987 年诞生,最后一个版本是 1989 年. (这就是为什么 GIF 文件头的 magic number 是 GIF89a). APNG 相对新一些,是 Mozilla 在 2004 年推出的,十几年的科技进步是不容小觑的,所以 APNG相对于 GIF 的优势十分明显,后面会分析.

使用Log4jdbc-log4j2监听MyBatis中运行的SQL和Connection

- - CSDN博客数据库推荐文章
引言: 在项目中使用了MyBatis,一个比较苦恼的问题是无法看到执行的SQL的具体情况,所以,就找到了Log4jdbc-log4j2. 这个是一个基于jdbc层面的监听工具,可以监听对于数据库的主要操作,从而完美的查看到其中执行的操作. Log4jdbc-log4j2版本选择.      如果项目是基于Maven工具,则可以直接使用如下依赖,即可完美解决问题:.

log4j与logback自定义文件存放目录方法

- - 研发管理 - ITeye博客
       为了方便日志的管理,我们在集群之间通过网络挂载的方式创建了一个共享目录即在所有的服务器上均可以访问此磁盘目录. 因此我们在写日志时需要根据集群的环境动态的设定日志的存储路径.        我们的工程日志的记录采用了两种方式log4j和sl4f+logback. 一、log4j自定义路径.

Spring-Boot 默认日志logback配置_Java_t0m的专栏-CSDN博客

- -
Spring-Boot官方开发指导文档. SpringBoot默认采用了logback日志系统,也支持Log4j2、JDK (Java Util Logging)、SLF4J、Commons Logging等. 下面说一下logback日志系统在SpringBoot中的配置. 如果日志需求简单,可以直接在application.properties中配置logback日志属性,否则可以自定义logback日志文件位置,然后根据自己需要配置logback内容.

转 redis vs memcached

- - 数据库 - ITeye博客
传统MySQL+ Memcached架构遇到的问题.   实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题:.   1.MySQL需要不断进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间.

NOSQL数据库大比拼:Cassandra vs MongoDB vs CouchDB vs Redis vs Riak vs HBase

- - 博客园_Ruby's Louvre
话说,尽管 SQL 数据库一直是我们IT行业中最有用的工具,然而,它们这样在行业中超过15年以上的“转正”终于就要寿终正寝了. 现在,虽然关系型数据库仍然无所不在,但它越来越不能满足我们的需要了. 但是,各种 "NoSQL" 数据库之间的差异比当年众多关系型数据库之间的差异要大许多. 这就加大了人们在建设自己的应用是选择合适的数据库的难度.