簡介
- 本篇將介紹如何使用
JDK原生自帶的日志框架JUL,
JUL日志框架
JUL全稱Java Util Logging,是Java原生的日志框架,使用時不需要另外引入第三方類別庫,相對于其他日志框架來說其特點是使用方便,能夠在小型應用中靈活應用,JUL日志框架使用的頻率并不高,但一旦需要解除此類的代碼,仍然要求開發人員能夠迅速看懂代碼,并理解,
框架結構
- 結構圖:

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對日志內容進行排版,之后再輸出到指定的位置,
- 用戶使用
入門案例
- 示例代碼:
@Test
public void testQuick() {
// 1.創建JUL Logger物件無法傳入class物件,迂回戰術可以獲取該類之后獲取名字
Logger logger = Logger.getLogger(JulTest.class.getName());
// 2.輸出日志的兩種方式,其中日志級別共有7種,還有2種特殊級別
logger.info("Hello, here is Java Util Logging");
logger.log(Level.INFO, "You can also use this way to output log.");
// 3.盡量不采用拼接字串的形式,采用占位符的形式,占位符中需要填寫索引編號
String name = "dylan";
int age = 12;
logger.log(Level.INFO, "[USER]:{0} [AGE]:{1}", new Object[]{name, age});
}
日志級別
JUL內置工7種日志級別,另外有2中特殊級別:java.util.logging.Level中定義了7種日志級別:SEVERE(最高等級)WARNINGINFO(默認等級)CONFIGFINEFINERFINEST(最低等級)
- 還有
2種特殊日志級別,用于開關日志:OFF:可用于關閉日志記錄;ALL:啟用所有訊息的日志記錄,
- 示例代碼:
- 默認的日志級別是由
RootLogger決定的,所有的Logger物件默認都會繼承并使用RootLogger所提供的控制臺輸出處理器物件ConsoleHandler; - 同時,
RootLogger的默認日志輸出等級為INFO,則所有未經配置的Logger默認也是使用該日志級別,
- 默認的日志級別是由
/**
* JUL默認的日志級別是Info,所有級別高于等于Info的日志都會被輸出控制臺,
*/
@Test
public void testLogLevel() {
// 1.獲取日志記錄器物件,其中getLogger的引數name是此日志物件Logger的名稱,可以由logger.getName()取出
final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest");
// 2.使用默認的ConsoleHandler輸出各級別的日志,默認情況下只有Server、Warning和Info會輸出到控制臺
logger.severe("Level Severe.");
logger.warning("Level Warning.");
logger.info("Level Info.");
logger.config("Level Config.");
logger.fine("Level Fine.");
logger.finer("Level Finer.");
logger.finest("Level Finest");
}
- 運行輸出:

- 盡管代碼中定義了輸出
INFO等級以下的日志,但實際控制臺中并沒有相關的日志資訊,這是因為此時創建的Logger物件繼承并使用了RootLogger中的日志等級和處理器物件, - 考慮以下代碼:
@Test
public void testFindDefault() {
final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest");
System.out.println("Default Logger's Level: " + logger.getLevel());
System.out.println("Default Logger's Handlers' Quantity: " + logger.getHandlers().length);
}
- 其中采用更為直觀的方式列印當前
Logger物件的日志等級,和與其關聯的處理器物件數量,運行輸出,得到:

- 這并不意外,默認情況下由于
Logger繼承并使用RootLogger中的日志等級與處理器物件,因此對于它自身來說,并不擁有任何的日志等級資訊與處理器物件, - 考慮以下代碼:
@Test
public void testParentLogger() {
final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest");
final Logger loggerParent = logger.getParent();
System.out.println("Logger's Default Parent Logger is: " + loggerParent.getClass().getSimpleName());
System.out.println("Parent Logger's Level: " + loggerParent.getLevel());
System.out.println("Parent Logger's Handlers' Quantity: " + loggerParent.getHandlers().length);
for (Handler handler : loggerParent.getHandlers()) {
System.out.println("Default " + handler.getClass().getSimpleName() + "'s Level: " + handler.getLevel());
}
}
- 同樣采用更直觀的方式列印當前
Logger的父日志相關資訊,得到以下輸出:

- 至此,不難推斷出
Logger的日志輸出等級取決于RootLogger, - 還有一個定論,即
Logger和Handler的日志等級是相互牽制的,其中等級較高一方的配置生效, - 通過更改
RootLogger中的日志級別及其ConsoleHandler的日志級別,撰寫以下代碼:
@Test
public void testLevel() {
// 0.準備作業
final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest");
final Logger parent = logger.getParent();
// 1.RootLogger日志等級高于ConsoleHandler日志等級
parent.setLevel(Level.WARNING);
parent.getHandlers()[0].setLevel(Level.CONFIG);
logger.severe("[SEVERE ]Something.");
logger.warning("[WARNING]Something.");
logger.info("[INFO ]Something.");
logger.config("[CONFIG ]Something.");
System.out.println("=============================================");
// 2.RootLogger日志等級小于ConsoleHandler日志等級
parent.setLevel(Level.FINER);
logger.severe("[SEVERE ]Something.");
logger.warning("[WARNING]Something.");
logger.info("[INFO ]Something.");
logger.config("[CONFIG ]Something.");
logger.fine("[FINE ]Something.");
logger.finer("[FINER ]Something.");
}
- 運行輸出:

- 程式的輸出與此前的定論一致,
自定義配置
- 通常會在單獨的組態檔中去配置
Logger的日志等級和處理器型別等,但作為入門,需要了解如何在Java代碼中,通過更改Logger日志級別和配置自定義ConsoleHandler的方式,去影響日志輸出, - 如果不希望
Logger物件使用RootLogger中的日志級別進行輸出,則需要對Logger進行以下配置:- 重新設定
Logger的日志輸出等級; - 重新配置
Logger的處理器Handler型別,并不再使用RootLogger中提供的默認處理器,
- 重新設定
- 測驗類
testUserDefined原始碼如下:
@Test
public void testUserDefined() {
// 1.獲取日志記錄器物件
Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest");
// 2.配置Logger使其不再繼承使用RootLogger中的所有Handler
logger.setUseParentHandlers(false);
// 3.自定義ConsoleHandler物件,并配置該處理器的日志等級
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
// 4.為Logger添加自定義的ConsoleHandler
logger.addHandler(consoleHandler);
// 5.由于Logger默認會使用RootLogger的日志等級,如果希望輸出Level.ALL的日志,同時需要設定Logger的日志等級也為Level.ALL
// *.否則,Logger將會使用RootLogger的默認日志等級INFO,最終日志只會輸出等級高于或等于INFO的內容
logger.setLevel(Level.ALL);
// 6.分別輸出各等級的日志
logger.severe("[SEVERE ]Something.");
logger.warning("[WARNING]Something.");
logger.info("[INFO ]Something.");
logger.config("[CONFIG ]Something.");
logger.fine("[FINE ]Something.");
logger.finer("[FINER ]Something.");
logger.finest("[FINEST ]Something.");
}
- 運行輸出:

- 而開發中較為經常的使用方式,是通過
logging.properties檔案的方式進行配置:- 程式中需要撰寫代碼去顯示加載
logging.properties檔案以啟用自定義的配置; - 組態檔中,
Handler是單獨進行配置的,開發人員可以單獨定義控制臺輸出日志的處理器物件ConsoleHandler或檔案輸出日志的處理器物件FileHandler等; - 具備相關自定義
Handler后,需要將Logger與Handler進行關聯,組態檔支持RootLogger或指定名稱的Logger與自定義Handler進行關聯; - 無論任何時候,都需要明確日志最終的輸出等級,是同時由
Logger與其相關聯的Handler所決定的,
- 程式中需要撰寫代碼去顯示加載
# RootLogger的日志級別(默認INFO),所有的Handler都受限于此日志級別,Handler的日志級別可以比RootLogger的日志級別高
.level=ALL
# RootLogger默認的處理器,可以配置多個,所有非手動解除父日志的子日志都將使用這些處理器
handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
# ConsoleHandler控制臺輸出處理器配置
# 指定ConsoleHandler默認日志級別
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.encoding=UTF-8
# FileHandler檔案輸出處理器配置
# 指定FileHandler默認日志級別
java.util.logging.FileHandler.level=INFO
# 日志檔案輸出路徑
java.util.logging.FileHandler.pattern=/dylan%u.log
# 單個日志檔案大小,單位是bit,1024bit即為1kb
java.util.logging.FileHandler.limit=1024*1024*10
# 日志檔案數量,如果數量為2,則會生成dylan.log.0檔案和dylan.log.1檔案,總容量為: (limit * count)bit
java.util.logging.FileHandler.count=1
# FileHandler持有的最大并發鎖數
java.util.logging.FileHandler.maxLocks=100
# 指定要使用的Formatter類的名稱,FileHandler默認使用的是XMLFormatter
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
# 涉及中文日志就最好加上編碼集
java.util.logging.FileHandler.encoding=UTF-8
# 是否以追加方式添加日志內容
java.util.logging.FileHandler.append=true
# SimpleFormatter的輸出格式配置
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
# 自定義日志級別,其中”cn.hanna“指的是Logger.getLogger(String name)中的入參name!!!
cn.hanna.handlers=java.util.logging.ConsoleHandler
cn.hanna.level=INFO
# 如果此時不關閉名為cn.hanna的Logger的父日志處理器,則在控制臺會同時出現父日志處理器和自定義的處理器,訊息將重復輸出
cn.hanna.useParentHandlers=false
- 測驗類
test()的加載程序如下:- 專案是通過
Maven構建的,默認使用getResourceAsStream(String fileName)將讀取resource目錄下的組態檔; - 其中
LogManager物件是用于全域配置日志的管理物件,它是單例的,使用它來加載應用組態檔; - 組態檔使用
readConfiguration(InputStream is)加載應用后,RootLogger的日志級別和與之關聯的Handler及其日志級別就已經配置完畢,與logging.properties中一致; - 同時特殊名稱
cn.hanna的Logger會遵循組態檔中設定的日志等級INFO,其關閉依賴RootLogger,同時僅僅將日志輸出到控制臺中,
- 專案是通過
@Test
public void testUserDefined() throws IOException {
// 1.讀取組態檔
final InputStream is =
Jul2Test.class.getClassLoader().getResourceAsStream("logging.properties");
// 2.獲取LogManager,LogManager是單例物件,并加載應用組態檔logging.properties
final LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(is);
// 4.正常輸出日志
final Logger loggerNormal = Logger.getLogger(this.getClass().getName());
loggerNormal.severe("[SEVERE ]Something.");
loggerNormal.warning("[WARNING]Something.");
loggerNormal.info("[INFO ]Something.");
loggerNormal.config("[CONFIG ]Something.");
loggerNormal.fine("[FINE ]Something.");
loggerNormal.finer("[FINER ]Something.");
loggerNormal.finest("[FINEST ]Something.");
System.out.println("=============================================");
// 5.指定日志物件的名稱,組態檔中對cn.hanna名稱的Logger進行了特殊配置
final Logger loggerSpecial = Logger.getLogger("cn.hanna");
loggerSpecial.severe("[SEVERE ]Something.");
loggerSpecial.warning("[WARNING]Something.");
loggerSpecial.info("[INFO ]Something.");
loggerSpecial.config("[CONFIG ]Something.");
loggerSpecial.fine("[FINE ]Something.");
loggerSpecial.finer("[FINER ]Something.");
loggerSpecial.finest("[FINEST ]Something.");
}
- 運行測驗,控制臺輸出:

- 日志檔案
dylan0.log輸出:

占位符相關
- 留意到配置內
formatter中的包含了相關占位符,如果希望掌握這一部分的含義,需要首先了解String.format()的使用, - 考慮以下代碼:
@Test
public void test() {
String name = "Mike";
double score = 89;
Calendar calendar = Calendar.getInstance();
calendar.set(1992, Calendar.JANUARY, 3);
final String formatA = String.format("[%1$-5s] %2$tF score: (%3$-8.2f)", name, calendar, score);
final String formatB = String.format("[%1$-5s] %2$tY-%2$tm-%2$td score: (%3$8.2f)", name, calendar, score);
System.out.println(formatA);
System.out.println(formatB);
}
- 運行輸出:

- 可以看到,
String.format可以將資料按照指定的格式轉換為String型別的資料,其中需要遵循以下規則:%[argument_index$][flags][width][.precision]conversion
- 引數說明:
| 引數 | 描述 |
|---|---|
argument_index$ |
格式化引數在String.format中的索引位置,從1開始計算索引 |
flags |
關于此格式化資料的額外格式設定,如左對齊-、是否顯示正負數符號+ |
width |
此格式資料所占用的寬度,當寬度大于引數的實際寬度時,會自動將格式化資料右對齊 |
.precision |
需要格式化的是一個浮點數時,可以自定義該浮點數格式化后所需保留的小數點位數 |
conversion |
表示需要格式的資料型別是什么 |
- 關于
String.format的詳細內容,可以參考以下網址,其中提供了大量的關于各種型別的各種格式化占位符資訊:- https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html
- 了解
String.format后,需要知道其實日志記錄的格式化,內部使用的也是String.format,形式如下:String.format(format, date, source, logger, level, message, thrown);
- 引數說明:
| 引數 | 描述 |
|---|---|
format |
SimpleFormatter.format中使用的格式,也是組態檔logging.properties中定義的格式 |
date |
日志的輸出日期 |
source |
日志的呼叫者,如果不存在則會輸出日志物件的名稱 |
logger |
日志物件的名稱 |
level |
日志等級 |
message |
日志資訊 |
thrown |
當有例外時,會在日志資訊中包含例外資訊,如果不存在則不輸出 |
- 關于
SimpleFormatter的詳細內容,可以參考以下網址,其中包含了更詳細的說明與示例:- https://docs.oracle.com/javase/7/docs/api/java/util/logging/SimpleFormatter.html
- 初步了解相關原理后,重新審視組態檔中的
%4$s: %5$s [%1$tc]%n:%4$s:索引位4是level,表示日志等級,資料型別是String,使用的conversion為s;%5$s:索引位5是message,表示日志資訊,資料型別是String,使用的conversion為s;%1$tc:索引位1是date,表示日志輸出日期,資料型別是Date,使用的conversion為tc;%n:換行符,
- 因此,當運行代碼
logger.severe("[SEVERE ]Something.");,將得到日志記錄,對比自定義格式:

- 格式與日志輸出格式一致,
濾器配置
- 關于
Filter過濾器,了解即可, - 代碼中需要使用
Logger物件中的setFilter方法,配置一個Filter物件,其原始碼如下:
@FunctionalInterface
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);
}
- 其中真正控制日志是否運行記錄的物件是
LogRecord,使用其中的getMessage()可以獲取到日志資訊, - 當方法
isLoggable回傳false時,不記錄日志;回傳true則記錄日志, - 測驗代碼如下:
- 其中使用
lambda運算式,回傳的結果為!record.getMessage().contains("What"),即如果日志記錄包含了關鍵字What,則回傳false,表示過濾該條日志資訊,不進行記錄操作,
- 其中使用
@Test
public void test() throws IOException {
final InputStream is =
Jul3Test.class.getClassLoader().getResourceAsStream("logging.properties");
final LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(is);
final Logger logger = Logger.getLogger(this.getClass().getName());
logger.severe("[SEVERE ]Something What.");
logger.warning("[WARNING]Something.");
logger.info("[INFO ]Something.");
logger.config("[CONFIG ]Something.");
logger.fine("[FINE ]Something What.");
logger.finer("[FINER ]Something.");
logger.finest("[FINEST ]Something What.");
System.out.println("=============================================");
// *.配置過濾器Filter
logger.setFilter((x) -> !x.getMessage().contains("What"));
logger.severe("[SEVERE ]Something What.");
logger.warning("[WARNING]Something.");
logger.info("[INFO ]Something.");
logger.config("[CONFIG ]Something.");
logger.fine("[FINE ]Something What.");
logger.finer("[FINER ]Something.");
logger.finest("[FINEST ]Something What.");
}
- 運行輸出:

Filter生效并成功將包含What的日志記錄過濾,
完整的組態檔示例
- 最后貼一個
JUL可用的組態檔示例,其中RootLogger僅使用了ConsoleHandler輸出日志:
.level=ALL
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.encoding=UTF-8
java.util.logging.FileHandler.level=INFO
java.util.logging.FileHandler.pattern=/sample%u.log
java.util.logging.FileHandler.limit=1024*1024*10
java.util.logging.FileHandler.count=1
java.util.logging.FileHandler.maxLocks=100
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.encoding=UTF-8
java.util.logging.FileHandler.append=true
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
cn.xyz.handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
cn.xyz.level=INFO
cn.xyz.useParentHandlers=false
- 關于其他的
Handler不作演示了,因為實際遇到的情況很少,有需要的時候查閱相關官方資料即可,
總結
- 關于
JUL的介紹到此為止,總體來說是一款很輕量的框架; - 在專案體量很小時,可以使用
JUL記錄相關日志,免去使用其他日志門面或框架的繁瑣操作,如依賴匯入等,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/242673.html
標籤:Java
下一篇:如何完成一次快速的查詢
