java自帶的Logger日誌系統
在練習Servlet基礎的時候,我想著是要寫一個輸出請求連結的日誌攔截器,但是真到寫doFilter這塊時,自己又一臉懵逼,憑藉著感覺寫,只知道有Logger這個物件,怎麼使用的不會,百度一下簡單抄了兩行程式碼:
Logger logger = Logger.getLogger("filter"); logger.log(Level.ALL,攔截的地址:" + req.getRequestURL());
雖然也能正常工作,也有輸出,但是自己整個過程一臉懵逼,很難受。下定決心打算研究研究這玩意。
自己英文不咋好,還好有中文版的JDK6可以閱讀,找到java.util.logging下面的Logger類,閱讀基本介紹。
Logger物件是用來記錄特定系統或應用程式元件的日誌訊息。Logger具有層次結構,層次結構使用圓點分割表示,比如說FruitSales.AppleSales,就代表著如下圖:
每一個Logger物件都有一個唯一的名稱,這個名稱可以是任意的字串,但是最好是帶有一定的層次結構,比如說基於包名或類名。
Logger fruitsaleslogger = Logger.getLogger("com.sales.fruitsales");
Logger applesaleslogger = Logger.getLogger("com.sales.fruitsales.applesales");
也可以建立"匿名"的Logger,但是使用匿名的Logger物件,它的名稱不會儲存在Logger名稱空間中。
Logger名稱空間就像是一個Map集合,儲存著所有的logger物件,名稱空間的key就是logger物件名,而value就是logger物件。上面說了,Logger具有一定的層次結構,每一個Logger物件都會跟蹤一個"父"Logger,也就是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通過呼叫getLogger(String name)工廠方法來獲得Logger物件,在該方法的引數name若在Logger的名稱空間裡不存在,則建立一個新的logger物件,否則返回該logger物件。
Logger appLogger = Logger.getLogger("appLogger");
每個Logger物件都有一個與其相關的“Level”,標誌著其輸出的級別,如果“Level”被設定為null(預設為null),那麼它的有效級別繼承自父logger物件,這可以通過其父logger一直沿樹向上遞迴得到(當心父logger會突然改變其級別,子類為null的也都會隨著改變)。
null:預設為null時,級別低於INFO,但在CONFIG之上。
話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之間傳遞日誌請求。
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物件才是最終用來發布日誌的,這是一個抽象類,其有五個子類分別是: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==========
<?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裡的輸出互不影響。