日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java util logging_Java 日志系列篇一 原生 Java.util.logging

發布時間:2025/4/5 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java util logging_Java 日志系列篇一 原生 Java.util.logging 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文網大多網絡整理所得,出處太多,不一一列舉

簡介

Java 中的 Logging API 讓 Java 應用可以記錄不同級別的信息,它在debug過程中非常有用,如果系統因為各種各樣的原因而崩潰,崩潰原因可以在日志中清晰地追溯,下面讓我們來看看 Java 原生的 Logging 功能。

從1.4.2開始,Java 通過 Java.util.logging 包為應用程序提供了記錄消息的可能,在 API 中的核心類為 Logger 類。理解在記錄消息中的日志的不同級別是非常重要的。Java 為此定時了8個級別,它們是分別SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST 以及 ALL. 它們按照優先級降序排列,在應用運行的任何時間點,日志級別可以被更改。

通常來說,當為 Logger 指定了一個 Level, 該 Logger 會包含當前指定級別以及更高級別的日志。舉例而言,如果 Level 被設置成了 WARNING, 所有的 warning 消息以及 SERVER 消息會被記錄。應用可以用下列方法記錄日志:Logger.warning(), Logger.info(), Logger.config() ...

工作原理和日志處理流程

幾個重要類的說明

Logger 對外發布的日志記錄器,應用系統可以通過該對象完成日志記錄的功能

Level 日志的記錄級別

LoggingMXBean 接口對象,對外發布的日志管理器

LogRecord 日志信息描述對象

LoggerManager 日志管理器

Filter 日志過濾器,接口對象,在日志被 Handler 處理之前,起過濾作用

Handler 日志處理器,接口對象,決定日志的輸出方式

Formatter 日志格式化轉換器,接口對象,決定日志的輸出格式

工作原理

首先通過LoggerManager進行日志框架的初始化,生成Logger的根節點RootLogger.?這里需要注意的是LoggerManager的初始化工作,并沒有將構建配置文件中所有的日志對象,而僅僅是構建了根節點,這種方式就是我們多例模式中經常用到的懶加載,對象只有在真正被時候的時候,再進行構建。

通過Logger.getLogger(String?name)?獲取一個已有的Logger對象或者是新建一個Logger對象。Logger,日志記錄器,這就是在應用程序中需要調用的對象了,通過Logger對象的一系列log方法,

Logger的大致處理流程

收到應用程序的記錄請求,將參數中的日志信息和運行時的信息構建出LogRecord對象,而后通過Logger對象本身設置的記錄級別和調用者傳遞進來的日志級別,如果傳遞進來的日志級別低于Logger對象本身設置的記錄級別(從語義上的理解,而實際上語義級別越高的級別其內部用數字表示的標志的數值越小),那么Logger對象將直接返回,因為他認為這條日志信息,在當前運行環境中,沒有必要記錄。

而滿足以上條件的日志信息,將會通過Logger對象的filter元素的過濾校驗,filter是動態的,在運行時是可以隨意設置的,如果有filter對象,那么將調用filter對象,對日志對象LogRecord進行校驗,只有校驗通過的LogRecord對象,才會繼續往下執行。

通過filter校驗后,Logger對象將依次調用其配置的處理器,通過處理器來真正實現日志的記錄功能,一個Logger對象可以配置多個處理器handler,所以一條日志記錄可以被多個處理器處理,同時Logger對象的實現是樹形結構,如果Logger對象設置其可以繼承其父節點的處理器(默認),一條日志記錄還會被其父節點的Logger對象處理。??而handler的處理方式就會是形形色色了,但是歸根節點,會有以下幾個大的步驟:

1.?級別的判定和比較,決定某條具體的日志記錄是否應該繼續處理

2.?將日志記錄做格式化處理,以達到輸出的日志在格式上統一,美觀,可讀性高。?3.?資源的釋放,不管是以何種方式記錄日志,總是會消耗一些方面的資源,所以

會涉及到資源的釋放問題。比如以文件方式記錄的日志的,在一定的時候需要做文件關閉操作,以報文方式發送日志的,在和遠程通話的過程中,也需要涉及到網絡IO的關閉操作,或者是存儲在數據庫等等,資源釋放在程序開發過程中,是個不變的主題。

從一個示例講起

public class TestLogger {

public static void main(String[] args) {

Logger log = Logger.getLogger("lavasoft");

log.info("aaa");

}

}

console output:

>>> aaa

以上簡單的代碼背后發生那些事

LoggerManager 將會返回一個新的或者已經存在的同名的 Logger , 首先會查找是否有同名 Logger 被 namedLoggers 維護有則返回, 但是在我們這個示例中大多是重新生成一個 Logger,首先 LoggerManager 會讀取系統配置,設定一個默認的的 INFO 級別的 Logger, 然后也許跟其他線程搶到一個 Logger 后返回

tips:

默認的Java日志框架將其配置存儲到一個名為 logging.properties 的文件中。

在這個文件中,每行是一個配置項,配置項使用點標記(dot notation)的形式。

Java在其安裝目錄的lib文件夾下面安裝了一個全局配置文件,但在啟動一個Java程序時,

你可以通過指定 java.util.logging.config.file 屬性的方式來使用一個單獨的日志配置文件,

同樣也可以在個人項目中創建和存儲 logging.properties 文件。

Logger 中召喚 LoggerManager 片段

---------------------------

public static Logger getLogger(String name) {

LogManager manager = LogManager.getLogManager();

return manager.demandLogger(name);

}

LoggerManager 中 產生 Logger 的片段

-----------------------------

Logger demandLogger(String name) {

Logger result = getLogger(name);

if (result == null) {

Logger newLogger = new Logger(name, null);

do {

if (addLogger(newLogger)) {

return newLogger;

}

result = getLogger(name);

} while (result == null);

}

return result;

}

LoggerManager 中維護了一個有繼承關系的含有弱引用的 LoggerWeakRef

-------------------------------

private Hashtable namedLoggers = new Hashtable<>();

LoggerWeakRef 類結構

-----------------

final class LoggerWeakRef extends WeakReference {

private String name; // for namedLoggers cleanup

private LogNode node; // for loggerRef cleanup

private WeakReference parentRef; // for kids cleanup

以上兩者維護了JVM中弱引用的 Loggers 父子結構

log.info()

Logger 中的 info(String msg) 方法

-----------------------------

public void info(String msg) {

if (Level.INFO.intValue() < levelValue) {

return;

}

log(Level.INFO, msg);

}

上面說過默認 LoggerManager 產生的 Logger 日志級別默認為 INFO ,所以這里默認的

levelValue 為 Level.INFO.intValue()

如果這里 Level.INFO.intValue() 低于 levelValue 的 , 將 do nothing

調用 log(Level level, String msg) 方法

----------------------------------

public void log(Level level, String msg) {

if (level.intValue() < levelValue || levelValue == offValue) {

return;

}

LogRecord lr = new LogRecord(level, msg);

doLog(lr);

}

上面的 log.info 方法只是 log(Level level, String msg) 方法簡單封裝,在這里日志級別

為 Level.OFF.intValue() 也 do nothing 了,否則創建真正的 LogRecord 對象

調用 doLog(LogRecord lr) 方法

-------------------------

private void doLog(LogRecord lr) {

lr.setLoggerName(name);

String ebname = getEffectiveResourceBundleName();

if (ebname != null) {

lr.setResourceBundleName(ebname);

lr.setResourceBundle(findResourceBundle(ebname));

}

log(lr);

}

getEffectiveResourceBundleName() 將一直上溯查找有效的 resourceBundleName , 有可能返回 null

調用 log(LogRecord lr) 方法

-----------------------

public void log(LogRecord record) {

if (record.getLevel().intValue() < levelValue || levelValue == offValue) {

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) {

for (Handler handler : logger.getHandlers()) {

handler.publish(record);

}

if (!logger.getUseParentHandlers()) {

break;

}

logger = logger.getParent();

}

}

在這里我們可以看到了 Filter 與 Handler 的出現,我們可以使用 setFilter(Filter newFilter)

與 addHandler(Handler handler) 來為 Logger 添加 Filter 與 Handler

這里我們可以看出在 while 循環中會先對當前所有 handler 輸出,在上溯所有父 Logger 所有 Handler

輸出,至此兩句代碼解析結束。

話說 Filter

作為一個接口, Filter:為所記錄的日志提供日志級別控制以外的細粒度控制。

public interface Filter {

/**

* Check if a given log record should be published.

* @param record a LogRecord

* @return true if the log record should be published.

*/

public boolean isLoggable(LogRecord record);

}

我們可以實現一個 Filter 接口的的對象來使用,下面是示例代碼

public class MyFilter implements Filter {

public boolean isLoggable(LogRecord record) {

// TODO: 在這里我們可以添加自己的一些邏輯進去

return false;

// 返回 false 則不被記錄日志, true 則被記錄日志

}

}

然后我們為 Logger 對象設定 Filter 對象

Filter filter = new MyFilter();

logger1.setFilter(filter);

或者我們也可以為 Handler 對象設定 Filter 對象

Filter filter = new MyFilter();

ConsoleHandler consoleHandler = new ConsoleHandler();

consoleHandler.setLevel(Level.ALL);

consoleHandler.setFilter(filter);

話說 Handler

先上一張 java.util.logging 包中有關 Handler 的類圖

Handler負責從Logger中取出日志消息并將消息發送出去,比如發送到控制臺、文件、網絡上的其他日志服務或操作系統日志等。

Handler也具有級別概念,用于判斷當前Logger中的消息是否應該被發送出去,可以使用定義好的各種日志級別(如Level.OFF表示關閉等)。

除了級別概念,一個Handler還可以具有自己的過濾器(Filter)、格式化器(Formatter)、錯誤管理器(ErrorManager)以及編碼字符集等,這些屬性借助LogManager中的配置信息進行設置。

Handler是一個抽象類,需要根據實際情況創建真正使用的具體Handler(如ConsoleHandler、FileHandler等),實現各自的publish、flush以及close等方法。

對幾種具體實現 Handler 類的類做簡單說明

MemoryHandler,將當前日志信息寫入內存緩沖區中同時丟棄緩存中以前的內容。將內存緩沖區中的信息轉發至另一個Handler

StreamHandler所有基于I/O流的Handler的基類,將日志信息發送至給定的java.io.OutputStream中

ConsoleHandler,將消息發送至System.err(而非System.out),默認配置與其父類StreamHandler相同。

FileHandler,將消息發送至單個一般文件或一個可回滾的文件集合。可回滾文件集中的文件依據文件大小進行回滾,久文件名稱通過當前文件名附加編號0、1、2等方式依次進行標示。默認情況下日志信息都存放在I/O緩沖中,但如果一條完整的日志信息會觸發清空緩沖的動作。與其父類StramHandler不同的是,FileHandler的默認格式器是java.util.logging.XMLFormatter:

SocketHandler,負責將日志信息發送至網絡,默認情況下也采用java.util.logging.XMLFormatter格式。

關于 MemoryHandler

MemoryHandler 使用了典型的“注冊 - 通知”的觀察者模式。MemoryHandler 先注冊到對自己感興趣的 Logger 中(logger.addHandler(handler)),在這些 Logger 調用發布日志的 API:log()、logp()、logrb() 等,遍歷這些 Logger 下綁定的所有 Handlers 時,通知觸發自身 publish(LogRecord)方法的調用,將日志寫入 buffer,當轉儲到下一個日志發布平臺的條件成立,轉儲日志并清空 buffer。

這里的 buffer 是 MemoryHandler 自身維護一個可自定義大小的循環緩沖隊列,來保存所有運行時觸發的 Exception 日志條目。同時在構造函數中要求指定一個 Target Handler,用于承接輸出;在滿足特定 flush buffer 的條件下,如日志條目等級高于 MemoryHandler 設定的 push level 等級(實例中定義為 SEVERE)等,將日志移交至下一步輸出平臺。從而形成如下日志轉儲輸出鏈:

MemoryHandler 使用方式

以上是記錄產品 Exception 錯誤日志,以及如何轉儲的 MemoryHandler 處理的內部細節;接下來給出 MemoryHandler 的一些使用方式。

直接使用 java.util.logging 中的 MemoryHandler

// 在 buffer 中維護 5 條日志信息

// 僅記錄 Level 大于等于 Warning 的日志條目并

// 刷新 buffer 中的日志條目到 fileHandler 中處理

int bufferSize = 5;

f = new FileHandler("testMemoryHandler.log");

m = new MemoryHandler(f, bufferSize, Level.WARNING);

myLogger = Logger.getLogger("com.ibm.test");

myLogger.addHandler(m);

myLogger.log(Level.WARNING, “this is a WARNING log”);

自定義(反射)

思考自定義 MyHandler 繼承自 MemoryHandler 的場景,由于無法直接使用作為父類私有屬性的 size、buffer 及 buffer 中的 cursor,如果在 MyHandler 中有獲取和改變這些屬性的需求,一個途徑是使用反射。清單 5 展示了使用反射讀取用戶配置并設置私有屬性。

int m_size;

String sizeString = manager.getProperty(loggerName + ".size");

if (null != sizeString) {

try {

m_size = Integer.parseInt(sizeString);

if (m_size <= 0) {

m_size = BUFFER_SIZE; // default 1000

}

// 通過 java 反射機制獲取私有屬性

Field f;

f = getClass().getSuperclass().getDeclaredField("size");

f.setAccessible(true);

f.setInt(this, m_size);

f = getClass().getSuperclass().getDeclaredField("buffer");

f.setAccessible(true);

f.set(this, new LogRecord[m_size]);

} catch (Exception e) {

}

}

自定義(重寫)

直接使用反射方便快捷,適用于對父類私有屬性無頻繁訪問的場景。思考這樣一種場景,默認環形隊列無法滿足我們存儲需求,此時不妨令自定義的 MyMemoryHandler 直接繼承 Handler,直接對存儲結構進行操作,可以通過清單 6 實現。

public class MyMemoryHandler extends Handler{

// 默認存儲 LogRecord 的緩沖區容量

private static final int DEFAULT_SIZE = 1000;

// 設置緩沖區大小

private int size = DEFAULT_SIZE;

// 設置緩沖區

private LogRecord[] buffer;

// 參考 java.util.logging.MemoryHandler 實現其它部分

...

}

logging.properties 文件

默認的 logging.properties 存放在 jre/lib/logging.properties,截取有效的配置項

handlers= java.util.logging.ConsoleHandler

.level= INFO

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

java.util.logging.ConsoleHandler.level = INFO

java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

com.xyz.foo.level = SEVERE

總結

以上是生活随笔為你收集整理的java util logging_Java 日志系列篇一 原生 Java.util.logging的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。