切換語言為:簡體
同是優秀的日誌框架,log4j2 竟然比 logback 的效能高 2 倍!

同是優秀的日誌框架,log4j2 竟然比 logback 的效能高 2 倍!

  • 爱糖宝
  • 2024-05-24
  • 2091
  • 0
  • 0

一、簡介

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.LoggerFactory 的 getLogger 方法獲取logger例項,注意推薦 private static final

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


二、效能測試對比

效能對比圖

同是優秀的日誌框架,log4j2 竟然比 logback 的效能高 2 倍!

從上圖可以得出兩個結論:

  1. log4j2 全面優於 logback, log4j2效能是 logback的兩倍

  2. 隨著執行緒數量的增加, 日誌輸出能力並不會線性增加,在增加到約兩倍於CPU核數的時候, 日誌效能達到比較高的一個值。

tips:

已知的影響效率的是,打出方法名稱和行號都會顯著降低日誌輸出效率, 如我們單單去掉 行號,在單執行緒情況下, log4j2 的效能相差一倍多. 見下圖:

同是優秀的日誌框架,log4j2 竟然比 logback 的效能高 2 倍!

附:測試環境:

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. 參考資料

這些參考資料有可能不太對, 但是爲了方便大家查閱, 我還是給出了一些官方的和比較受歡迎的資料

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.