Java 日誌框架學習筆記

来源:https://www.cnblogs.com/Yee-Q/archive/2022/11/07/16864734.html
-Advertisement-
Play Games

日誌概念 1. 日誌文件 日誌文件是用於記錄系統操作事件的文件集合 1.1 調試日誌 1.2 系統日誌 系統日誌是記錄系統中硬體、軟體和系統問題的信息,同時還可以監視系統中發生的事件。用戶可以通過它來檢查錯誤發生的原因,或者尋找受到攻擊時攻擊者留下的痕跡 日誌門面 當我們的系統變的更加複雜的時候,我 ...


日誌概念

1. 日誌文件

日誌文件是用於記錄系統操作事件的文件集合

1.1 調試日誌
1.2 系統日誌

系統日誌是記錄系統中硬體、軟體和系統問題的信息,同時還可以監視系統中發生的事件。用戶可以通過它來檢查錯誤發生的原因,或者尋找受到攻擊時攻擊者留下的痕跡


日誌門面

當我們的系統變的更加複雜的時候,我們的日誌就容易發生混亂。隨著系統開發的進行,可能會更新不同的日誌框架,造成當前系統中存在不同的日誌依賴,讓我們難以統一的管理和控制。所以我們需要借鑒 JDBC 的思想,為日誌系統也提供一套門面,那麼我們就可以面向這些介面規範來開發,避免了直接依賴具體的日誌框架。這樣我們的系統在日誌中,就存在了日誌的門面和日誌的實現

常見的日誌門面:JCL、Slf4j

常見的日誌實現:JUL、log4j、logback、log4j2

日誌門面和日誌實現的關係:

使用日誌門面的優勢:

  • 面向介面開發,不再依賴具體的實現類,減少代碼的耦合
  • 項目通過導入不同的日誌實現類,可以靈活的切換日誌框架
  • 統一 API,方便開發者學習和使用
  • 統一配置便於項目日誌的管理

JUL

JUL(Java util Logging)是 Java 原生的日誌實現框架,使用時不需要另外引用第三方類庫,相對其他日誌框架使用方便,學習簡單,能夠在小型應用中靈活使用

1. 架構

  • Loggers:記錄器,應用程式通過獲取 Logger 對象,調用其 API 來發佈日誌信息
  • Appenders:也稱為 Handlers,每個 Logger 都會關聯一組 Handlers,Logger 會將日誌交給關聯 Handlers 處理,由 Handlers 負責將日誌做記錄。Handlers 在此是一個抽象,其具體的實現決定了 日誌記錄的位置可以是控制台、文件、網路上的其他日誌服務或操作系統日誌等
  • Layouts:也稱為 Formatters,它負責對日誌事件中的數據進行轉換和格式化。Layouts 決定了數據在一條日誌記錄中的最終形式
  • Level:每條日誌消息都有一個關聯的日誌級別,該級別粗略指導了日誌消息的重要性和緊迫,可以將Level 和 Loggers、Appenders 做關聯以便於我們過濾消息
  • Filters:過濾器,根據需要定製哪些信息會被記錄,哪些信息會被放過

總結一下就是:

  • 用戶使用 Logger 來進行日誌記錄,Logger 持有若幹個 Handler,日誌的輸出操作是由 Handler 完成的
  • Handler 在輸出日誌前,會經過 Filter 的過濾,判斷哪些日誌級別過濾放行哪些攔截,Handler 會將日誌內容輸出到指定位置(日誌文件、控制台等)
  • Handler 在輸出日誌時會使用 Layout,將輸出內容進行排版

2. 入門案例

public class JULTest {
    
    @Test
    public void testQuick() throws Exception {
        // 1.創建日誌記錄器對象
        Logger logger = Logger.getLogger("com.itheima.log.JULTest");
        // 2.日誌記錄輸出
        logger.info("hello jul");
        logger.log(Level.INFO, "info msg");
        String name = "jack";
        Integer age = 18;
        logger.log(Level.INFO, "用戶信息:{0},{1}", new Object[]{name, age});
    }
}

3. 日誌級別

JUL 中定義的日誌級別:SEVERE(最高值)、WARNING、INFO(預設級別)、CONFIG、FINE、FINER、FINEST(最低值)

還有兩個特殊的級別:OFF,可用來關閉日誌記錄、ALL,啟用所有消息的日誌記錄

@Test
public void testLogLevel() throws Exception {
    // 1.獲取日誌對象
    Logger logger = Logger.getLogger("com.itheima.log.QuickTest");
    // 2.日誌記錄輸出
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("cofnig");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

雖然我們測試了 7 個日誌級別,但是預設只實現 info 以上的級別,info 以下不會輸出,我們可以自定義日誌級別配置

@Test
public void testLogConfig() throws Exception {
    
    // 創建日誌記錄器對象
    Logger logger = Logger.getLogger("com.itheima.log.JULTest");
    
    // 一、自定義日誌級別
    // a.關閉系統預設配置
    logger.setUseParentHandlers(false);
    // b.創建handler對象
    ConsoleHandler consoleHandler = new ConsoleHandler();
    // c.創建formatter對象
    SimpleFormatter simpleFormatter = new SimpleFormatter();
    // d.進行關聯
    consoleHandler.setFormatter(simpleFormatter);
    logger.addHandler(consoleHandler);
    // e.設置日誌級別
    logger.setLevel(Level.ALL);
    consoleHandler.setLevel(Level.ALL);
    
    // 二、輸出到日誌文件
    FileHandler fileHandler = new FileHandler("d:/logs/jul.log");
    fileHandler.setFormatter(simpleFormatter);
    
    // 日誌記錄輸出
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

4. logger 之間的父子關係

JUL 中 Logger 之間存在父子關係,這種父子關係通過樹狀結構存儲,JUL 在初始化時會創建一個頂層 RootLogger 作為所有 Logger 父 Logger,存儲上作為樹狀結構的根節點,父子關係通過包路徑來關聯

@Test
public void testLogParent() throws Exception {
    
    // 日誌記錄器對象父子關係
    Logger logger1 = Logger.getLogger("com.itheima");
    Logger logger2 = Logger.getLogger("com");
    // true
    System.out.println(logger1.getParent() == logger2);
    
    // 所有日誌記錄器對象的頂級父元素 class 為 java.util.logging.LogManager$RootLogger
    // logger2 parent:java.util.logging.LogManager$RootLogger,name:
    System.out.println("logger2 parent:" + logger2.getParent() + ",name" +
    logger2.getParent().getName());
    
    // 一、自定義日誌級別
    // a.關閉系統預設配置
    logger2.setUseParentHandlers(false);
    // b.創建handler對象
    ConsoleHandler consoleHandler = new ConsoleHandler();
    // c.創建formatter對象
    SimpleFormatter simpleFormatter = new SimpleFormatter();
    // d.進行關聯
    consoleHandler.setFormatter(simpleFormatter);
    logger2.addHandler(consoleHandler);
    // e.設置日誌級別
    logger2.setLevel(Level.ALL);
    consoleHandler.setLevel(Level.ALL);
    
    // 測試日誌記錄器對象父子關係,這裡會全部輸出,因為logger1繼承logger2的級別配置
    logger1.severe("severe");
    logger1.warning("warning");
    logger1.info("info");
    logger1.config("config");
    logger1.fine("fine");
    logger1.finer("finer");
    logger1.finest("finest");
}

5. 日誌的配置文件

預設配置文件路徑 $JAVAHOME\jre\lib\logging.properties

@Test
public void testProperties() throws Exception {
    // 讀取自定義配置文件
    InputStream in = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
    // 獲取日誌管理器對象
    LogManager logManager = LogManager.getLogManager();
    // 通過日誌管理器載入配置文件
    logManager.readConfiguration(in);
    Logger logger = Logger.getLogger("com.itheima.log.JULTest");
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

配置文件:

## RootLogger使用的處理器(獲取時設置)
handlers= java.util.logging.ConsoleHandler
# RootLogger日誌等級
.level= INFO

# 自定義Logger
com.itheima.handlers= java.util.logging.FileHandler
# 自定義Logger日誌等級
com.itheima.level= INFO
# 忽略父日誌設置
com.itheima.useParentHandlers=false

## 控制台處理器
# 輸出日誌級別
java.util.logging.ConsoleHandler.level = ALL
# 輸出日誌格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定handler對象的字元集
java.util.logging.ConsoleHandler.encoding = UTF-8

# 指定日誌消息格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

## 文件處理器
# 輸出日誌級別
java.util.logging.FileHandler.level=INFO
# 輸出日誌格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 輸出日誌文件路徑
# /logs/java0.log
java.util.logging.FileHandler.pattern = /logs/java%u.log
# 輸出日誌文件限制大小(50000位元組)
java.util.logging.FileHandler.limit = 50000
# 輸出日誌文件限制個數
java.util.logging.FileHandler.count = 10
# 輸出日誌文件是否是追加
java.util.logging.FileHandler.append=true

6. 日誌原理解析

  1. 初始化 LogManager
    • LogManager 載入 logging.properties 配置
    • 添加 Logger 到 LogManager
  2. 從單例 LogManager 獲取 Logger
  3. 設置級別 Level,並指定日誌記錄 LogRecord
  4. Filter 提供了日誌級別之外更細粒度的控制
  5. Handler 是用來處理日誌輸出位置
  6. Formatter 是用來格式化 LogRecord 的

Log4J

Log4j 是Apache 下的一款開源的日誌實現框架,通過在項目中使用 Log4J,我們可以控制日誌信息輸出到控制台、文件、甚至是資料庫中。我們可以控制每一條日誌的輸出格式,通過定義日誌的輸出級別,可以更靈活的控制日誌的輸出過程,方便項目調試

1. 入門案例

<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>
public class Log4jTest {
    
    @Test
    public void testQuick() throws Exception {
        
        // 初始化系統配置,不需要配置文件
        BasicConfigurator.configure();
        
        // 創建日誌記錄器對象
        Logger logger = Logger.getLogger(Log4jTest.class);
        
        // 日誌記錄輸出
        logger.info("hello log4j");
        
        // 日誌級別
        logger.fatal("fatal"); // 嚴重錯誤,一般會造成系統崩潰和終止運行
        logger.error("error"); // 錯誤信息,但不會影響系統運行
        logger.warn("warn"); // 警告信息,可能會發生問題
        logger.info("info"); // 程式運行信息,資料庫的連接、網路、IO操作等
        logger.debug("debug"); // 調試信息,一般在開發階段使用,記錄程式的變數、參數等
        logger.trace("trace"); // 追蹤信息,記錄程式的所有流程信息
    }
}

2. 日誌級別

每個 Logger 都被定義了一個日誌級別,用來控制日誌信息的輸出,日誌級別從高到低分為:

  • fatal:指出每個嚴重的錯誤事件將會導致應用程式的退出
  • error:指出雖然發生錯誤事件,但仍然不影響系統的繼續運行
  • warn:表明會出現潛在的錯誤情形
  • info:一般和在粗粒度級別上,強調應用程式的運行全程
  • debug:一般用於細粒度級別上,對調試應用程式非常有幫助
  • trace 是程式追蹤,可以用於輸出程式運行中的變數,顯示執行的流程

還有兩個特殊的級別:OFF 可用來關閉日誌記錄、ALL 啟用所有消息的日誌記錄

3. Log4j 組件

3.1 Loggers

日誌記錄器,負責收集處理日誌記錄,實例的命名就是類的全限定名

Logger 的名字大小寫敏感,其命名有繼承機制,例如:name 為 org.apache.commons 的 logger 會繼承 name 為 org.apache 的 logger

Log4J 中有一個特殊的 logger 叫做 root,他是所有 logger 的根,可以用 Logger.getRootLogger() 方法獲取,其他所有的 logger 都會直接或間接地繼承自 root

3.2 Appenders

Appender 用來指定日誌輸出到哪個地方,可以同時指定日誌的輸出目的地。Log4j 常用的輸出目的地有以下幾種:

  • ConsoleAppender:將日誌輸出到控制台
  • FileAppender:將日誌輸出到文件中
  • DailyRollingFileAppender:將日誌輸出到一個日誌文件,並且每天輸出到一個新的文件
  • RollingFileAppender:將日誌信息輸出到一個日誌文件,並且指定文件的尺寸,當文件大小達到指定尺寸時,會自動把文件改名,同時產生一個新的文件
  • JDBCAppender:把日誌信息保存到資料庫中
3.3 Layouts

佈局器 Layouts 用於控制日誌輸出內容的格式,讓我們可以使用各種需要的格式輸出日誌。Log4j 常用的 Layouts:

  • HTMLLayout:格式化日誌輸出為 HTML 表格形式
  • SimpleLayout:簡單的日誌輸出格式化,列印的日誌格式為(info - message)
  • PatternLayout:最強大的格式化期,可以根據自定義格式輸出日誌,如果沒有指定轉換格式,就使用預設的轉換格式

4. 配置文件詳解

#指定日誌的輸出級別與輸出端
log4j.rootLogger=INFO,Console

# 自定義Logger
# com.itheima包下的logger使用文件輸出配置
log4j.logger.com.itheima = info,file
# apache包下的logger輸出error級別日誌
log4j.logger.org.apache = error

# 控制台輸出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
# log4j 採用類似 C 語言的 printf 函數的列印格式格式化日誌信息,具體的占位符及其含義如下:
# %m 輸出代碼中指定的日誌信息
# %p 輸出優先順序,及 DEBUG、INFO 等
# %n 換行符(Windows平臺的換行符為 "\n",Unix 平臺為 "\n")
# %r 輸出自應用啟動到輸出該 log 信息耗費的毫秒數
# %c 輸出列印語句所屬的類的全名
# %t 輸出產生該日誌的線程全名
# %d 輸出伺服器當前時間,預設為 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日
# HH:mm:ss}
# %l 輸出日誌時間發生的位置,包括類名、線程、及在代碼中的行數。如:
# Test.main(Test.java:10)
# %F 輸出日誌消息產生時所在的文件名稱
# %L 輸出代碼中的行號
# %% 輸出一個 "%" 字元
# 可以在 % 與字元之間加上修飾符來控制最小寬度、最大寬度和文本的對其方式。如:
# %5c 輸出category名稱,最小寬度是5,category<5,預設的情況下右對齊
# %-5c 輸出category名稱,最小寬度是5,category<5,"-"號指定左對齊,會有空格
# %.5c 輸出category名稱,最大寬度是5,category>5,就會將左邊多出的字元截掉,<5不會有空格
# %20.30c category名稱<20補空格,並且右對齊,>30字元,就從左邊交遠銷出的字元截掉
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

# 文件輸出配置
log4j.appender.A = org.apache.log4j.DailyRollingFileAppender
#指定日誌的輸出路徑
log4j.appender.A.File = D:/log.txt
log4j.appender.A.Append = true
#使用自定義日誌格式化器
log4j.appender.A.layout = org.apache.log4j.PatternLayout
#指定日誌的輸出格式
log4j.appender.A.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] -
[%p] %m%n
#指定日誌的文件編碼
log4j.appender.A.encoding=UTF-8

#mysql
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=root
log4j.appender.logDB.Sql=INSERT INTO
log(project_name,create_date,level,category,file_name,thread_name,line,all_categ
ory,message) values('itcast','%d{yyyy-MM-dd
HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
CREATE TABLE `log` (
    `log_id` int(11) NOT NULL AUTO_INCREMENT,
    `project_name` varchar(255) DEFAULT NULL COMMENT '目項名',
    `create_date` varchar(255) DEFAULT NULL COMMENT '創建時間',
    `level` varchar(255) DEFAULT NULL COMMENT '優先順序',
    `category` varchar(255) DEFAULT NULL COMMENT '所在類的全名',
    `file_name` varchar(255) DEFAULT NULL COMMENT '輸出日誌消息產生時所在的文件名稱 ',
    `thread_name` varchar(255) DEFAULT NULL COMMENT '日誌事件的線程名',
    `line` varchar(255) DEFAULT NULL COMMENT '號行',
    `all_category` varchar(255) DEFAULT NULL COMMENT '日誌事件的發生位置',
    `message` varchar(4000) DEFAULT NULL COMMENT '輸出代碼中指定的消息',
    PRIMARY KEY (`log_id`)
);

JCL

全稱為 Jakarta Commons Logging,是 Apache 提供的一個日誌門面實現,為所有的 Java 日誌實現提供一個統一的介面,它自身也提供一個日誌的實現,但是功能較弱,它允許開發人員使用不同的具體日誌實現工具,如:Log4J,JUL

JCL 有兩個基本的抽象類:Log(基本記錄器)和 LogFactory(負責創建 Log 實例)

1. 入門案例

<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.2</version>
</dependency>
public class JULTest {
    
    @Test
    public void testQuick() throws Exception {
        // 創建日誌對象
        Log log = LogFactory.getLog(JULTest.class);
        // 日誌記錄輸出
        log.fatal("fatal");
        log.error("error");
        log.warn("warn");
        log.info("info");
        log.debug("debug");
    }
}

2. JCL 原理

JCL 通過 LogFactory 動態載入 Log 實現類

. 獲取具體的日誌實現的源碼如下:

private static final String[] classesToDiscover =
	new String[]{"org.apache.commons.logging.impl.Log4JLogger",
					"org.apache.commons.logging.impl.Jdk14Logger",
					"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
					"org.apache.commons.logging.impl.SimpleLog"};


for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
	result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}

classesToDiscover 數組是 JCL 支持的日誌實現數組,對它進行遍歷,嘗試通過反射去實現


SLF4J

SLF4J(Simple Logging Facade For Java)主要是為了給 Java 日誌訪問提供一套標準、規範的 API 框架,其主要意義在於提供介面,具體的實現可以交由其他日誌框架,例如 log4j 和 logback 等

SLF4J 也提供了功能較為簡單的實現,但是一般很少用到。對於一般的 Java 項目而言,日誌框架會選擇 slf4j-api 作為門面,配上具體的實現框架(log4j、logback 等),中間使用橋接器完成橋接

SLF4J 的優勢:

  • SLF4J 提供了對所有流行的日誌框架的綁定,可以在部署時切換到所需的日誌記錄框架
  • 無論使用哪種綁定,SLF4J 都支持參數化日誌記錄消息
  • SLF4J 提供了一個稱為橋接器的工具,可以將現有項目的使用日誌框架(JCL、JUL、Log4j)遷移到 SLF4J

1. 入門案例

<!--slf4j core 使用slf4j必須添加-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.27</version>
</dependency>
<!--slf4j 自帶的簡單日誌實現 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.27</version>
</dependency>
public class Slf4jTest {
    
    // 聲明日誌對象
    public final static Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
    
    @Test
    public void testQuick() throws Exception {
        
        // 列印日誌信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
        
        // 使用占位符輸出日誌信息
        String name = "jack";
        Integer age = 18;
        LOGGER.info("用戶:{},{}", name, age);
        
        // 將系統異常信息寫入日誌
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            // e.printStackTrace();
            LOGGER.info("出現異常:", e);
        }
    }
}

2. 綁定日誌的實現

SLF4J 提供了用於綁定日誌實現框架的 Jar 文件,每個綁定對應一個受支持的框架

使用 SLF4J 的日誌綁定流程如下:

  1. 添加 slf4j-api 的依賴
  2. 使用 slf4j 的 API 在項目中進行統一的日誌記錄
  3. 綁定具體的日誌實現框架
    • 綁定已經實現了 slf4j 的日誌框架,可直接添加對應依賴
    • 綁定沒有實現 slf4j 的日誌框架,先添加日誌的適配器,再添加實現類的依賴
  4. slf4j 有且僅有一個日誌實現框架的綁定(如果出現多個預設使用第一個依賴日誌實現)

通過 maven 引入常見的日誌實現框架:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.27</version>
</dependency>

<!-- log4j 適配器 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.27</version>
</dependency>
<!-- log4j 依賴 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

<!-- jul 適配器 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.27</version>
</dependency>
<!-- jul 是 jdk 自帶的日誌實現,不需要引入依賴 -->

<!-- logback 依賴 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

要切換日誌框架,只需替換類路徑上的 slf4j 綁定,例如,要從 java.util.logging 切換到 log4j,只需將 java.util.logging 的依賴替換為 log4j 的依賴即可

如果出現多個日誌實現。預設載入第一個實現,也就是會使用 log4j

3. 橋接舊的日誌框架

項目初期使用了一個簡單的日誌實現框架,如 Log4J,後續需要升級成 Slf4J + Logback,但又不想修改原有 Log4J 的代碼,因此可以使用橋接器,將原本對 Log4J 的 API 調用重定向到 Slf4J 的實現

例如,最初的項目使用了 Log4J

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
public class Log4jTest {
    
    // 聲明日誌對象
    public final static Logger LOGGER = Logger.getLogger(Log4jTest.class);
    
    @Test
    public void test01() throws Exception {
        // 列印日誌信息
        LOGGER.info("hello log4j");
    }
}

如果我們要使用 SLF4J 的橋接器,替換原有的日誌框架,那麼我們需要做的第一件事情,就是刪除掉原有項目中的日誌框架的依賴,然後替換成 SLF4J 提供的橋接器

<!-- <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency> -->

<!-- log4j 橋接器 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>

註意的問題:

  • log4j-over-slf4j.jar 和 slf4j-log4j12.jar 不能同時出現,前一個 jar 文件將導致 log4j 將日誌系統的選擇委托給 slf4j,後一個 jar 文件將導致 slf4j 將日誌系統的選擇委托給 log4j,從而導致無限迴圈,其他日誌實現框架同理
  • 所有的橋接都只對 Logger 日誌記錄器對象有效,如果程式中調用了內部的配置類或者是 Appender、Filter 等對象,將無法產生效果

4. SLF4J 原理解析

  1. SLF4J 通過 LoggerFactory 載入日誌具體的實現對象
  2. LoggerFactory 在初始化的過程中,會通過 performInitialization() 方法綁定具體的日誌實現
  3. 在綁定具體實現的時候,通過類載入器,載入 org/slf4j/impl/StaticLoggerBinder.class
  4. 所以,只要是一個日誌實現框架,在 org.slf4j.impl 包中提供一個自己的StaticLoggerBinder類,在其中提供具體日誌實現的 LoggerFactory 就可以被 SLF4J 所載入

Logback

Logback 是由 log4j 創始人設計的另一個開源日誌組件,性能優於 log4j

Logback 主要分為三個模塊:

  • logback-core:其它兩個模塊的基礎模塊
  • logback-classic:它是 log4j 的一個改良版本,同時它完整實現了 slf4j API
  • logback-access:訪問模塊與 Servlet 容器集成提供通過 Http 來訪問日誌的功能

1. 入門案例

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
public class LogBackTest {
    
    //定義日誌對象
	public final static Logger LOGGER = LoggerFactory.getLogger(LogBackTest.class);

    @Test
    public void testSlf4j(){
        //列印日誌信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

2. Logback 配置

Logback 會依次讀取以下類型配置文件,如果均不存在會採用預設配置:

  • logback.groovy
  • logback-test.xml
  • logback.xml

基本配置信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<!-- 
		格式化輸出:
			%d:日期
			%thread:線程名
			%-5level:級別從左顯示5個字元寬度
			%n:換行符
			%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
			%c:類的完整名稱
			%M:方法名
            %L:行號
            %thread:線程名稱
            %m或%msg:日誌信息
	-->
	<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/>
    
	<!-- 
		Appender:設置日誌信息的去向,常用的有以下幾個:
			ch.qos.logback.core.ConsoleAppender (控制台)
			ch.qos.logback.core.rolling.RollingFileAppender (文件大小到達指定尺
寸的時候產生一個新文件)
			ch.qos.logback.core.FileAppender (文件)
	-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 輸出流對象預設 System.out 改為 System.err-->
        <target>System.err</target>
        <!--日誌格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        	<pattern>${pattern}</pattern>
        </encoder>
    </appender>

	<!-- 日誌文件存放目錄 -->
	<property name="log_dir" value="d:/logs"></property>

    <!-- 日誌文件輸出 appender 對象 -->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!-- 日誌格式配置 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        	<pattern>${pattern}</pattern>
        </encoder>
        <!-- 日誌輸出路徑 -->
        <file>${log_dir}/logback.log</file>
    </appender>
    
    <!-- 生成 html 格式 appender 對象 -->
    <appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
        <!-- 日誌格式配置 -->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        	<layout class="ch.qos.logback.classic.html.HTMLLayout">
        		<pattern>${pattern}</pattern>
        	</layout>
        </encoder>
        <!--日誌輸出路徑-->
        <file>${log_dir}/logback.html</file>
    </appender>
    
    <!-- 日誌文件拆分和歸檔的appender對象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日誌格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        	<pattern>${pattern}</pattern>
        </encoder>
        <!--日誌輸出路徑-->
        <file>${log_dir}/roll_logback.log</file>
        <!--指定日誌文件拆分和壓縮規則-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通過指定壓縮文件名稱,來確定分割文件方式-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!-- 日誌級別過濾 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 日誌級別 -->
            <level>error</level>
            <!-- 對error級別及以上的日誌進行記錄 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 攔截error級別以下的日誌 -->
			<onMismatch>DENY</onMismatch>
		</filter>
    </appender>
    
    <!-- 開啟非同步日誌記錄,提升性能 -->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
    	<appender-ref ref="rollFile"/>
    </appender>
    
    <!-- RootLogger 對象 -->
    <!-- level:用來設置列印級別,大小寫無關:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,預設debug -->
    <!-- <root>可以包含零個或多個<appender-ref>元素,標識這個appender將會添加到這個logger-->
    <root level="all">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
        <appender-ref ref="htmlFile"/>
    </root>
    
    <!--自定義 logger -->
    <!-- name:指定受此logger約束的某一個包或者具體的某一個類 -->
    <!-- level:用來設置列印級別,大小寫無關:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,預設繼承父級的級別 -->
    <!-- additivity:是否從 rootLogger 繼承配置 -->
    <logger name="com.itheima" level="debug" additivity="false">
    	<appender-ref ref="async"/>
    </logger>
    
</configuration>

Log4j2

Log4j2 也是日誌門面,因為它的日誌實現功能非常強大,性能優越,所以一般還是將 Log4j2 看作是日誌的實現

1. 入門案例

<!-- Log4j2 門面API-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>
<!-- Log4j2 日誌實現 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
public class Log4j2Test {
    
    // 定義日誌記錄器對象
    public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
    
    @Test
    public void testQuick() throws Exception {
        LOGGER.fatal("fatal");
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

也可以使用 slf4j 作為日誌的門面,log4j2 作為日誌的實現

<!-- 使用slf4j作為日誌的門面,使用log4j2來記錄日誌 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- log4j2的適配器 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.10.0</version>
</dependency>

<!-- Log4j2 門面API-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>
<!-- Log4j2 日誌實現 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>

2. Log4j2 配置

log4j2 預設載入 classpath 下的 log4j2.xml 文件中的配置

<?xml version="1.0" encoding="UTF-8"?>
<!--
	status:日誌本身的輸出日誌級別
	monitorInterval:檢查配置文件是否更新的間隔時間
-->
<Configuration status="warn" monitorInterval="5">
    
    <!-- 配置屬性管理,使用時通過 ${name} 來引用 -->
	<properties>
		<property name="LOG_HOME">D:/logs</property>
	</properties>
    
    <!-- 日誌處理 -->
    <Appenders>
        
        <!-- 控制台輸出 -->
		<Console name="Console" target="SYSTEM_OUT">
			<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
		</Console>
        
        <!-- 日誌文件輸出 -->
		<File name="file" fileName="${LOG_HOME}/myfile.log">
			<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
		</File>
        
        <!-- 使用隨機讀寫流的日誌文件輸出 -->
		<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
			<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
		</RandomAccessFile>
        
        <!-- 按照規則拆分日誌文件輸出 -->
		<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!-- 日誌級別過濾器 -->
			<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <!-- 日誌消息格式 -->
			<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <!-- 拆分規則 -->
            <Policies>
                <!-- 系統啟動時,生成一個新的日誌文件 -->
                <OnStartupTriggeringPolicy />
                <!-- 按照指定的大小拆分 -->
                <SizeBasedTriggeringPolicy size="10 MB" />
                <!-- 按照時間節點拆分,規則由filePattern定義 -->
                <TimeBasedTriggeringPolicy />
            </Policies>
            <!-- 同一目錄下,文件個數限定為30個,超過將進行覆蓋 -->
			<DefaultRolloverStrategy max="30" />
		</RollingFile>
        
        <!-- logger 定義 -->
        <Loggers>
            <!-- rootLogger 配置 -->
            <Root level="trace">
            	<AppenderRef ref="Console" />
                <AppenderRef ref="file" />
                <AppenderRef ref="accessFile" />
                <AppenderRef ref="rollingFile" />
            </Root>
        </Loggers>
        
</Appenders>

3. Log4j2 非同步日誌

Log4j2 提供了兩種實現日誌的方式,一個是通過 AsyncAppender,一個是通過 AsyncLogger,分別對應前面的 Appender 組件和 Logger 組件

配置非同步日誌需要添加依賴

<!-- 非同步日誌依賴 -->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.4</version>
</dependency>

使用 AsyncAppender 的方式

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    
	<properties>
		<property name="LOG_HOME">D:/logs</property>
	</properties>
    
	<Appenders>
		<File name="file" fileName="${LOG_HOME}/myfile.log">
			<PatternLayout>
				<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
			</PatternLayout>
		</File>
		<Async name="Async">
			<AppenderRef ref="file"/>
		</Async>
	</Appenders>
    
	<Loggers>
		<Root level="error">
			<AppenderRef ref="Async"/>
		</Root>
	</Loggers>
    
</Configuration

AsyncLogger 是官方推薦的非同步方式,它可以使得調用 Logger.log 返回得更快,並且有兩種選擇:全局非同步和混合非同步

  • 全局非同步:所有的日誌都非同步記錄,配置文件上不用做任何改動,只需要在類路徑添加一個名為 log4j2.component.properties 配置文件,並包含如下內容:

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    
  • 混合非同步:可以在應用中同時使用同步日誌和非同步日誌,這使得日誌的配置方式更加靈活,如下配置: com.itheima 日誌是非同步的,root 日誌是同步的

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        
    	<properties>
    		<property name="LOG_HOME">D:/logs</property>
    	</properties>
        
    	<Appenders>
    		<File name="file" fileName="${LOG_HOME}/myfile.log">
    			<PatternLayout>
    				<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
    			</PatternLayout>
    		</File>
    	</Appenders>
        
    	<Loggers>
            <!-- 自定義非同步 logger 對象 -->
            <!--
    			includeLocation="false":關閉日誌記錄的行號信息,列印位置信息會急劇降低非同步日誌的性能
     		-->
    		<AsyncLogger name="com.itheima" level="trace" includeLocation="false" additivity="false">
    			<AppenderRef ref="file"/>
    		</AsyncLogger>
    		<Root level="info" includeLocation="true">
    			<AppenderRef ref="file"/>
    		</Root>
    	</Loggers>
        
    </Configuration>
    

4. 無垃圾模式

垃圾收集暫停是延遲峰值的常見原因,並且對於許多系統而言,花費大量精力來控制這些暫停

許多日誌庫(包括以前版本的 Log4j)在穩態日誌記錄期間分配臨時對象,如日誌事件對象、字元串、字元數組、位元組數組等。這會對垃圾收集器造成壓力並增加 GC 暫停發生的頻率

從 2.6 版本開始,預設情況下 Log4j 以無垃圾模式運行,重用對象和緩衝區,儘可能不分配臨時對象

還有一個低垃圾模式,它不是完全無垃圾,但不使用 ThreadLocal 欄位,Log4j 2.6 中的無垃圾日誌記錄部分通過重用 ThreadLocal 欄位中的對象來實現,部分通過在將文本轉換為位元組時重用緩衝區來實現

有兩個單獨的系統屬性可用於手動控制 Log4j 用於避免創建臨時對象的機制:

  • log4j2.enableThreadlocals:如果為 true(預設),對象存儲在 ThreadLocal 欄位中並重新使用,否則將為每個日誌事件創建新對象
  • log4j2.enableDirectEncoders:如果為 true(預設),日誌事件轉換為文本,則將此文本轉換為位元組而不創建臨時對象。註意:由於共用緩衝區上的同步,在此模式下多線程應用程式的同步日誌記錄性能可能更差,如果您的應用程式是多線程的並且日誌記錄性能很重要,請考慮使用非同步記錄器

SpringBoot 日誌使用

Springboot 預設使用 SLF4J 作為日誌門面,logback 作為日誌實現來記錄日誌

1. SpringBoot 的日誌設計

<dependency>
    <artifactId>spring-boot-starter-logging</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

依賴關係圖:

  1. Springboot 底層預設使用 logback 作為日誌實現,並提供了其他日誌實現框架的橋接器進行轉換
  2. Springboot 使用了 SLF4J 作為日誌門面,也可以使用如 Log4j2 等日誌門面,但最終還是通過 slf4j 進行調用 logback

2. SpringBoot 日誌使用

在 springboot 中測試列印日誌

@SpringBootTest
class SpringbootLogApplicationTests {
	//記錄器
	public static final Logger LOGGER = LoggerFactory.getLogger(SpringbootLogApplicationTests.class);
    
    @Test
    public void contextLoads() {
        // 列印日誌信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info"); // 預設日誌級別
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

修改預設日誌配置

logging.level.com.itheima=trace
# 在控制台輸出的日誌的格式 同logback
logging.pattern.console=%d{yyyy-MM-dd} [%thread] [%-5level] %logger{50} - %msg%n
# 指定文件中日誌輸出的格式
logging.file=D:/logs/springboot.log
logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n

給類路徑下放上每個日誌框架自己的配置文件,SpringBoot 就不使用預設配置的了

  • Logback :logback-spring.xml,logback.xml
  • Log4j2:log4j2-spring.xml ,log4j2.xml
  • JUL:logging.properties

使用 SpringBoot 可以切換不同環境的日誌配置,如在 logback-spring.xml 添加如下內容:

...
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
	<springProfile name="dev">
		<pattern>${pattern}</pattern>
	</springProfile>
	<springProfile name="pro">
		<pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
	</springProfile>
</encoder>
...

application.properties 配置如下:

spring.profiles.active=dev

如果希望將日誌切換為 log4j2

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- 排除 logback -->
        <exclusion>
            <artifactId>spring-boot-starter-logging</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
	</exclusions>
</dependency>
<!-- 添加log4j2 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • call,apply,bind都是一種方法。 一,call() ①:call() 是可以 調用函數的。 1 function fn() { 2 console.log(12) 3 } 4 5 fn.call() // 12 ②:通過給call() 內部傳參,可以改變 this指向。 1 let Do ...
  • React 全家桶-React基礎 用於構建用戶界面的JavaScript庫。 facebook開源、組件化、聲明式編碼、React Native移動端開發、虛擬DOM+Diffing演算法 官網:https://react.docschina.org/ 第一章:React的基本使用 1.相關js庫 ...
  • 具體需求 在我的疫情可視化項目中有一個功能需要導出word文檔,在頁面點擊按鈕後處理數據生成word文件,然後自動下載文檔。 實現步驟 多番查詢後發現前端導出word,使用docxtemplater較為方便。具體使用步驟如下: 安裝docxtemplater:npm i docxtemplater ...
  • 導讀:對於錯誤碼的設計,不同的開發團隊有不同的風格習慣。本文分享阿裡文娛技術專家長統對於錯誤碼的看法,希望從錯誤碼使用的不同場景討論得到一個合理的錯誤碼規約,得到一個面嚮日志錯誤碼標準和一個面向外部傳遞的錯誤碼標準。 ...
  • 1、UML統一建模語言 定義:用於軟體系統設計與分析的語言工具 目的:幫助開發人員更好的梳理邏輯、思路 學習地址:UML概述_w3cschool 官網:https://www.omg.org/spec/UML 1.1、UML組成結構 flowchart TD; UML圖 --> 結構圖 & 行為圖; ...
  • 一、人狗大戰 1、需求 用代碼模擬人、狗打架的小游戲 人和狗種類不同,因此雙方的屬性各不相同 推導一: 人和狗各有不同屬性 使用字典方式儲存屬性較為方便,並可儲存多種屬性 # 1、在字典內儲存‘人’屬性 person = { 'name': '阿拉蕾', 'age': 18, 'gender': ' ...
  • 您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ Lambda表達式雖然將介面作為代碼塊賦值給了變數,但如果僅僅只是Lambda表達式,還無法讓Java由量變引起質變。真正讓Lambda能夠發揮出巨大威力的,就是流式計算。 所謂流式計算,就是讓數據像在流水線上一樣,從一道工序流轉到下一道工序。 ...
  • 簡介: 橋接模式又叫橋梁模式,屬於結構型模式。目的是將抽象與實現分離,使它們都可以獨立的變化,解耦。繼承有很多好處,但是會增加耦合,而橋接模式偏向組合和聚合的方式來共用。 適用場景: 不希望或不適用使用多繼承的場景。 一個類存在2個或更多的 獨立變化維度 , 並且這些維度都需要 獨立擴展 優點: 解 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...