Java - 日誌(入門篇)

一、日誌的概念

日誌文件是用於記錄系統操作事件的文件集合,可分爲事件日誌和消息日誌。具有處理歷史數據、診斷問題的追蹤以及理解系統的活動等重要作用。

在計算機中,日誌文件是記錄在操作系統或其他軟件運行中發生的事件或在通信軟件的不同用戶之間的消息的文件。記錄是保持日誌的行爲。在最簡單的情況下,消息被寫入單個日誌文件。

許多操作系統,軟件框架和程序包括日誌系統。廣泛使用的日誌記錄標準是在因特網工程任務組(IETF)RFC5424中定義的syslog。 syslog標準使專用的標準化子系統能夠生成,過濾,記錄和分析日誌消息。

1.1 調試日誌

軟件開發中,我們經常需要去調試程序,做一些信息,狀態的輸出便於我們查詢程序的運行狀況。爲了讓我們能夠更加靈活和方便的控制這些調試的信息,所有我們需要專業的日誌技術。java中尋找bug會需要重現。調試也就是debug 可以在程序運行中暫停程序運行,可以查看程序在運行中的情況。日誌主要是爲了更方便的去重現問題。

1.2 系統日誌

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

系統日誌的價值
系統日誌策略可以在故障剛剛發生時就向你發送警告信息,系統日誌幫助你在最短的時間內發現問題。

系統日誌是一種非常關鍵的組件,因爲系統日誌可以讓你充分了解自己的環境。這種系統日誌信息對於決定故障的根本原因或者縮小系統攻擊範圍來說是非常關鍵的,因爲系統日誌可以讓你瞭解故障或者襲擊發生之前的所有事件。爲虛擬化環境制定一套良好的系統日誌策略也是至關重要的,因爲系統日誌需要和許多不同的外部組件進行關聯。良好的系統日誌可以防止你從錯誤的角度分析問題,避免浪費寶貴的排錯時間。另外一種原因是藉助於系統日誌,管理員很有可能會發現一些之前從未意識到的問題,在幾乎所有剛剛部署系統日誌的環境當中。

二、JAVA日誌框架

問題:

  1. 控制日誌輸出的內容和格式
  2. 控制日誌輸出的位置
  3. 日誌優化:異步日誌,日誌文件的歸檔和壓縮
  4. 日誌系統的維護
  5. 面向接口開發 – 日誌的門面

2.1 爲什麼要用日誌框架

因爲軟件系統發展到今天已經很複雜了,特別是服務器端軟件,涉及到的知識,內容,問題太多。在某些方面使用別人成熟的框架,就相當於讓別人幫你完成一些基礎工作,你只需要集中精力完成系統的業務邏輯設計。而且框架一般是成熟,穩健的,他可以處理系統很多細節問題,比如,事務處理,安全性,數據流控制等問題。還有框架一般都經過很多人使用,所以結構很好,所以擴展性也很好,而且它是不斷升級的,你可以直接享受別人升級代碼帶來的好處。

2.2 現有的日誌框架

JUL(java util logging)、logback、log4j、log4j2
JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)

日誌門面
JCL、slf4j

日誌實現
JUL、logback、log4j、log4j2

三、JUL 學習

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

3.1 JUL入門

3.1.1 架構介紹

在這裏插入圖片描述

  • Loggers:被稱爲記錄器,應用程序通過獲取Logger對象,調用其API來來發布日誌信息。Logger通常時應用程序訪問日誌系統的入口程序。
  • 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,將輸出內容進行排版。

3.1.2 入門案例

package top.onefine;

import org.junit.Test;

import java.util.logging.Level;
import java.util.logging.Logger;

public class JULTest {
    // Java獲取當前類名的兩種方法
    //  - 適用於非靜態方法:this.getClass().getName()
    //  - 適用於靜態方法:Thread.currentThread().getStackTrace()[1].getClassName()

    @Test
    public void testQuick() {
        // 1. 獲取日誌記錄器對象,參數需要一個唯一標識
        Logger logger = Logger.getLogger(this.getClass().getName());
        // 2. 日誌記錄輸出
        logger.info("hello jul");  // 日誌級別:info

        // 使用通用方法進行日誌記錄
        logger.log(Level.INFO, "info msg");

        // 通過佔位符的方式輸出變量的值
        String name = "one fine";
        int age = 18;
        logger.log(Level.INFO, "用戶信息:{0}, {1}", new Object[] {name, age});
    }
}

3.2 日誌的級別

jul中定義的日誌級別

* java.util.logging.Level中定義了日誌的級別:
	SEVERE(最高值)			# 嚴重
	WARNING					# 警告
	INFO (默認級別)			# 信息
	
	CONFIG					# 配置
	FINE					# 詳細
	FINER					# 較詳細
	FINEST(最低值)			# 非常詳細

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

雖然我們測試了7個日誌級別但是默認只實現info以上的級別

package top.onefine;

import org.junit.Test;

import java.util.logging.Logger;

public class JULTest {

    @Test
    public void testLogLevel() {
        // 1. 獲取日誌記錄器對象,參數需要一個唯一標識
        Logger logger = Logger.getLogger(this.getClass().getName());
        // 2. 日誌記錄輸出
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        // 默認情況下後面將不會輸出到控制檯(被過濾)
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

自定義日誌級別配置

package top.onefine;

import org.junit.Test;

import java.io.IOException;
import java.util.logging.*;

public class JULTest {

    // 自定義日誌級別
    @Test
    public void testLogConfig() throws IOException {
        // 1. 獲取日誌記錄器對象,參數需要一個唯一標識
        Logger logger = Logger.getLogger(this.getClass().getName());

        // 關閉系統默認配置
        logger.setUseParentHandlers(false);

        /* 自定義配置日誌級別
         */
        // 創建ConsoleHandler  控制檯輸出
        ConsoleHandler consoleHandler = new ConsoleHandler();

        // 創建簡單格式formatter轉換對象
        SimpleFormatter simpleFormatter = new SimpleFormatter();

        // 進行關聯
        consoleHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);

        // 配置日誌具體級別
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);

        /* 輸出日誌到文件
         */
        // 創建FileHandler  文件輸出
        FileHandler fileHandler = new FileHandler("d:/logs/jul.log");// 參數爲日誌文件放置位置,注意處理異常

        // 進行關聯
        fileHandler.setFormatter(simpleFormatter);
        logger.addHandler(fileHandler);

        // 配置日誌具體級別
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);

        // 2. 日誌記錄輸出
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

3.3 Logger之間的父子關係

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

package top.onefine;

import org.junit.Test;

import java.util.logging.*;

public class JULTest {

    // Logger對象父子關係
    @Test
    public void testLogParent() {
        Logger logger1 = Logger.getLogger("top.onefine");
        Logger logger2 = Logger.getLogger("top");
        System.out.println("logger2" + ((logger1.getParent() == logger2) ? "是" : "不是") + "logger1的父親");  // true
        System.out.println("logger2 parent: " + logger2.getParent() + ", " +
                "name: " + logger2.getParent().getName());

        /* 輸出:
            logger2是logger1的父親
            logger2 parent: java.util.logging.LogManager$RootLogger@79bc6541, name:
         */
        // LogManager$RootLogger是所有日誌記錄器的頂級父元素,name爲空串""


        /* 配置logger2的日誌級別
         */

        // 關閉logger2默認配置
        logger2.setUseParentHandlers(false);
        // 創建ConsoleHandler  控制檯輸出
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // 創建簡單格式formatter轉換對象
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        // 進行關聯
        consoleHandler.setFormatter(simpleFormatter);
        logger2.addHandler(consoleHandler);
        // 配置日誌具體級別
        logger2.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);

        logger1.severe("severe");
        logger1.warning("warning");
        logger1.info("info");
        logger1.config("config");
        logger1.fine("fine");
        logger1.finer("finer");
        logger1.finest("finest");
    }
}

結果:

logger2是logger1的父親
logger2 parent: java.util.logging.LogManager$RootLogger@79bc6541, name: 
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
嚴重: severe
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
嚴重: severe
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
警告: warning
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
警告: warning
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
信息: info
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
信息: info
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
配置: config
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
詳細: fine
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
較詳細: finer
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
非常詳細: finest

3.4 日誌的配置文件

默認配置文件路徑$JAVAHOME\jre\lib\logging.properties:

// C:\Program Files\Java\jre1.8.0_191\lib\logging.properties


############################################################
#  	Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.  
# For example java -Djava.util.logging.config.file=myfile
############################################################

############################################################
#  	Global properties
############################################################

# "handlers" specifies a comma separated list of log Handler 
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Example to customize the SimpleFormatter output format 
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

栗子

配置文件resources/logging.properties:

# 指定RootLogger(頂級父類元素)的處理器(獲取時設置)爲:ConsoleHandler和FileHandler  即同時指定
#handlers= java.util.logging.ConsoleHandler, java.util.logging.FileHandler

# RootLogger頂級父類元素的日誌等級:ALL
.level= ALL

## 文件處理器handle對象
# 輸出日誌級別
java.util.logging.FileHandler.pattern = d:/logs/java%u.log
# 輸出日誌文件限制大小(50000字節)
java.util.logging.FileHandler.limit = 50000
# 輸出日誌文件限制個數,5表示:0 - 4
java.util.logging.FileHandler.count = 1
# 以追加的方式添加日誌內容,默認false
java.util.logging.FileHandler.append = true
# 輸出日誌格式,這裏是XML
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
#java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter

## 控制檯處理器handle對象
# 輸出日誌級別
java.util.logging.ConsoleHandler.level = ALL
# 指定輸出日誌消息格式對象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定此控制檯處理器handler對象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 指定日誌消息格式:五月 17, 2020 8:16:07 上午 top.onefine.JULTest testLogProperties 默認
#java.util.logging.ConsoleHandler.format = %4$s: %5$s [%1$tc]%n


## 自定義Logger, name = top.onefine
# 指定處理器
top.onefine.handlers = java.util.logging.ConsoleHandler
# 設置日誌級別
top.onefine.level = CONFIG
# 關閉默認配置(忽略父日誌設置)
top.onefine.useParentHanlders = false

java/top/onefine/JULTest.java:

package top.onefine;

import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.*;

public class JULTest {

    // 加載自定義配置文件
    @Test
    public void testLogProperties() throws IOException {
        // 讀取配置文件,通過類加載器
        InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
        // 創建LogManager
        LogManager logManager = LogManager.getLogManager();
        // 通過LogManager加載配置文件
        logManager.readConfiguration(ins);  // 注意處理異常

        // 創建日誌記錄器
        Logger logger = Logger.getLogger(this.getClass().getName());

        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

3.5 日誌原理解析

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

在這裏插入圖片描述

四、LOG4J 學習

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

官方網站: http://logging.apache.org/log4j/1.2/

4.1 Log4j入門

  1. 建立maven工程
  2. 添加依賴
dependencies {
//    // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
//    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'

    // https://mvnrepository.com/artifact/log4j/log4j
    compile group: 'log4j', name: 'log4j', version: '1.2.17'
    // https://mvnrepository.com/artifact/junit/junit
    testCompile group: 'junit', name: 'junit', version: '4.13'
}
  1. java代碼
package top.onefine;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {

    @Test
    public void testQuick() {

        // 初始化配置信息,這裏暫不使用配置文件
        BasicConfigurator.configure();

        // 1. 獲取日誌記錄器對象
        Logger logger = Logger.getLogger(Log4jTest.class);
        // 2. 日誌記錄輸出
        logger.info("hello log4j!");

        // 日誌級別
        logger.fatal("fatal");  // 嚴重錯誤,一般會造成系統崩潰和終止運行
        // 最常用
        logger.error("error");  // 錯誤信息,但不會影響系統運行
        logger.warn("warn");    // 警告信息,可能會發生問題
        logger.info("info");    // 程序運行信息,數據庫的連接、網絡、IO操作等
        logger.debug("debug");  // 調試信息,一般在開發階段使用,記錄程序的變量、參數等
        // log4j默認級別是debug
        logger.trace("trace");  // 追蹤信息,記錄程序的所有流程信息
    }
}
  1. 日誌的級別
*每個Logger都被了一個日誌級別(log level),用來控制日誌信息的輸出。日誌級別從高到低分爲:
	fatal 指出每個嚴重的錯誤事件將會導致應用程序的退出。
	error 指出雖然發生錯誤事件,但仍然不影響系統的繼續運行。
	warn 表明會出現潛在的錯誤情形。
	info 一般和在粗粒度級別上,強調應用程序的運行全程。
	debug 一般用於細粒度級別上,對調試應用程序非常有幫助。默認級別
	
	trace 是程序追蹤,可以用於輸出程序運行中的變量,顯示執行的流程。

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

注:一般只使用4個級別,優先級從高到低爲 ERROR > WARN > INFO > DEBUG

4.2 Log4j組件

Log4J 主要由 Loggers (日誌記錄器)、Appenders(輸出端)和 Layout(日誌格式化器)組成。其中:

  • Loggers 控制日誌的輸出級別與日誌是否輸出;
  • Appenders 指定日誌的輸出方式(輸出到控制檯、文件等);
  • Layout 控制日誌信息的輸出格式。

resources/log4j.properties:

# 指定RootLogger頂級父元素默認配置信息
# 指定日誌級別爲trace,使用的appender爲console
log4j.rootLogger = trace, console
# 指定控制檯日誌輸出的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式layout
#log4j.appender.console.layout = org.apache.log4j.SimpleLayout
#log4j.appender.console.layout = org.apache.log4j.HTMLLayout
#log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout
# 自定義消息輸出格式及配置
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#log4j.appender.console.layout.conversionPattern =  %r [%t] %p %c %x - %m%n
#%d{yyyy年MM月dd日 HH:mm:ss.SSS} 表示 年月日時分秒毫秒
log4j.appender.console.layout.conversionPattern =  %d{yyyy-MM-dd HH:mm:ss.SSS}%n

java/top/onefine/Log4jTest.java:

package top.onefine;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;
import org.junit.Test;

public class Log4jTest {

    @Test
    public void testQuick() {
//        // 開啓log4j內置日誌記錄
//        LogLog.setInternalDebugging(true);
        
//        // 初始化配置信息,這裏暫不使用配置文件
//        BasicConfigurator.configure();

        // 1. 獲取日誌記錄器對象
        Logger logger = Logger.getLogger(Log4jTest.class);
        // 2. 日誌記錄輸出
        logger.info("hello log4j!");

        // 日誌級別
        logger.fatal("fatal");  // 嚴重錯誤,一般會造成系統崩潰和終止運行
        // 最常用
        logger.error("error");  // 錯誤信息,但不會影響系統運行
        logger.warn("warn");    // 警告信息,可能會發生問題
        logger.info("info");    // 程序運行信息,數據庫的連接、網絡、IO操作等
        logger.debug("debug");  // 調試信息,一般在開發階段使用,記錄程序的變量、參數等
        // log4j默認級別是debug
        logger.trace("trace");  // 追蹤信息,記錄程序的所有流程信息
    }
}

4.2.1 Loggers

日誌記錄器,負責收集處理日誌記錄,實例的命名就是類“XX”的full quailied name(類的全限定名),Logger的名字大小寫敏感,其命名有繼承機制:例如:name爲org.apache.commons的logger會繼承name爲org.apache的logger。

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

但是,自log4j 1.2版以來, Logger 類已經取代了 Category 類。對於熟悉早期版本的log4j的人來說,Logger 類可以被視爲 Category 類的別名。

在這裏插入圖片描述

4.2.2 Appenders

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

輸出端類型 作用
ConsoleAppender 將日誌輸出到控制檯
FileAppender 將日誌輸出到文件中
DailyRollingFileAppender 將日誌輸出到一個日誌文件,並且每天輸出到一個新的文件
RollingFileAppender 將日誌信息輸出到一個日誌文件,並且指定文件的尺寸,當文件大小達到指定尺寸時,會自動把文件改名,同時產生一個新的文件
JDBCAppender 把日誌信息保存到數據庫中

4.2.3 Layouts

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

格式化器類型 作用
HTMLLayout 格式化日誌輸出爲HTML表格形式
SimpleLayout 簡單的日誌輸出格式化,打印的日誌格式爲(info - message)
PatternLayout 最強大的格式化期,可以根據自定義格式輸出日誌,如果沒有指定轉換格式,就是用默認的轉換格式

4.3 Layout的格式

在 log4j.properties 配置文件中,我們定義了日誌輸出級別與輸出端,在輸出端中分別配置日誌的輸出格式。

* 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字符,就從左邊交遠銷出的字符截掉

4.4 Appender的輸出

控制檯,文件,數據庫

# 指定RootLogger頂級父元素默認配置信息
# 指定日誌級別爲trace,使用的appender爲console
#log4j.rootLogger = trace, console, file
#log4j.rootLogger = trace, rollingFile
#log4j.rootLogger = trace, dailyFile
#log4j.rootLogger = trace, logDB
log4j.rootLogger = trace, A
# 指定控制檯日誌輸出的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式layout
#log4j.appender.console.layout = org.apache.log4j.SimpleLayout
#log4j.appender.console.layout = org.apache.log4j.HTMLLayout
#log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout
# 自定義消息輸出格式及配置
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#log4j.appender.console.layout.conversionPattern =  %d [%t] %-5p [%c] - %m%n
#%d{yyyy-MM-dd HH:mm:ss.SSS} 表示 年月日時分秒毫秒
log4j.appender.console.layout.conversionPattern =  %d{yyyy-MM-dd HH:mm:ss-SSS}%n

## 日誌文件輸出的appender對象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的內容
log4j.appender.file.layout.conversionPattern = %r [%t] %p %c %x - %m%n
# 指定日誌文件保存路徑
log4j.appender.file.file = d:/logs/log4j.log
# 指定日誌文件的字符集
log4j.appender.file.encoding = UTF-8


# 文件輸出配置
log4j.appender.A = org.apache.log4j.DailyRollingFileAppender
#指定日誌的輸出路徑
log4j.appender.A.File = d:/logs/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


## 按照文件大小拆分的appender對象
# 日誌文件輸出的appender對象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的內容
log4j.appender.rollingFile.layout.conversionPattern = %r [%t] %p %c %x - %m%n
# 指定日誌文件保存路徑
log4j.appender.rollingFile.file = d:/logs/log4j.log
# 指定日誌文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日誌文件內容的大小
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日誌文件的數量
log4j.appender.rollingFile.maxBackupIndex = 10

## 按照時間拆分的appender對象
# 日誌文件輸出的appender對象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的內容
log4j.appender.dailyFile.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日誌文件保存路徑
log4j.appender.dailyFile.file = d:/logs/log4j.log
# 指定日誌文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 指定日期拆分的規則
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

# mysql
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.cj.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123456
log4j.appender.logDB.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) values('one fine','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

建表sql語句:

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

導入mysql依賴:

dependencies {
	// ...
    // https://mvnrepository.com/artifact/mysql/mysql-connector-java
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.20'
}

4.5 自定義Logger

# RootLogger配置
log4j.rootLogger = trace, console

# 自定義Logger
log4j.logger.top.onefine = info, file
log4j.logger.org.apache = error

# 指定控制檯日誌輸出的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式layout
#log4j.appender.console.layout = org.apache.log4j.SimpleLayout
#log4j.appender.console.layout = org.apache.log4j.HTMLLayout
#log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout
# 自定義消息輸出格式及配置
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern =  %d [%t] %-5p [%c] - %m%n

## 日誌文件輸出的appender對象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的內容
log4j.appender.file.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
# 指定日誌文件保存路徑
log4j.appender.file.file = d:/logs/log4j.log
# 指定日誌文件的字符集
log4j.appender.file.encoding = UTF-8
package top.onefine;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;
import org.junit.Test;

public class Log4jTest {

    @Test
    public void testQuick() {
//        // 開啓log4j內置日誌記錄
//        LogLog.setInternalDebugging(true);

//        // 初始化配置信息,這裏暫不使用配置文件
//        BasicConfigurator.configure();

        // 1. 獲取日誌記錄器對象
        Logger logger = Logger.getLogger(Log4jTest.class);
        // 2. 日誌記錄輸出

//        for (int i = 0; i < 100; i++) {
            // 日誌級別
            logger.fatal("fatal");  // 嚴重錯誤,一般會造成系統崩潰和終止運行
            // 最常用
            logger.error("error");  // 錯誤信息,但不會影響系統運行
            logger.warn("warn");    // 警告信息,可能會發生問題
            logger.info("info");    // 程序運行信息,數據庫的連接、網絡、IO操作等
            logger.debug("debug");  // 調試信息,一般在開發階段使用,記錄程序的變量、參數等
            // log4j默認級別是debug
            logger.trace("trace");  // 追蹤信息,記錄程序的所有流程信息
//        }

        // 自定義 org.apache
        Logger logger2 = Logger.getLogger(Logger.class);
        logger2.fatal("fatal logger2"); // 嚴重錯誤,一般會造成系統崩潰和終止運行
        logger2.error("error logger2"); // 錯誤信息,但不會影響系統運行
        logger2.warn("warn logger2"); // 警告信息,可能會發生問題
        logger2.info("info logger2"); // 程序運行信息,數據庫的連接、網絡、IO操作等
        logger2.debug("debug logger2"); // 調試信息,一般在開發階段使用,記錄程序的變量、參數等
        logger2.trace("trace logger2"); // 追蹤信息,記錄程序的所有流程信息

    }
}

控制檯輸出:

2020-05-17 20:58:13,203 [Test worker] FATAL [top.onefine.Log4jTest] - fatal
2020-05-17 20:58:13,206 [Test worker] ERROR [top.onefine.Log4jTest] - error
2020-05-17 20:58:13,207 [Test worker] WARN  [top.onefine.Log4jTest] - warn
2020-05-17 20:58:13,207 [Test worker] INFO  [top.onefine.Log4jTest] - info
2020-05-17 20:58:13,208 [Test worker] FATAL [org.apache.log4j.Logger] - fatal logger2
2020-05-17 20:58:13,208 [Test worker] ERROR [org.apache.log4j.Logger] - error logger2

五、JCL 學習【瞭解】

JCL全稱爲Jakarta Commons Logging,是Apache提供的一個通用日誌API。

它是爲 "所有的Java日誌實現"提供一個統一的接口,它自身也提供一個日誌的實現,但是功能非常常弱(SimpleLog),所以一般不會單獨使用它。他允許開發人員使用不同的具體日誌實現工具: Log4j, Jdk自帶的日誌(JUL)

JCL 有兩個基本的抽象類:

  • Log:基本記錄器
  • LogFactory:負責創建Log實例

在這裏插入圖片描述

5.1 JCL入門

  1. 建立gradle工程
  2. 添加依賴
dependencies {
    // https://mvnrepository.com/artifact/commons-logging/commons-logging
    compile group: 'commons-logging', name: 'commons-logging', version: '1.2'
    // https://mvnrepository.com/artifact/junit/junit
    testCompile group: 'junit', name: 'junit', version: '4.13'
//    // https://mvnrepository.com/artifact/log4j/log4j
//    compile group: 'log4j', name: 'log4j', version: '1.2.17'
}
  1. 入門代碼
package top.onefine;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;

public class JCLTest {

    @Test
    public void testQuick() {
        // 獲取log日誌記錄器對象
        Log log = LogFactory.getLog(JCLTest.class);
        // 日誌記錄輸出
        log.info("hello jcl");  // 若不導入任何日誌實現,則默認使用jdk14
    }
}

不導入log4j的輸出:

在這裏插入圖片描述
導入log4j的輸出:

在這裏插入圖片描述

我們爲什麼要使用日誌門面:

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

5.2 JCL原理

  1. 通過LogFactory動態加載Log實現類
    在這裏插入圖片描述
  2. 日誌門面支持的日誌實現數組
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"
		};
  1. 獲取具體的日誌實現
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
	result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}





一、日誌框架

日誌框架的能力:
  • 定製輸出目標
  • 定製輸出格式
  • 攜帶上下文信息
  • 運行時選擇性輸出
  • 靈活的配置
  • 優異的性能
名詞解釋:
  • 日誌門面:日誌的統一的接口層(抽象層)
  • 日誌實現:日誌接口的實現層
日誌門面 (日誌的抽象層) 日誌實現
JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-logging Log4j JUL(java.util.logging) Log4j2 Logback

左邊選一個門面(抽象層)、右邊來選一個實現:

  • 日誌門面: SLF4J
  • 日誌實現:Logback

SpringBoot選用 SLF4j和logback這個組合!另外,SpringBoot底層是Spring框架,Spring框架默認是用JCL。

二、SLF4j使用

1、如何在系統中使用SLF4j:https://www.slf4j.org

以後開發的時候,日誌記錄方法的調用不應該來直接調用日誌的實現類,而是調用日誌抽象層裏面的方法;

在這裏插入圖片描述
圖片來源: http://www.slf4j.org/manual.html

導入slf4j 和 logback 的座標:

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>

每一個日誌的實現框架都有自己的配置文件。使用slf4j(日誌門面)以後,配置文件還是做成 日誌實現框架 自己本身的配置文件。

2. 遺留問題

我的系統使用slf4j+logback,而Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx…

如何統一日誌記錄,即使是別的框架也和我的系統一起統一使用slf4j進行輸出?

在這裏插入圖片描述

圖片來源:http://www.slf4j.org/legacy.html

如何讓系統中所有的日誌都統一到slf4j:
  1. 將系統中其他日誌框架先排除出去;

  2. 用中間包來替換原有的日誌框架;

  3. 導入slf4j其他的實現;

3. 使用SLF4J

package top.onefine;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
public class LoggerTest {

    private Logger logger = LoggerFactory.getLogger(LoggerTest.class);  //

    @Test
    public void loggerTest() {
        // 日誌的級別由低到高:trace < debug < info < warn < error
        // SpringBoot默認日誌級別是:info級別(只會輸出info及以後的內容),可在配置文件中調整輸出的日誌級別
        logger.debug("debug...");
        logger.info("info...");
        logger.error("error...");
    }
}

或者引入lombok

package top.onefine;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j  // 引入lombok
public class LoggerTest {

//    private Logger logger = LoggerFactory.getLogger(LoggerTest.class);

    @Test
    public void loggerTest() {
        // 日誌的級別由低到高:trace < debug < info < warn < error
        // SpringBoot默認日誌級別是:info級別(只會輸出info及以後的內容),可在配置文件中調整輸出的日誌級別
        log.debug("debug...");
        log.info("info...");
        log.error("error...");
    }
}

使用變量:

三、SpringBoot日誌關係

在這裏插入圖片描述
總結:

  1. SpringBoot底層也是使用slf4j+logback的方式進行日誌記錄

  2. SpringBoot也把其他的日誌都替換成了slf4j;

如果要引入其他框架,一定要把這個框架的默認日誌依賴移除掉,如Spring框架用的是commons-logging,SpringBoot的做法:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring‐core</artifactId>
	<exclusions>
		<exclusion>
			<groupId>commons‐logging</groupId>
			<artifactId>commons‐logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>

SpringBoot能自動適配所有的日誌,而且底層使用slf4j+logback的方式記錄日誌,引入其他框架的時候,只需要把這個框架依賴的日誌框架排除掉即可;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章