spring boot log4j2 最佳實踐


為什么選擇 log4j2

Log4j2 使用了 LMAX Disruptor 庫。在多線程場景中,異步 Logger 的吞吐量比 Log4j 1.x 和 Logback 高 18 倍,延遲低幾個數量級。如下是官網的性能對比:

image
上圖來源:https://logging.apache.org/log4j/2.x/performance.html

最終效果

  • 日志輸出整齊有序
  • 不同級別日志采用不同顏色輸出,更加清晰直觀;
  • 日志輸出可帶有用戶標識或特定標識,有利於查看同組的多個請求軌跡、排查問題;
  • 不同環境采用不同配置,本地測試日志只輸出到控制台,其他環境日志輸出到指定文件;
  • 線上日志按天分割,歷史日志按月壓縮存放,定期刪除之前日志,便於排查問題,防止日志累積達到磁盤上限;
  • 警告錯誤日志再單獨輸出到錯誤日志文件中,便於快速定位問題;

日志效果圖

代碼示例

maven 依賴

點擊查看代碼
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
       <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

注入標識過濾器

點擊查看代碼
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class UidLogFilter extends OncePerRequestFilter {

    private static final String UID = "Uid";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String uid = request.getHeader(UID);

        if (!StringUtils.hasLength(uid)){
            uid = request.getParameter(UID);
        }
        /**
         * 將用戶標識放入日志上下文中
         */
        MDC.put("uid", uid);
        filterChain.doFilter(request, response);
    }
}

BestPracticeApplication

點擊查看代碼
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@SpringBootApplication
public class BestPracticeApplication {

    /**
     *  根據請求參數輸出不同級別的日志
     */
    @GetMapping("/")
    public String home(String level) {
        String msg = "Hello World!";
        try {
            switch (level){
                case "trace": log.trace(msg); break;
                case "debug": log.debug(msg); break;
                case "info": log.info(msg); break;
                case "warn": log.warn(msg); break;
                case "error": log.error(msg); break;
                //帶參數的日志輸出方式
                default: log.error("未知的日志級別: {}", level); break;
            }
        }catch (NullPointerException e){
            //異常日志輸出方式
            log.error("日志級別為空", e);
        }
        return msg;
    }

    @Bean
    public UidLogFilter uidLogFilter(){
        return new UidLogFilter();
    }

    static {
        System.setProperty("log4j.skipJansi", "false");
    }


    public static void main(String[] args) {
        SpringApplication.run(BestPracticeApplication.class, args);
    }

}

application.yml

點擊查看代碼
spring:
  profiles:
    active: local

---
#本地環境
spring:
  config:
    name: local

logging:
  config: classpath:log4j2-local.xml
---
#測試環境
spring:
  config:
    name: test
---
#線上環境
spring:
  config:
    name: online

log4j2-local.xml

點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
    <Properties>
        <Property name="LOG_PATTERN_LAYOUT">%d{HH:mm:ss,SSS} %highlight{[%-5.5level]}{STYLE=Logback} [%-5.5thread] %blue{[%-5.5X{uid}]} %cyan{[%-40.40c{1.}:%-4line]} - %msg%xEx%n</Property>
        <Property name="DEFAULT_CHARSET">UTF-8</Property>
    </Properties>

    <Appenders>
        <!-- 定義控制台輸出 -->
        <Console name="Console" target="SYSTEM_OUT" follow="true">
            <PatternLayout  charset="${DEFAULT_CHARSET}" pattern="${LOG_PATTERN_LAYOUT}"/>
        </Console>
    </Appenders>

    <Loggers>
        <!-- 包名以cn.learncoding 開頭的日志輸出級別為TRACE -->
        <Logger name="cn.learncoding" level="TRACE"/>
        <!-- 默認日志輸出級別為INFO -->
        <Root level="INFO">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

log4j2.xml

點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
    <Properties>
        <!-- ${sys:catalina.home:-.}/logs 表示 取系統變量catalina.home指向的目錄下面的logs目錄,如果沒有則取當前所在目錄下面的logs目錄 -->
        <Property name="LOG_HOME">${sys:catalina.home:-.}/logs</Property>
        <Property name="LOG_BACK_HOME">${sys:catalina.home:-.}/logs/backup</Property>

        <!-- 日志輸出格式  -->
        <!-- %-5.5thread 表示最少長度為5,不足空格補齊,超出5則只保留后5位  -->
        <!-- %highlight{[%-5.5level]}{STYLE=Logback} 表示此字段使用Logback格式的高亮顏色展示,最少長度為5,不足空格補齊,超出5則只保留后5位  -->
        <!-- %blue{[%-5.5X{uid}]} 表示此上下文字段采用藍色進行輸出,最少長度為5,不足空格補齊,超出5則只保留后5位  -->
        <Property name="LOG_PATTERN_LAYOUT">%d{HH:mm:ss,SSS} %highlight{[%-5.5level]}{STYLE=Logback} [%-5.5thread] %blue{[%-5.5X{uid}]} %cyan{[%-40.40c{1.}:%-4line]} - %msg%xEx%n </Property>
        <Property name="DEFAULT_CHARSET">UTF-8</Property>
        <Property name="ERROR_FILE_NAME">error</Property>
        <Property name="INFO_FILE_NAME">info</Property>
    </Properties>

    <Appenders>
        <!-- 配置日常日志  歷史日志按月存放,按天分割壓縮存儲-->
        <RollingFile name="${INFO_FILE_NAME}" fileName="${LOG_HOME}/${INFO_FILE_NAME}.log" filePattern="${LOG_BACK_HOME}/$${date:yyyy-MM}/${INFO_FILE_NAME}-%d{yyyy-MM-dd}.log.gz" append="true">
            <PatternLayout charset="${DEFAULT_CHARSET}" pattern="${LOG_PATTERN_LAYOUT}"/>
            <Policies>
                <!-- 基於時間的滾動策略,按天分割 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <DefaultRolloverStrategy >
                <!--刪除30天前的日志-->
                <Delete basePath="${LOG_BACK_HOME}" maxDepth="2">
                    <IfFileName glob="*/*.log.gz" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
        <!-- 配置錯誤日志 歷史日志按月存放,按天分割壓縮存儲-->
        <RollingFile name="${ERROR_FILE_NAME}" fileName="${LOG_HOME}/${ERROR_FILE_NAME}.log" filePattern="${LOG_BACK_HOME}/$${date:yyyy-MM}/${ERROR_FILE_NAME}-%d{yyyy-MM-dd}.log.gz" append="true">
            <PatternLayout charset="${DEFAULT_CHARSET}" pattern="${LOG_PATTERN_LAYOUT}"/>
            <Policies>
                <!-- 基於時間的滾動策略,按天分割 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <Filters>
                <!--級別大於等於WARN的日志可以寫入-->
                <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- 默認日志輸出級別為INFO -->
        <Root level="INFO">
            <AppenderRef ref="${INFO_FILE_NAME}"/>
            <AppenderRef ref="${ERROR_FILE_NAME}"/>
        </Root>
    </Loggers>
</Configuration>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM