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,不在綴訴。

 


免責聲明!

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



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