原文链接
原作者:Rohit Joshi
译者:smallclover
个人翻译,水平有限,如有错误欢迎指出,谢谢!
Jdk Logging 解析
在本文中,我们将讨论关于Java的日志(logging
)功能。日志,用简单的话来说就是记录应用程序的活动。日志通常被用于存储异常(exceptions
)、信息(information
)、警告(warnings
)的消息,这些消息可能会出现在程序的整个执行过程。日志在程序员调式(debug
)程序的过程中会有所帮助。
Java 包(package)java.util.logging
与日志记录的功能有关。这个包由一些用于日志功能的类(class)和接口(interface)组成。系统使用Logger
对象(object)来记录消息。
这个Logger
对象会被分配一个LogRecord
对象,该LogRecord
对象存储了记录的消息。这个LogRecord
对象会被转发到所有的handler,再由handler分派给Logger
对象。logger和handler可以视需要使用一个与它们有关联的过滤器(Filter
)来过滤日志消息。然后,handler会发布这些被日志记录的消息到外部的系统。
让我们从这个包中一些比较重要的类开始。
1.Logger and Level
创建一个Logger
类的logger对象来记录消息。logger对象会要求使用者提供一个名称和一组用于设置记录消息的等级的方法。虽然你可以提供任何一个名字到logger,但是建议使用包和类名来创建logger。
Level类提供7种日志级别
SEVERE (最高级别)
WARNING
INFO
CONFIG
FINE
FINER
FINEST (最低级别)
在Level
中所有的级别(level)都被定义成静态常量字段(static final field
)。你能使用任何一个级别,然后根据这些级别来记录消息。另外,它还提供了一个level OFF
可以关闭日志记录,一个level All
用于打开所有级别的日志。
我们看一个关于如何创建和使用logger的示例。
LoggerExample.java
01 package com.javacodegeeks.corejava.util.logging;
02
03 import java.io.IOException;
04 import java.util.logging.Level;
05 import java.util.logging.Logger;
06
07 public class LoggerExample {
08
09 private static final Logger LOGGER = Logger.getLogger(LoggerExample.class.getName());
10 public static void main(String[] args) throws SecurityException, IOException {
11
12 LOGGER.info("Logger Name: "+LOGGER.getName());
13
14 LOGGER.warning("Can cause ArrayIndexOutOfBoundsException");
15
16 //An array of size 3
17 int []a = {1,2,3};
18 int index = 4;
19 LOGGER.config("index is set to "+index);
20
21 try{
22 System.out.println(a[index]);
23 }catch(ArrayIndexOutOfBoundsException ex){
24 LOGGER.log(Level.SEVERE, "Exception occur", ex);
25 }
26
27
28 }
29
30 }
如果我们运行以上代码,我们将得到如下结果:
1 Jun 08, 2014 1:19:30 PM com.javacodegeeks.corejava.util.logging.LoggerExample main
2 INFO: Logger Name: com.javacodegeeks.corejava.util.logging.LoggerExample
3 Jun 08, 2014 1:19:31 PM com.javacodegeeks.corejava.util.logging.LoggerExample main
4 WARNING: Can cause ArrayIndexOutOfBoundsException
5 Jun 08, 2014 1:19:31 PM com.javacodegeeks.corejava.util.logging.LoggerExample main
6 SEVERE: Exception occur
7 java.lang.ArrayIndexOutOfBoundsException: 4
8 at com.javacodegeeks.corejava.util.logging.LoggerExample.main(LoggerExample.java:22)
在上面这个示例中,我们使用getLogger
静态方法(static method)创建了一个logger对象。然后,我们记录了不同级别的日志消息。为了阐述Logger
的使用我们也尝试抛出了ArrayIndexOutOfBoundsException
。
让我们看一看在本例中使用的方法。
Logger.getLogger(String name):该方法通过传递的name参数来创建或者发现一个logger。
void info(String msg):该实例方法用于记录INFO级别的日志消息,但是前提是当前的logger能够使用INFO级别的日志消息,否则该级别的日志消息将会被忽略。
void warning(String msg): 该实例方法用于记录WARNING级别的日志消息,但是前提是当前的logger能够使用INFO级别的日志消息,否则该级别的日志消息将会被忽略。
void config(String msg): 该实例方法用于记录CONFIG级别的日志消息,但是前提是当前的logger能够使用INFO级别的日志消息,否则该级别的日志消息将会被忽略。
void log(Level level, String msg, Object param1):该方法根据传递的日志级别来记录消息,同时传递的参数还有一个object。当你想存储一个对象在日志中的时候你可使用这个方法。正如我们上面的示例一样,我们记录了一个SEVERE级别的exception object。
请注意,INFO级别是logger的默认级别。所有低于INFO级别的消息记录将会被忽略。正如你所看到的那样,WARNING级别的消息记录被忽略了,并没有被打印在控制台上。(此处原文可能有误)
译者注:这里原文可能有错误,首先WARNING的级别是高于INFO级别的,其次WARNING级别的日志的确打印在控制台上。所以,译者认为这里应该是CONFIG级别。请读者注意。
2.Handler
Handler是日志框架的组件之一,它负责打印日志消息到目标目的地。这个目的地可以是控制台(console)也可以是文件(file)。Handler
获得一个LogRecord
对象形式的日志消息,并输出到目标目的地。一个Logger
可以关联一个或者多个Handler
,最后将日志消息转发给所有的Handler
。Handler
在Java 包 java.util.logging
中是一个抽象类,同时它也是各种Handler
的父类。
在Java中内置4种handler。
ConsoleHandler: ConsoleHandler 记录所有的System.in的日志消息,默认情况下,Logger是与此Handler相关联的。
FileHandler: FileHandler 记录所有的来自特定的文件或者一组文件集合的日志消息。
StreamHandler: StreamHandler会发布所有的日志消息到一个OutputStream.
SocketHandler: SocketHandler会发布LogRecords到一个网络连接流中。
MemoryHandler: 它是用来保持LogRecords内存缓冲区。如果缓冲区满了,新的LogRecords会覆盖旧的LogRecords。
HandlerExample.java
01 package com.javacodegeeks.corejava.util.logging;
02
03 import java.io.IOException;
04 import java.util.logging.ConsoleHandler;
05 import java.util.logging.FileHandler;
06 import java.util.logging.Handler;
07 import java.util.logging.Level;
08 import java.util.logging.Logger;
09
10 public class HandlerExample {
11
12 private static final Logger LOGGER = Logger.getLogger(LoggerExample.class.getName());
13 public static void main(String[] args) {
14
15 Handler consoleHandler = null;
16 Handler fileHandler = null;
17 try{
18 //Creating consoleHandler and fileHandler
19 consoleHandler = new ConsoleHandler();
20 fileHandler = new FileHandler("./javacodegeeks.log");
21
22 //Assigning handlers to LOGGER object
23 LOGGER.addHandler(consoleHandler);
24 LOGGER.addHandler(fileHandler);
25
26 //Setting levels to handlers and LOGGER
27 consoleHandler.setLevel(Level.ALL);
28 fileHandler.setLevel(Level.ALL);
29 LOGGER.setLevel(Level.ALL);
30
31 LOGGER.config("Configuration done.");
32
33 //Console handler removed
34 LOGGER.removeHandler(consoleHandler);
35
36 LOGGER.log(Level.FINE, "Finer logged");
37 }catch(IOException exception){
38 LOGGER.log(Level.SEVERE, "Error occur in FileHandler.", exception);
39 }
40
41 LOGGER.finer("Finest example on LOGGER handler completed.");
42
43 }
44
45 }
如果我们运行以上代码,我们将会得到以下结果:
1 Jun 08, 2014 1:43:19 PM com.javacodegeeks.corejava.util.logging.HandlerExample main
2 CONFIG: Configuration done.
该示例还会在项目的根目录下生成日志文件javacodegeeks.log。
该文件包含以下记录:
01 <?xml version="1.0" encoding="windows-1252" standalone="no"?>
02 <!DOCTYPE log SYSTEM "logger.dtd">
03 <log>
04 <record>
05 <date>2014-06-08T13:43:19</date>
06 <millis>1402215199326</millis>
07 <sequence>0</sequence>
08 <logger>com.javacodegeeks.corejava.util.logging.LoggerExample</logger>
09 <level>CONFIG</level>
10 <class>com.javacodegeeks.corejava.util.logging.HandlerExample</class>
11 <method>main</method>
12 <thread>1</thread>
13 <message>Configuration done.</message>
14 </record>
15 <record>
16 <date>2014-06-08T13:43:19</date>
17 <millis>1402215199376</millis>
18 <sequence>1</sequence>
19 <logger>com.javacodegeeks.corejava.util.logging.LoggerExample</logger>
20 <level>FINE</level>
21 <class>com.javacodegeeks.corejava.util.logging.HandlerExample</class>
22 <method>main</method>
23 <thread>1</thread>
24 <message>Finer logged</message>
25 </record>
26 <record>
27 <date>2014-06-08T13:43:19</date>
28 <millis>1402215199376</millis>
29 <sequence>2</sequence>
30 <logger>com.javacodegeeks.corejava.util.logging.LoggerExample</logger>
31 <level>FINER</level>
32 <class>com.javacodegeeks.corejava.util.logging.HandlerExample</class>
33 <method>main</method>
34 <thread>1</thread>
35 <message>Finest example on LOGGER handler completed.</message>
36 </record>
37 </log>
在该示例中,我们通过FileHandler
和ConsoleHandler记录日志消息。
我们将讨论上面的示例。
ConsoleHandler():构造函数,创建一个与system.err有关的ConsoleHandler
FileHandler(String pattern):构造函数,创建一个FileHandler,记录消息到指定文件。
void addHandler(Handler handler):该实例方法来自类Logger,分配一个handler给logger对象。你也可以分配多个handler给一个logger对象。就像该示例一样,我们分配了ConsoleHandler 和 FileHandler给了一个logger对象。
void setLevel(Level newLevel):Logger和Handler类都有该方法,logger对象通过它来设置指定的日志级别,规定哪些级别的消息可以被记录。消息级别小于指定的级别的将会被忽略。
void removeHandler(Handler handler):该方法会移除与logger对象相关联的handler,一旦该handler被移除,它将没有能力发布任何日志消息。在该示例中,我们移除了ConsoleHandler之后,接下来所有的日志消息将不会被打印在控制台。
void finer(String msg):该示例方法用来记录FINER级别的消息,如果当前允许FINER级别的日志消息则记录,否则忽略。
该日志消息通过FileHandler 发布,在上面的示例中被发布为XML格式。该格式是FileHandler 的默认格式,我们可以改变该handler 的格式。
在下一节中,我们将讨论关于Formatter
类和它的使用。
3.Formatter
Formatter
用于格式化LogRecord
。每个handler会关联一个formatter。Java提供了两个内置的超类formatter:SimpleFormatter
和 XMLFormatter
。让我们来看一些示例:
FormatterExample.java
01 package com.javacodegeeks.corejava.util.logging;
02
03 import java.io.IOException;
04 import java.util.logging.Formatter;
05 import java.util.logging.FileHandler;
06 import java.util.logging.Handler;
07 import java.util.logging.Level;
08 import java.util.logging.Logger;
09 import java.util.logging.SimpleFormatter;
10
11 public class FormatterExample {
12
13 private static final Logger LOGGER = Logger.getLogger(LoggerExample.class.getName());
14 public static void main(String[] args) {
15
16 Handler fileHandler = null;
17 Formatter simpleFormatter = null;
18 try{
19
20 // Creating FileHandler
21 fileHandler = new FileHandler("./javacodegeeks.formatter.log");
22
23 // Creating SimpleFormatter
24 simpleFormatter = new SimpleFormatter();
25
26 // Assigning handler to logger
27 LOGGER.addHandler(fileHandler);
28
29 // Logging message of Level info (this should be publish in the default format i.e. XMLFormat)
30 LOGGER.info("Finnest message: Logger with DEFAULT FORMATTER");
31
32 // Setting formatter to the handler
33 fileHandler.setFormatter(simpleFormatter);
34
35 // Setting Level to ALL
36 fileHandler.setLevel(Level.ALL);
37 LOGGER.setLevel(Level.ALL);
38
39 // Logging message of Level finest (this should be publish in the simple format)
40 LOGGER.finest("Finnest message: Logger with SIMPLE FORMATTER");
41 }catch(IOException exception){
42 LOGGER.log(Level.SEVERE, "Error occur in FileHandler.", exception);
43 }
44 }
45
46 }
如果我们运行以上代码,我们将得到以下结果:
1 Jun 08, 2014 4:57:02 PM com.javacodegeeks.corejava.util.logging.FormatterExample main
2 INFO: Finnest message: Logger with DEFAULT FORMATTER
该示例也会在项目的根目录下生成一个日志文件:javacodegeeks.formatter.log。
在上面的示例中,我们使用SimpleFormatter
以一种简单的,可读的格式打印LogRecord
。请注意在设置handler的格式为 SimpleFormatter
之前,我们的消息会发布到一个XML格式的文件中。因为FileHandler
的默认格式是XMLFormatter
。同时还要注意这个LogRecord
也会被发布到控制台,因为 ConsoleHandler
和 Logger
是默认关联的。
SimpleFormatter():这个构造函数用来创建一个SimpleFormatter对象.
void setFormatter(Formatter newFormatter):这个方法来自handler类,handler通过它设置formatter。
3.Filter
Filter
接口(interface)位与java.util.logging
包下。 Handler
通过它来控制消息的记录。每一个Logger
和Handler
可以选择一个Filter
。Filter
有一个isLoggable
方法,该方法返回一个boolean
值。在Logger
或者Handler
发布消息之前会调用此方法,如果返回值为true就发布消息,否则就忽略。
FilterExample.java
01 package com.javacodegeeks.corejava.util.logging;
02
03 import java.util.logging.Filter;
04 import java.util.logging.LogRecord;
05 import java.util.logging.Logger;
06
07 public class FilterExample implements Filter{
08
09 private static final Logger LOGGER = Logger.getLogger(LoggerExample.class.getName());
10 public static void main(String[] args) {
11 //Setting filter FilterExample
12 LOGGER.setFilter(new FilterExample());
13 //Since this message string does not contain the word important. Despite of being the Level SEVERE this will be ignored
14 LOGGER.severe("This is SEVERE message");
15 //This will get published
16 LOGGER.warning("This is important warning message");
17
18 }
19
20 // This method will return true only if the LogRecord object contains the message which contains the word important
21 @Override
22 public boolean isLoggable(LogRecord record) {
23 if(record == null)
24 return false;
25
26 String message = record.getMessage()==null?"":record.getMessage();
27
28 if(message.contains("important"))
29 return true;
30
31 return false;
32 }
33
34 }
如果我们运行以上代码,我们将得到以下的结果:
1 Jun 08, 2014 5:13:46 PM com.javacodegeeks.corejava.util.logging.FilterExample main
2 WARNING: This is important warning message
boolean isLoggable(LogRecord record):该方法来自Filter的接口,它会检查LogRecord是否可以发布。
void setFilter(Filter newFilter): 该方法设置一个 Filter 控制 Logger的输出。
5.Configuration
你可以通过一个配置文件把配置的属性提供给Logger
。这有助于你移除配置代码并且提供一种更加简单的方法在不改变代码的情况下一次又一次的更改配置。通过类LogManager
我们可以使用这种灵活的方式。
ConfigurationExample.java
01 package com.javacodegeeks.corejava.util.logging;
02
03 import java.io.FileInputStream;
04 import java.io.IOException;
05 import java.util.logging.Level;
06 import java.util.logging.LogManager;
07 import java.util.logging.Logger;
08
09 public class ConfigurationExample {
10
11 private static final LogManager logManager = LogManager.getLogManager();
12 private static final Logger LOGGER = Logger.getLogger("confLogger");
13 static{
14 try {
15 logManager.readConfiguration(newFileInputStream("./javacodegeeks.properties"));
16 } catch (IOException exception) {
17 LOGGER.log(Level.SEVERE, "Error in loading configuration",exception);
18 }
19 }
20 public static void main(String[] args) {
21 LOGGER.fine("Fine message logged");
22 }
23 }
该示例读取了包含以下属性的属性文件:
属性文件
1 handlers=java.util.logging.ConsoleHandler
2 .level=ALL
3 java.util.logging.ConsoleHandler.level=ALL
4 java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
5 confLogger.level=ALL
如果运行以上代码,我们将获得以下结果:
1 Jun 08, 2014 5:23:25 PM com.javacodegeeks.corejava.util.logging.ConfigurationExample main
2 FINE: Fine message logged
让我们讨论该示例的代码和它所配置的属性。
handlers: 设置所有的logger使用默认的handler。
.level:设置所有logger的默认日志级别,值ALL表示开启所有级别的日志消息。
java.util.logging.ConsoleHandler.level: 设置所有ConsoleHandler 的默认日志级别为ALL,即对于ConsoleHandler开启所有级别的日志消息。
java.util.logging.ConsoleHandler.formatter: 设置 ConsoleHandler 的默认格式为SimpleFormatter.
confLogger.level: 设置名称为confLogger的默认级别为开启所有的级别的日志消息。
需要注意的是这些属性都可以在代码中被覆盖。
LogManager.getLogManager():该方法是一个静态方法,用于获取LogManager对象。该LogManager对象是全局的,用来维护一组共享状态和日志服务。
void readConfiguration(InputStream ins):该方法被用来重新初始化日志记录的属性,通过从stream中重新读取日志配置,该配置应该符合
java.util.Properties
的格式
6.Download the source code
你可以从这里下载这个示例的源代码:LoggingExample.zip
失败是成功之母。
failure is mother of success.
失敗は成功の母。