1. 程式人生 > 實用技巧 >java自帶的Logger日誌系統

java自帶的Logger日誌系統

java自帶的Logger日誌系統

探索

一手文件

Logger是幹什麼的?

Logger物件的建立

結構

什麼是Logger名稱空間?

建立Logger物件的方式

Logger物件的級別

簡述

分析API

LogRecord是幹啥的?Handler是幹啥的?

Handler物件

探索

在練習Servlet基礎的時候,我想著是要寫一個輸出請求連結的日誌攔截器,但是真到寫doFilter這塊時,自己又一臉懵逼,憑藉著感覺寫,只知道有Logger這個物件,怎麼使用的不會,百度一下簡單抄了兩行程式碼:

Logger logger = Logger.getLogger("filter");
logger.log(Level.ALL,攔截的地址:
" + req.getRequestURL());

雖然也能正常工作,也有輸出,但是自己整個過程一臉懵逼,很難受。下定決心打算研究研究這玩意。

一手文件

自己英文不咋好,還好有中文版的JDK6可以閱讀,找到java.util.logging下面的Logger類,閱讀基本介紹。

Logger是幹什麼的?

Logger物件是用來記錄特定系統或應用程式元件的日誌訊息。Logger具有層次結構,層次結構使用圓點分割表示,比如說FruitSales.AppleSales,就代表著如下圖:

前提是你要有這個FruitSales才行。

Logger物件的建立

結構

每一個Logger物件都有一個唯一的名稱,這個名稱可以是任意的字串,但是最好是帶有一定的層次結構,比如說基於包名或類名。

Logger fruitsaleslogger = Logger.getLogger("com.sales.fruitsales");
Logger applesaleslogger = Logger.getLogger("com.sales.fruitsales.applesales");

也可以建立"匿名"的Logger,但是使用匿名的Logger物件,它的名稱不會儲存在Logger名稱空間中。

什麼是Logger名稱空間?

Logger名稱空間就像是一個Map集合,儲存著所有的logger物件,名稱空間的key就是logger物件名,而value就是logger物件。上面說了,Logger具有一定的層次結構,每一個Logger物件都會跟蹤一個"父"Logger,也就是Logger名稱空間中與其

最近的祖先,(TreeMap?)。拿這兩個Logger物件來說:

Logger fruitsaleslogger = Logger.getLogger("com.sales.fruitsales");
Logger applesaleslogger = Logger.getLogger("com.sales.fruitsales.applesales")

我們建立的fruitsaleslogger物件,並沒有指定它的祖先Logger,這裡的祖先並不是說在它前面有圓點,圓點前面的就一定是它的祖先,不是這樣的,我們並沒有宣告sales這個Logger物件所以fruitsaleslogger就不存在saleslogger,不存在它會繼續向上找,找comlogger,同樣也不存在comlogger。如果一個logger物件沒有顯示宣告它的祖先logger,則其祖先logger為RootLogger,也就是根Logger物件,所以fruitsaleslogger的祖先Logger為RootLogger。而在上面applesaleslogger的祖先最近的是fruitsaleslogger,所有其祖先Logger即fruitsaleslogger。口說無憑,我們看一個例子:

package logger;

import java.util.logging.Logger;

public class LoggerTest {
    public static void main(String[] args) {
        Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales");
        Logger appleSaleslogger = Logger.getLogger("com.sales.fruitsales.applesales");
        Logger fspLogger = fruitSaleslogger.getParent();
        Logger aspLogger = appleSaleslogger.getParent();

        System.out.println("fruitSaleslogger:" + fruitSaleslogger);
        System.out.println("fruitSaleslogger的祖先Logger是:" + fspLogger);

        System.out.println("============================");

        System.out.println("appleSaleslogger:" + appleSaleslogger);
        System.out.println("appleSaleslogger的祖先Logger是:" + aspLogger);
    }
}

列印結果:

fruitSaleslogger:java.util.logging.Logger@3d4eac69
fruitSaleslogger的祖先Logger是:java.util.logging.LogManager$RootLogger@42a57993
============================
appleSaleslogger:java.util.logging.Logger@75b84c92
appleSaleslogger的祖先Logger是:java.util.logging.Logger@3d4eac69

建立Logger物件的方式

Logger通過呼叫getLogger(String name)工廠方法來獲得Logger物件,在該方法的引數name若在Logger的名稱空間裡不存在,則建立一個新的logger物件,否則返回該logger物件。

Logger appLogger = Logger.getLogger("appLogger");

Logger物件的級別

簡述

每個Logger物件都有一個與其相關的“Level”,標誌著其輸出的級別,如果“Level”被設定為null(預設為null),那麼它的有效級別繼承自父logger物件,這可以通過其父logger一直沿樹向上遞迴得到(當心父logger會突然改變其級別,子類為null的也都會隨著改變)。

各級別按降序排列如下:

SEVERE(最高值) :指示嚴重失敗的訊息級別。

WARNING:指示潛在問題的訊息級別。

INFO(最常用):報告訊息的訊息級別。

CONFIG:用於靜態配置訊息的訊息級別。

FINE:提供跟蹤訊息的訊息級別。

FINER:指示一條相當詳細的跟蹤訊息。

FINEST(最低值):指示一條最詳細的跟蹤訊息。

OFF:是一個可用於關閉日誌記錄的特殊級別。

ALL:指示應該記錄所有訊息。

null:預設為null時,級別低於INFO,但在CONFIG之上。

分析API

1:” 對於每次日誌記錄呼叫,Logger最初都依照logger的有效日誌級別對請求級別(例如SEVERE或FINE)進行簡單的檢查。如果請求級別低於日誌級別,則日誌記錄呼叫將立即返回【摘抄API】

上面這兩句話是什麼意思?什麼是有效日誌級別和請求級別?如果你查閱過API就大概能明白,對於"每次日誌記錄的呼叫"說的即是:呼叫log(Level level, String mas)或info(String msg)等輸出方法。"有效日誌級別"即該logger物件最初設定的級別( logger.setLevel(Level level) ,也可能為null,為null時級別低於INFO,但在CONFIG之上)。"請求級別"即呼叫log方法裡的Level引數或者info方法的Level.INFO。上面標紅的那句話我們用例項證明,我們來測試一下:

1:預設時我沒設定Level,即Level = null(有效日誌級別null)

public static void main(String[] args) {
        Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales");
        
        fruitSaleslogger.severe("1==========");// 高於 null
        fruitSaleslogger.warning("2==========");// 高於 null
        fruitSaleslogger.info("3==========");//高於 null
        fruitSaleslogger.config("4==========");// 低於 null
    }

控制檯結果:

五月 05, 2019 6:48:05 下午 logger.LoggerTest main
嚴重: 1==========
五月 05, 2019 6:48:05 下午 logger.LoggerTest main
警告: 2==========
五月 05, 2019 6:48:05 下午 logger.LoggerTest main
資訊: 3==========

2:設定Level = Level.SEVERE(有效日誌級別SEVERE)

public static void main(String[] args) {
        Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales");
        fruitSaleslogger.setLevel(Level.SEVERE);
        
        
        fruitSaleslogger.severe("1=========="); // 等於 SEVERE
        fruitSaleslogger.warning("2=========="); // 低於 SEVERE
        fruitSaleslogger.info("3=========="); // 低於 SEVERE
        fruitSaleslogger.config("4=========="); // 低於 SEVERE
    }

控制檯結果:

五月 05, 2019 6:50:57 下午 logger.LoggerTest main
嚴重: 1==========

經過上面的測試我們知道,當呼叫一些輸出方法時會進行日誌級別的判斷,對於低於有效日誌級別的輸出則直接忽略,那對於高於或等於有效日誌級別的呢?再看API裡的一句話,分析一下:

2:”通過此初始(簡單)測試後,Logger將分配一個LogRecord來描述日誌記錄訊息。接著呼叫Filter(如果存在)進行更詳細的檢查,以確定是否應該釋出該記錄。如果檢查通過,則將LogRecord釋出到其輸出Handler。在預設情況下,logger也將LogRecord沿樹遞推釋出到其父Handler【摘抄API】

LogRecord是幹啥的?Handler是幹啥的?

LogRecord物件用於在日誌框架和單個日誌Handler之間傳遞日誌請求。

Handler物件從Logger中獲取日誌資訊,並將這些資訊匯出。例如,它可將這些資訊寫入控制檯或檔案中,也可以將這些資訊傳送到網路日誌服務中,或將其轉發到作業系統日誌中。

摘自【知行流浪

對於上面一張我自己根據理解畫的圖,參考原始碼,不管logger呼叫的是哪種輸出方法最終都會呼叫doLog(LogRecord lr)最後轉到log(LogRecord lr),如果說LogRecord是一個封裝了logger的請求也能讓人理解,在log(LogRecord lr)方法裡,對LogRecord進行校驗,校驗其是否應該被髮布,如果校驗通過,則通知在這個logger物件上的所有觀察者(Handler物件),Handler物件去呼叫publish(LogRecord lr)方法,釋出這條日誌。

原始碼如下:

public void log(LogRecord record) {
        if (!isLoggable(record.getLevel())) {
            return;
        }
        Filter theFilter = filter;
        if (theFilter != null && !theFilter.isLoggable(record)) {
            return;
        }

        // Post the LogRecord to all our Handlers, and then to
        // our parents' handlers, all the way up the tree.

        Logger logger = this;
        while (logger != null) {
            final Handler[] loggerHandlers = isSystemLogger
                ? logger.accessCheckedHandlers()
                : logger.getHandlers();

            for (Handler handler : loggerHandlers) {
                handler.publish(record);
            }

            final boolean useParentHdls = isSystemLogger
                ? logger.useParentHandlers
                : logger.getUseParentHandlers();

            if (!useParentHdls) {
                break;
            }

            logger = isSystemLogger ? logger.parent : logger.getParent();
        }
    }

Handler物件

Handler物件才是最終用來發布日誌的,這是一個抽象類,其有五個子類分別是:ConsoleHandler(釋出日誌到控制檯),FileHandler(釋出日誌到檔案),MemoryHandler(釋出日誌到記憶體),SocketHandler(釋出日誌到網路),StreamHandler(釋出日誌到流)

測試:

package logger;

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

public class LoggerTest {
    public static void main(String[] args) throws Exception {
        Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales");
        // 設定有效日誌級別
        fruitSaleslogger.setLevel(Level.ALL);
        
        FileHandler ch = new FileHandler("C:\\Users\\admin\\Desktop\\ServletBase\\LoggerTest\\log\\logger.txt");

        // 設定日誌級別,指定該 Handler 所記錄的資訊級別。
        ch.setLevel(Level.SEVERE); // 輸出到檔案的日誌級別
        fruitSaleslogger.addHandler(ch);
        
        fruitSaleslogger.severe("1==========");
        fruitSaleslogger.warning("2==========");
        fruitSaleslogger.info("3==========");
        fruitSaleslogger.config("4==========");
    }
}

控制檯:

五月 05, 2019 8:56:17 下午 logger.LoggerTest main
嚴重: 1==========
五月 05, 2019 8:56:17 下午 logger.LoggerTest main
警告: 2==========
五月 05, 2019 8:56:18 下午 logger.LoggerTest main
資訊: 3==========

logger.txt檔案

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2019-05-05T20:56:17</date>
  <millis>1557060977954</millis>
  <sequence>0</sequence>
  <logger>com.sales.fruitsales</logger>
  <level>SEVERE</level>
  <class>logger.LoggerTest</class>
  <method>main</method>
  <thread>1</thread>
  <message>1==========</message>
</record>
</log>

如果你設定的Handler是ConsoleHandler則控制檯可能會有部分輸出重複,為了方便比對Level,我換成了FileHandler。可以看出控制檯輸出和Handler裡的輸出互不影響。

Handler中可以設定一些Fileter,不在綴訴。