Tips
做一個終身學習的人。
在這章中,主要介紹以下內容:
- 新的平台日志(logging)API
- JVM日志的命令行選項
JDK 9已經對平台類(JDK類)和JVM組件的日志系統進行了大整。 有一個新的API可以指定所選擇的日志框架作為從平台類記錄消息的日志后端。 還有一個新的命令行選項,可以從所有JVM組件訪問消息。 在本章中,詳細介紹兩個記錄工具。
一. 平台日志API
Java SE 9添加了一個平台日志API,可用於指定可由Java平台類(JDK中的類)記錄消息的記錄器(Logger),例如Log4j,SLF4J或自定義記錄器。 有關這個API的幾點要注意。 該API旨在由JDK中的類使用,而不是應用程序類。 因此,不應該使用此API來記錄應用程序消息。 需要使用Log4j等日志框架來記錄應用程序消息。 API不允許以編程方式配置記錄器。 API由以下內容組成:
- 一個服務接口
java.lang.System.LoggerFinder
,它是一個抽象的靜態類 - 一個接口
java.lang.System.Logger
,它提供了日志API java.lang.System
類中的重載方法getLogger()
返回一個System.Logger
配置平台記錄器的細節取決於要使用的記錄器。 例如,如果使用Log4j,則需要單獨配置Log4j框架,並配置平台記錄器。 配置平台記錄器需要執行以下步驟:
- 創建一個實現
System.Logger
接口的類。 - 為
System.LoggerFinder
服務接口創建一個實現。 - 在模塊聲明中指定實現。
在這里配置Log4j 2.0為平台記錄器。 配置和使用Log4j是一個廣泛的話題。 這里僅涵蓋配置平台記錄器所需的Log4i的詳細信息。
Tips
如果不配置自定義平台記錄器,JDK將使用System.LoggerFinder
的默認實現,它在java.logging模塊存在時使用java.util.logging
作為后端框架。 它返回一個將日志消息路由到java.util.logging.Logger
的記錄器實例。 否則,如果不存在java.logging模塊,則默認實現將返回一個簡單的記錄器實例,它將INFO級別及以上的日志消息傳遞到控制台(System.err
)。
1. 設置Log4j類庫
需要下載Log4j 2.0庫,以便用在本節中的示例中。可以從https://logging.apache.org/log4j/2.0/download.html下載Log4J 2.0類庫。 解壓縮下載的文件並將以下兩個JAR復制到C:\Java8Revealed\extlib目錄。 如果將它們復制到另一個目錄,請務必更換路徑。
- log4j-api-2.8.jar
- log4j-core-2.8.jar
如果下載不同版本的Log4j,則這些JAR文件名中的版本可能不同。 在這個例子中,使用這些JAR作為自動模塊。 自動模塊名稱將從JAR文件名派生,它們是log4j.api和log4j.core。
2. 設置NetBeans項目
在NetBeans中創建了名為com.jdojo.logger的Java項目。 上一節討論的兩個Log4j JAR被添加到項目的模塊路徑中,如下圖所示。 要將這些JAR添加到NetBeans中的模塊路徑,需要從擴展菜單中選擇“添加JAR/文件夾”選項以添加到模塊路徑。
3. 定義一個模塊
此示例的所有類和資源將位於com.jdojo.logger的模塊中,其聲明如下所示。
// module-info.java
module com.jdojo.logger {
requires log4j.api;
requires log4j.core;
exports com.jdojo.logger;
provides java.lang.System.LoggerFinder
with com.jdojo.logger.Log4jLoggerFinder;
}
前兩個requires
語句對Log4j JAR的依賴關系,這是這種情況下的自動模塊。exports
語句導出此模塊的com.jdojo.logger包中的所有類型。 provides
語句對於設置平台記錄器很重要。 它聲明提供了com.jdojo.logger.Log4jLoggerFinder
類作為服務接口java.lang.System.LoggerFinder
的實現。 很快就會創建這個類。 com.jdojo.logger模塊的模塊圖如下所示。
注意模塊圖中的循環依賴關系和未命名的模塊。 它們是因為在本模塊聲明中使用的自動模塊。com.jdojo.logger模塊讀取兩個自動模塊。 每個自動模塊讀取所有其他模塊,可以在從log4j.code和log4j.api模塊中的箭頭中看到所有其他模塊。 即使在模塊圖的顯示中,此示例中沒有任何未命名的模塊。 在這個例子中,未命名的模塊將不包含任何類型。 未命名的模塊出現在圖表中,因為自動模塊讀取所有其他模塊,包括未命名的模塊。
4. 添加Log4J配置文件
如下顯示了log4j2.xml的配置文件,它位於NetBeans項目源代碼的根目錄下。 換句話說,log4j2.xml文件放在一個未命名的包中。 此配置使Log4j將消息記錄在當前目錄下的logs/platform.log文件中。 有關此配置的更多信息,請參閱Log4j文檔。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<Appenders>
<File name="JdojoLogFile" fileName="logs/platform.log">
<PatternLayout>
<Pattern>%d %p %c [%t] %m%n</Pattern>
</PatternLayout>
</File>
<Async name="Async">
<AppenderRef ref="JdojoLogFile"/>
</Async>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
5. 創建系統記錄器
需要創建一個系統記錄器,它是一個實現System.Logger
接口的類。 該接口包含以下方法:
String getName()
boolean isLoggable(System.Logger.Level level)
default void log(System.Logger.Level level, Object obj)
default void log(System.Logger.Level level, String msg)
default void log(System.Logger.Level level, String format, Object... params)
default void log(System.Logger.Level level, String msg, Throwable thrown)
default void log(System.Logger.Level level, Supplier<String> msgSupplier)
default void log(System.Logger.Level level, Supplier<String> msgSupplier, Throwable thrown)
void log(System.Logger.Level level, ResourceBundle bundle, String format, Object... params)
void log(System.Logger.Level level, ResourceBundle bundle, String msg, Throwable thrown)
需要在System.Logger
接口中提供四種抽象方法的實現。 getName()
方法返回記錄器的名稱。 可以任何你想要的名字。 如果記錄器可以記錄指定級別的消息,則isLoggable()
方法返回true。 log()
方法的兩個版本方法用於記錄消息,他們被其他默認log()
方法所調用。
System.Logger.Level
枚舉定義要記錄的消息級別的常量。 級別具有名稱和嚴重程度。 級別值為ALL
,TRACE
,DEBUG
,INFO
,WARNING
,ERROR
,OFF
,按照嚴重程度增加。 可以使用其getName()
和getSeverity()
方法獲取級別的名稱和嚴重程度。
下面包含一個Log4jLogger
類的代碼,它實現了System.Logger
接口。
// Log4jLogger.java
package com.jdojo.logger;
import java.util.ResourceBundle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jLogger implements System.Logger {
// The backend logger. Our logger will delegate all loggings
// to this backend logger, which is Log4j
private final Logger logger = LogManager.getLogger();
@Override
public String getName() {
return "Log4jLogger";
}
@Override
public boolean isLoggable(Level level) {
// Get the log4j level from System.Logger.Level
org.apache.logging.log4j.Level log4jLevel = toLog4jLevel(level);
// Check if log4j can handle this level of logging and return the result
return logger.isEnabled(log4jLevel);
}
@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
logger.log(toLog4jLevel(level), msg, thrown);
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
logger.printf(toLog4jLevel(level), format, params);
}
private static org.apache.logging.log4j.Level toLog4jLevel(Level level) {
switch (level) {
case ALL:
return org.apache.logging.log4j.Level.ALL;
case DEBUG:
return org.apache.logging.log4j.Level.DEBUG;
case ERROR:
return org.apache.logging.log4j.Level.ERROR;
case INFO:
return org.apache.logging.log4j.Level.INFO;
case OFF:
return org.apache.logging.log4j.Level.OFF;
case TRACE:
return org.apache.logging.log4j.Level.TRACE;
case WARNING:
return org.apache.logging.log4j.Level.WARN;
default:
throw new RuntimeException("Unknown Level: " + level);
}
}
}
Log4jLogger
的實例用作平台記錄器來記錄平台類的消息。它將所有日志記錄工作委托給一個后端記錄器,這是基礎的Log4j。 logger
實例變量保存對Log4j Logger實例的引用。
現在正在處理兩個日志API,一個由System.Logger
定義,另一個由Log4j定義。它們使用不同的日志級別,它們由兩種不同的類型表示:System.Logger.Level
和org.apache.logging.log4j.Level
。要記錄消息,JDK類將將System.Logger.Level
傳遞給System.Logger
接口的log()
方法,該方法又需要將級別映射到Log4j級別。 toLog4jLevel()方法執行此映射。它收到一個System.Logger.Level並返回一個相應的org.apache.logging.log4j.Level。`isLoggable()方法將系統級別映射到Log4j級別,如果啟用日志記錄功能,則查詢Log4j。可以使用其配置文件配置Log4j來啟用任何級別的日志記錄。
在本例中保持執行的兩個log()
方法邏輯簡單。它們只是把他們的工作委托給Log4j。這些方法不使用ResourceBundle
參數。如果要在記錄之前本地化消息,則需要使用它。
現在已經寫了平台記錄器的主要邏輯,但還沒有准備好測試。需要更多的工作才能看到它的實際效果。
6. 創建日志查找器(Finder)
Java運行時需要找到平台記錄器。 它使用服務定位器模式來查找它。 在模塊聲明中回想一下以下語句。
provides java.lang.System.LoggerFinder
with com.jdojo.logger.Log4jLoggerFinder;
在本節中,創建了Log4jLoggerFinder
實現類,它實現服務接口System.LoggerFinder
。 記住,服務接口不需要是Java接口。 它可以是一個抽象類。 在這種情況下,System.LoggerFinder
是一個抽象類,Log4jLoggerFinder
類將繼承System.LoggerFinder
類。 下面包含Log4jLoggerFinder
類的代碼,它作為服務接口 System.LoggerFinder
的實現。
// Log4jLoggerFinder.java
package com.jdojo.logger;
import java.lang.System.LoggerFinder;
public class Log4jLoggerFinder extends LoggerFinder {
// A logger to be used as a platform logger
private final Log4jLogger logger = new Log4jLogger();
@Override
public System.Logger getLogger(String name, Module module) {
System.out.printf("Log4jLoggerFinder.getLogger(): " +
"[name=%s, module=%s]%n", name, module.getName());
// Use the same logger for all modules
return logger;
}
}
Log4jLoggerFinder
類創建了Log4jLogger
類的實例,並將其引用保存在logger
的實例變量中。 當JDK要求記錄器時,getLogger()
方法返回相同的記錄器。 getLogger()
方法中的名稱和模塊參數是請求的記錄器和請求者的模塊的名稱。 例如,當java.util.Currency
類需要記錄消息時,它會請求一個名java.util.Currency
的記錄器,並且請求者模塊是java.base模塊。 如果要為每個模塊使用單獨的記錄器,可以根據模塊參數返回不同的記錄器。 此示例為所有模塊返回相同的記錄器,因此所有消息都將記錄到同一個位置。 在getLogger()
方法中留下了System.out.println()
語句,因此可以在運行此示例時看到名稱和模塊參數的值。
7. 測試平台記錄器
下面包含PlatformLoggerTest
類的代碼,用於測試平台記錄器。 你可能得到不同的輸出。
// PlatformLoggerTest.java
package com.jdojo.logger;
import java.lang.System.Logger;
import static java.lang.System.Logger.Level.TRACE;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.INFO;
import java.util.Currency;
import java.util.Set;
public class PlatformLoggerTest {
public static void main(final String... args) {
// Let us load all currencies
Set<Currency> c = Currency.getAvailableCurrencies();
System.out.println("# of currencies: " + c.size());
// Let us test the platform logger by logging a few messages
Logger logger = System.getLogger("Log4jLogger");
logger.log(TRACE, "Entering application.");
logger.log(ERROR, "An unknown error occurred.");
logger.log(INFO, "FYI");
logger.log(TRACE, "Exiting application.");
}
}
輸出結果為:
# of currencies: 225
Log4jLoggerFinder.getLogger(): [name=javax.management.mbeanserver, module=java.management]
Log4jLoggerFinder.getLogger(): [name=javax.management.misc, module=java.management]
Log4jLoggerFinder.getLogger(): [name=Log4jLogger, module=com.jdojo.logger]
main()
方法嘗試獲取可用的貨幣符號列表並打印貨幣符號數量。 這樣做的目的是什么? 稍后解釋其目的。 現在,它只是java.util.Currency
類中的一個方法調用。
即使不應該使用記錄器來記錄應用程序的消息,但可以這樣做進行測試。 可以使用System.getLogger()
方法獲取平台記錄器的引用,並開始記錄消息。main()
方法中的以下代碼段執行此操作。
Logger logger = System.getLogger("Log4jLogger");
logger.log(TRACE, "Entering application.");
logger.log(ERROR, "An unknown error occurred.");
logger.log(INFO, "FYI");
logger.log(TRACE, "Exiting application.");
Tips
在JDK 9中,System
類包含兩個可用於獲取平台日志錄引用的靜態方法。 方法是getLogger(String name)
和getLogger(String name, ResourceBundle bundle)
。 兩種方法都返回System.Logger
接口的實例。
輸出中不顯示四條消息。 他們去哪兒了? 回想一下,配置了Log4j將消息記錄到當前目錄下logs/platform.log的文件中。 當前目錄取決於如何運行PlatformLoggerTest
類。 如果從NetBeans運行它,項目的目錄C:\Java9Revealed\com.jdojo.logger是當前目錄。 如果從命令提示符運行它,則可以控制當前目錄。 假設從NetBeans內部運行此類,將在C:\Java9Revealed\com.jdojo.logger\logs\platform.log中找到一個文件。 其內容如下所示。
2017-02-09 09:58:34,644 ERROR com.jdojo.logger.Log4jLogger [main] An unknown error occurred.
2017-02-09 09:58:34,646 INFO com.jdojo.logger.Log4jLogger [main] FYI
每次運行PlatformLoggerTest
類時,Log4j都會將消息附加到logs\platform.log文件中。 可以在運行程序之前刪除日志文件的內容,也可以刪除日志文件,每次運行程序時都會重新創建該文件。
日志文件指示只記錄了兩個消息ERROR和INFO,丟棄了TRACE消息。 這與Log4j配置文件中的記錄級別設置有關,如下所示。 已在記錄器中啟用INFO級別日志記錄:
<Loggers>
<Root level="info">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
每個記錄器級別都有一個嚴重程度,它是一個整數。 如果啟用具有級別x的記錄器,則會記錄其級別嚴重程度大於或等於x的所有消息。 下表顯示了由System.Logger.Level
枚舉及其相關嚴重程度定義的所有級別的名稱。 請注意,在示例中,正在使用Log4j記錄器級別,而不是System.Logger.Level
枚舉定義的級別。 但是,Log4j定義的級別的相對值與下表所示的順序相同。
Name | Severity |
---|---|
ALL | Integer.MIN_VALUE |
TRACE | 400 |
DEBUG | 500 |
INFO | 800 |
WARNING | 900 |
ERROR | 1000 |
OFF | Integer.MAX_VALUE |
如果啟用記錄器的級別INFO,記錄器將記錄在INFO,WARNING和ERROR級別上的所有消息。 如果要在各級記錄消息,可以使用TRACE或ALL作為Log4j配置文件中的級別值。
請注意,輸出中平台類通過調用Log4jLoggerFinder
類的getLogger()
方法請求記錄器三次。前兩次,請求由javax.management
模塊進行。第三個請求出現在輸出中,因為從PlatformLoggerTest
類的main()
方法請求了一個記錄器。
在日志中看到自己的消息,但沒有從JDK類記錄的任何消息。相信你好奇地看到日志中的JDK類的消息。如何知道將消息記錄到平台記錄器的JDK類的名稱以及如何使其記錄消息?沒有簡單直接的方式來知道這一點。查看JDK的源代碼,並找到用於記錄平台消息的sun.util.logging.PlatformLogger
類的引用,發現javax.management模塊記錄了TRACE級別的消息。要查看這些消息,需要設置Log4j記錄器的級別來跟蹤並重新運行PlatformLoggerTest
類。這在日志文件中記錄大量消息。
讓我們回到在PlatformLoggerTest
類中使用Currency
類。 用它來顯示JDK類記錄的消息,在這種情況下,這是java.util.Currency
類。 當請求所有貨幣的列表時,JDK會讀取其自身的貨幣列表(JDK內置的貨幣列表),以及位於JAVA_HOME\lib目錄中的自定義currency.properties文件。 在這種情況下,正在使用JDK運行此示例,因此JAVA_HOME是指JDK_HOME。 創建一個文本文件,該文件的路徑將是JDK_HOME\lib\currency.properties。 請注意,該文件只包含一個單詞,即ABadCurrencyFile。 可以使用任何一個單詞。
Currency
類嘗試將currency.properties文件加載為Java屬性文件,該文件應包含j鍵值對。 此文件不是有效的屬性文件。 當Currency
類嘗試加載它時,將拋出異常,並且該類向平台記錄器記錄錯誤消息。 現在,知道創建了無效的貨幣文件,因此可以通過JDK類查看平台記錄器。
再次運行PlatformLoggerTest類,它提供以下輸出:
Log4jLoggerFinder.getLogger(): [name=javax.management.mbeanserver, module=java.management]
Log4jLoggerFinder.getLogger(): [name=javax.management.misc, module=java.management]
Log4jLoggerFinder.getLogger(): [name=java.util.Currency, module=java.base]
# of currencies: 225
Log4jLoggerFinder.getLogger(): [name=Log4jLogger, module=com.jdojo.logger]
輸出表明java.base模塊請求了java.util.Currency
的平台記錄器。 這是因為使用的無效的貨幣文件。 日志文件的內容如下所示,它顯示了Currency
類中記錄的消息。
2017-02-09 10:45:52,413 INFO com.jdojo.logger.Log4jLogger [main] currency.properties entry for ABADCURRENCYFILE is ignored because of the invalid country code.
2017-02-09 10:45:52,420 ERROR com.jdojo.logger.Log4jLogger [main] An unknown error occurred.
2017-02-09 10:45:52,420 INFO com.jdojo.logger.Log4jLogger [main] FYI
8. 進一步的工作
展示了使用Log4j 2.0作為后端記錄器配置平台記錄器的示例。在生產環境中可以使用此示例之前,還有很多工作要做。其中一個改進是記錄正在記錄消息的類的名稱。在上面的例子中,發現所有記錄的消息使用相同的類名稱,即com.jdojo.logger.Log4jLogger
作為記錄器的類,這是不正確的。從com.jdojo.logger.PlatformLoggerTest
類中記錄了兩條消息,並從java.util.Currency
類中記錄了一條消息。怎么解決這個問題?
先來嘗試理解這個問題。 Logger的類名由Log4j決定。它只是查看其log()
方法的調用者,並將該類用作記錄消息的那個類。在之前的例子中,兩個log()
方法調用Log4j的log()
方法來委托日志記錄工作。 Log4j將com.jdojo.logger.Log4jLogger
類視為消息的記錄器,並將其名稱作為記錄消息中的記錄器類。
這里有兩種方法來解決它:
- 在
Log4jLogger
類中,使用使用添加到JDK 9的棧遍歷API自己格式化消息。棧遍歷API提供調用者的類名和其他詳細信息。這將更改Log4j配置文件中的模式布局,因此Log4j不會在消息中確定並包含記錄器的類名稱。 - 可以等待下一個版本的Log4j,這可能會支持開箱即用的JDK 9平台記錄器。
二. 統一JVM日志
JDK 9添加了一個新的命令行選項-Xlog
,它可以訪問從JVM的所有組件記錄的所有消息的單點訪問。 該選項的使用語法有點復雜。 先解釋一下已記錄消息的詳細信息。
Tips
可以使用-Xlog:help
選項與java
命令打印-Xlog
選項的描述。 該描述包含具有示例的所有選項的語法和值。
當JVM記錄消息或當正在尋找JVM記錄的消息時,請記住以下幾點:
- JVM需要確定消息所屬的主題(或JVM組件)。 例如,如果消息與垃圾回收有關,那么消息應該被標記為這樣。 消息可能屬於多個主題。 例如,消息可能屬於垃圾回收和堆管理。 因此,消息可以具有與其相關聯的多個標簽((tag)。
像任何其他日志記錄工具一樣,JVM日志可能會發生在不同的級別,如信息,警告等。
應該能夠使用附加的上下文信息(如當前日期和時間,線程記錄消息,消息使用的標簽等)來裝飾已記錄的消息。
信息應該在哪里記錄? 他們應該記錄到stdout,stderr,還是一個或多個文件? 是否可以指定日志文件的選項策略,例如文件名,大小和文件輪換策略。
如果了解了這些要點,現在是學習用於描述JVM日志記錄的以下術語的時候了:
- Tag(標簽)
- Level(級別)
- Decoration(裝飾)
- Output(輸出)
以下是運行com.jdojo.Welcome
類的示例,它使用標准輸出上的嚴格級別為trace或以上的gc
標簽(tag)記錄所有消息,其級別,時間和標簽裝飾。
C:\Java9revealed> java -Xlog:gc=trace:stdout:level,time,tags
--module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome
輸出結果為:
[2017-02-10T12:50:11.412-0600][trace][gc] MarkStackSize: 4096k MarkStackSizeMax: 16384k
[2017-02-10T12:50:11.427-0600][debug][gc] ConcGCThreads: 1
[2017-02-10T12:50:11.432-0600][debug][gc] ParallelGCThreads: 4
[2017-02-10T12:50:11.433-0600][debug][gc] Initialize mark stack with 4096 chunks, maximum 16384
[2017-02-10T12:50:11.436-0600][info ][gc] Using G1
Welcome to the Module System.
Module Name: com.jdojo.intro
1. 消息標簽
每個日志的消息與一個或多個稱為標簽集的標簽相關聯。以下是所有可用標簽的列表。此列表將來可能會更改。要獲取支持的標簽列表,使用-Xlog:help
選項與java命令。
add, age, alloc, aot, annotation, arguments, attach, barrier, biasedlocking, blocks, bot, breakpoint, census, class, classhisto, cleanup, compaction, constraints, constantpool, coops, cpu, cset, data, defaultmethods, dump, ergo, exceptions, exit, fingerprint, freelist, gc, hashtables, heap, humongous, ihop, iklass, init, itables, jni, jvmti, liveness, load, loader, logging, mark, marking, methodcomparator, metadata, metaspace, mmu, modules, monitorinflation, monitormismatch, nmethod, normalize, objecttagging, obsolete, oopmap, os, pagesize, patch, path, phases, plab, promotion, preorder, protectiondomain, ref, redefine, refine, region, remset, purge, resolve, safepoint, scavenge, scrub, stacktrace, stackwalk, start, startuptime, state, stats, stringdedup, stringtable, stackmap, subclass, survivor, sweep, task, thread, tlab, time, timer, update, unload, verification, verify, vmoperation, vtables, workgang, jfr, system, parser, bytecode, setting, event
如果有興趣記錄垃圾收集和啟動的消息,則可以使用帶有-Xlog
選項的gc
和startuptime
標簽。列表中的大多數標簽都有深奧的名稱,實際上它們適用於在JVM上工作的開發人員,而不是應用程序開發人員。
Tips
可以使用-Xlog
選項使用all這個特別標簽,通知JVM記錄所有消息,而不考慮與它們相關聯的標記。 標簽的默認值就是all。
2. 消息級別
級別是根據消息的嚴重程度確定要記錄的消息日志的嚴重性級別。 級別具有以下嚴重性順序的值:trace,debug,info,warning和error。 如果為嚴重級別S啟用日志記錄,則將記錄嚴重級別為S和更大的所有消息。 例如,如果在info級別啟用日志記錄,將記錄info,warning和error級別的所有消息。
Tips
可以使用-Xlog
選項命名的特殊嚴重成都級別off來禁用所有級別的日志記錄。 級別的默認值為info。
3. 消息裝飾
記錄JVM消息之前,可以增加其他信息。 這些額外的信息被稱為裝飾,它們是前面的消息。 每個裝飾都在括號內——“[]”。 下表含所有具有長名稱和短名稱的裝飾的列表。長名稱或短名稱與-Xlog
選項 一起使用。
長名字 | 短名字 | 描述 |
---|---|---|
hostname | hn | 計算機名稱 |
level | l | 消息的嚴重程度 |
pid | p | 進程標識符 |
tags | tg | 與消息相關聯的所有標簽 |
tid | ti | 線程標識符 |
time | t | 當前時間和日期為ISO-8601格式(例如:2017-02-10T18:42:58.418 + 0000) |
timemillis | tm | 當前時間以毫秒為單位,與System.currentTimeMillis() 生成的值相同 |
timenanos | tn | 當前時間以納秒為單位,與System.nanoTime() 生成的值相同 |
uptime | u | 自JVM啟動以來運行的時間,以秒和毫秒為單位(例如9.219s) |
uptimemillis | um | 自JVM啟動以來運行的毫秒數 |
uptimenanos | un | 自JVM啟動以來運行的納秒數 |
utctime | utc | UTC格式的當前時間和日期(例如:2017-02-10T12:42:58.418-0600) |
Tips
可以使用none的特殊裝飾與-Xlog
選項關閉裝飾。 裝飾的默認值為uptime,level和tag。
4. 消息輸出裝飾
可以指定JVM日志發送的三個目的地:
- stdout
- stderr
- file=
使用stdout和stderr值分別在標准輸出和標准錯誤上打印JVM日志。 默認的輸出目標是stdout。
使用文件值指定文本文件名將日志發送到文本文件。 可以在文件名中使用%p
和%t
,這將分別擴展到JVM的PID和啟動時間。 例如,如果使用-Xlog
選項指定file=jvm%p_%t.log
作為輸出目標,則對於每個JVM運行,消息將被記錄到名稱如下所示的文件中:
- jvm2348_2017-02-10_13-26-05.log
- jvm7292_2017-02-10_13-26-06.log
每次啟動JVM時,將創建一個類似於此列表中顯示的日志文件。 這里,2348和7292是兩個運行的JVM的PID。
Tips
缺少stdout和stderr作為輸出目的地表示輸出目的地是一個文本文件。 代替使用file=jvm.log
,可以簡單地使用jvm.log作為輸出目的地。
可以指定用於將輸出發送到文本文件的其他選項:
- filecount=
- filesize=
這些選項用於控制每個日志文件的最大大小和最大日志文件數。 考慮以下選項:
file=jvm.log::filesize=1M,filecount= 3
注意使用兩個連續的冒號(::)。 此選項使用jvm.log作為日志文件。 日志文件的最大大小為1M,日志文件的最大計數為3。它將創建四個日志文件:jvm.log,jvm.log.0,jvm.log.1和jvm.log0.2。 當前消息被記錄到jvm.log文件,當前文件中記錄的消息超過1MB時,其他三個文件輪換。 可以使用K指定千字節的文件大小,M表示兆字節。 如果在不包含K或M后綴的情況下指定文件大小,則該選項假定為字節。
5. -Xlog
語法
以下是使用-Xlog
選項的語法:
-Xlog[:<contents>][:[<output>][:[<decorators>][:<output-options>]]]
與-Xlog
使用的選項用冒號(:)分隔。 所有選項都是可選的。 如果缺少-Xlog
中的前一部分,則必須對該部分使用冒號。 例如,-Xlog :: stderr
表示所有部分都是默認值,除了指定為stderr的<output>
部分。
-Xlog
的最簡單的使用方法如下,將所有JVM消息記錄到標准輸出:
java -Xlog --module-path com.jdojo.intro\dist --module com.jdojo.intro/com.jdojo.intro.Welcome
有兩個特殊的-XLog
選項:help
和disable
,可以用作-Xlog
:-Xlog:help
打印-Xlog的幫助,-Xlog:disable
禁用所有JVM日志。 你可能會認為,不是使用-Xlog:disable
,你根本不會使用-Xlog
選項。 你是對的。 但是,由於不同的原因存在disable
選項。 -Xlog
選項可以使用相同的命令多次使用。 如果多次出現-Xlog
包含相同類型的設置,則最后一個-Xlog
的設置將生效。 因此,可以指定-Xlog:disable
作為第一個選項,並指定另一個-Xlog
打開特定類型的日志記錄。 這樣,首先關閉所有默認值,然后指定你感興趣的選項。
<contents>
部分指定要記錄的消息的標簽和嚴重程度級別。 其語法如下:
tag1[+tag2...][*][=level][,...]
<contents>
部分中的“+”表示邏輯AND。例如,gc + exit
表示記錄其標簽集完全包含兩個tag——gc
和exit
的所有消息。標簽名末尾的“*”用作通配符,這意味着“至少”。例如,gc *
表示記錄標簽集至少包含gc
的所有消息,它將使用標簽集[gc]
,[gc,exit]
,[gc,remset,exit]
等記錄消息。如果使用gc + exit *
,表示記錄包含至少包含gc
和exit
標簽的標簽集的所有消息,它將使用標簽集[gc,exit]
,[gc,remset,exit]
等記錄消息。可以指定嚴重程度每個要記錄的標簽名稱的級別。例如,gc = trace
記錄所有帶有標簽集的消息,其中只包含嚴重級別為trace或更高的gc
。可以指定以逗號分隔的多個條件。例如,gc = trace,heap = error
將使用gc
標簽集在trace或更高級別或使用heap
標簽集在錯誤級別記錄所有消息。
運行這些命令時可能會得到不同的輸出。以下命令將gc
和startuptime
指定為標簽,將其他設置保留為默認值:
C:\Java9Revealed>java -Xlog:gc,startuptime --module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome
輸出結果為;
[0.017s][info][startuptime] StubRoutines generation 1, 0.0002258 secs
[0.022s][info][gc ] Using G1
[0.022s][info][startuptime] Genesis, 0.0045639 secs
...
使用-Xlog
與-Xlog:all=info:stdout:uptime,level,tags
相同。 它記錄嚴重級別info或更高級別的所有信息,並包括帶有裝飾器的uptime
,level
和tag
。 以下命令顯示如何使用默認設置獲取JVM日志。
C:\Java9Revealed>java -Xlog --module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome
顯示部分輸出:
[0.015s][info][os] SafePoint Polling address: 0x000001195fae0000
[0.015s][info][os] Memory Serialize Page address: 0x000001195fdb0000
[0.018s][info][biasedlocking] Aligned thread 0x000001195fb37f40 to 0x000001195fb38000
[0.019s][info][class,path ] bootstrap loader class path=C:\java9\lib\modules
[0.019s][info][class,path ] classpath:
[0.020s][info][class,path ] opened: C:\java9\lib\modules
[0.020s][info][class,load ] opened: C:\java9\lib\modules
[0.027s][info][os,thread ] Thread is alive (tid: 17724).
[0.027s][info][os,thread ] Thread is alive (tid: 6436).
[0.033s][info][gc ] Using G1
[0.034s][info][startuptime ] Genesis, 0.0083975 secs
[0.038s][info][class,load ] java.lang.Object source: jrt:/java.base
[0.226s][info][os,thread ] Thread finished (tid: 7584).
[0.226s][info][gc,heap,exit ] Heap
[0.226s][info][gc,heap,exit ] Metaspace used 6398K, capacity 6510K,
[0.226s][info][safepoint,cleanup ] mark nmethods, 0.0000057 secs
[0.226s][info][os,thread ] Thread finished (tid: 3660).
...
以下命令將所有具有至少gc
嚴重級別debug或更高標記的消息記錄到當前目錄中具有時間裝飾器的gc.log的文件。 請注意,該命令在標准輸出上打印兩行消息,這些消息來自Welcome
類的main()
方法。
C:\java9revealed>java -Xlog:gc*=trace:file=gc.log:time --module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome
但是,顯示了gc.log文件的部分輸出,而不是標准輸出上打印的內容。
[2017-02-11T08:40:23.942-0600] Maximum heap size 2113804288
[2017-02-11T08:40:23.942-0600] Initial heap size 132112768
[2017-02-11T08:40:23.942-0600] Minimum heap size 6815736
[2017-02-11T08:40:23.942-0600] MarkStackSize: 4096k MarkStackSizeMax: 16384k
[2017-02-11T08:40:23.966-0600] Heap region size: 1M
[2017-02-11T08:40:23.966-0600] WorkerManager::add_workers() : created_workers: 4
[2017-02-11T08:40:23.966-0600] Initialize Card Live Data
[2017-02-11T08:40:23.966-0600] ParallelGCThreads: 4
[2017-02-11T08:40:23.966-0600] WorkerManager::add_workers() : created_workers: 1
...
以下命令記錄與上一個命令相同的消息,除了它記錄沒有任何裝飾的消息:
C:\java9revealed>java -Xlog:gc*=trace:file=gc.log:none --module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome
輸出的部分消息為:
Maximum heap size 2113804288
Initial heap size 132112768
Minimum heap size 6815736
MarkStackSize: 4096k MarkStackSizeMax: 16384k
Heap region size: 1M
WorkerManager::add_workers() : created_workers: 4
Initialize Card Live Data
ParallelGCThreads: 4
WorkerManager::add_workers() : created_workers: 1
...
以下命令記錄與上一個命令相同的消息,除了它使用有10個文件的輪換文件集,大小為5MB,基本名稱為gc.log:
C:\Java9Revealed>java -Xlog:gc*=trace:file=gc.log:none:filesize=5m,filecount=10
--module-path com.jdojo.intro\dist --module com.jdojo.intro/com.jdojo.intro.Welcome
以下命令記錄包含嚴重級別為debug或更高版本的gc
標簽的所有消息。 它關閉所有包含exit
標簽的消息。 它不會記錄包含gc
和exit
標簽的消息。 消息以默認裝飾輸出在stdout上。
C:\Java9Revealed>java -Xlog:gc*=debug,exit*=off --module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome
以下顯示部分輸出。
[0.015s][info][gc,heap] Heap region size: 1M
[0.015s][debug][gc,heap] Minimum heap 8388608 Initial heap 132120576 Maximum heap 2113929216
[0.015s][debug][gc,ergo,refine] Initial Refinement Zones: green: 4, yellow: 12, red: 20, min yellow size: 8
[0.016s][debug][gc,marking,start] Initialize Card Live Data
[0.016s][debug][gc,marking ] Initialize Card Live Data 0.024ms
[0.016s][debug][gc ] ConcGCThreads: 1
[0.018s][debug][gc,ihop ] Target occupancy update: old: 0B, new: 132120576B
[0.019s][info ][gc ] Using G1
[0.182s][debug][gc,metaspace,freelist] space @ 0x000001e7dbeb8260 704K, 99% used [0x000001e7fe880000, 0x000001e7fedf8400, 0x000001e7fee00000, 0x000001e7ff080000)
[0.191s][debug][gc,refine ] Stopping 0
...
以下命令記錄startuptime
標簽的信息,同時使用了hostname
,uptime
,level
和tag
的裝飾器。 所有其他設置都保留為默認設置。 它在info級別或更高級別記錄消息,並將它們記錄到stdout。 注意在命令中使用兩個連續的冒號(::)。 它們是必需的,因為沒有指定輸出目的地。
C:\Java9Revealed>java -Xlog:startuptime::hostname,uptime,level,tags
--module-path com.jdojo.intro\dist --module com.jdojo.intro/com.jdojo.intro.Welcome
輸出信息為:
[0.015s][kishori][info][startuptime] StubRoutines generation 1, 0.0002574 secs
[0.019s][kishori][info][startuptime] Genesis, 0.0038339 secs
[0.019s][kishori][info][startuptime] TemplateTable initialization, 0.0000081 secs
[0.020s][kishori][info][startuptime] Interpreter generation, 0.0010698 secs
[0.032s][kishori][info][startuptime] StubRoutines generation 2, 0.0001518 secs
[0.032s][kishori][info][startuptime] MethodHandles adapters generation, 0.0000229 secs
[0.033s][kishori][info][startuptime] Start VMThread, 0.0001491 secs
[0.055s][kishori][info][startuptime] Initialize java.lang classes, 0.0224295 secs
[0.058s][kishori][info][startuptime] Initialize java.lang.invoke classes, 0.0015945 secs
[0.162s][kishori][info][startuptime] Create VM, 0.1550707 secs
Welcome to the Module System.
Module Name: com.jdojo.intro
三. 總結
JDK 9已經對平台類(JDK類)和JVM組件的日志系統進行了大修。有一個新的API可以指定所選擇的日志記錄框架作為從平台類記錄消息的日志后端。還有一個新的命令行選項,可讓從所有JVM組件訪問消息。
平台日志API允許指定將由所有平台類用於記錄其消息的自定義記錄器。可以使用現有的日志記錄框架,如Log4j作為記錄器。該API由java.lang.System.LoggerFinder
類和java.lang.System.Logger
接口組成。
System.Logger
接口的實例代表平台記錄器。 System.LogFinder
類是一個服務接口。需要為此服務接口提供一個實現,該接口返回System.Logger
接口的實例。可以使用java.lang.System
類中的getLogger()
方法獲取System.Logger
。應用程序中的一個模塊必須包含一個表示System.LogFinder
服務接口實現的provides
語句。否則,將使用默認記錄器。
JDK 9允許使用-Xlog
的單個選項從所有組件記錄所有JVM消息。該選項允許指定消息的類型,消息的嚴重程度級別,日志目標,記錄消息的裝飾和日志文件屬性。消息由一組標簽標識。System.Logger.Level
枚舉的常量指定消息的嚴重程度級別。日志目標可以是stdout,stderr或一個文件。