1.日志的概念
在调试有问题的代码时,经常需要插入一些System.out.println方法来观察程序运行的操作过程。但是,一旦发现了问题并且解决了问题,就需要将这些System.out.println语句从代码中删除或者注释。如果接下来又出现了问题,就还要再插入几个System.out.println方法。日志API可以很好地解决这个问题。
日志API的优点:
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
- 可以很简单地禁止日志的输出,因此,将这些日志代码留在程序中的开销很小。
- 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现其指定的标准丢弃无用的记录项。
- 日志记录可以采用不同的方式格式化,例如,纯文本或XML。
- 应用程序可以使用多个日志记录器,它们使用类似包名的具有层次结构的名字。
- 在默认情况下,日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换配置文件。
2.基本日志
使用全局日志记录器(global logger)并调用info方法可以输出指定的信息。
输出有两行信息,第一行是调用日志方法的时间戳,包名,类名,方法名,第二行是用户指定的info信息。
package packageName; import java.util.logging.Logger; public class ClassName { public static void main(String[] args) { int x = 100; Logger.getGlobal().info("x的值为: " + x); } } 输出: 七月 20, 2018 9:11:20 上午 packageName.ClassName main 信息: x的值为: 100
同时,使用其setLevel方法可以很容易地取消日志的显示。
package packageName; import java.util.logging.Level; import java.util.logging.Logger; public class ClassName { public static void main(String[] args) { int x = 100; Logger.getGlobal().setLevel(Level.OFF); Logger.getGlobal().info("x的值为: " + x); } } 输出:
全局日志记录就是所有的日志都记录到一个日志记录器中:
package packageName; import java.util.logging.Logger; public class ClassName { public static void main(String[] args) { int x = 100; Logger.getGlobal().info("x的值为: " + x); int y = 101; Logger.getGlobal().info("y的值为: " + y); } } 输出: 七月 20, 2018 9:32:16 上午 packageName.ClassName main 信息: x的值为: 100 七月 20, 2018 9:32:16 上午 packageName.ClassName main 信息: y的值为: 101
3.高级日志
在一个专业的应用程序中,不要把所有的日志都记录到一个全局日志记录器中,而是可以自定义多个记录器。
使用Logger.getLogger方法来生成指定记录器名称的记录器,例如,下面的语句定义了两个日志记录器,日志记录器的名字之间有着很强的关联性,iplab就是school的子记录器。未被任何变量引用的日志记录器可能会被垃圾回收,为了防止这种情况,用一个静态变量存储日志记录器的引用。
private static Logger logger = Logger.getLogger("com.school"); private static Logger logger = Logger.getLogger("com.school.iplab");
如果对父记录器设置了日志级别,那么它的子记录器也会继承这个级别,级别一共有七种,SEVERE、WARNING、INFO、CONFIFG、FINE、FINER、FINEST在默认的情况下,只记录前三个级别,如果设置FINE,则FINE和更高的级别都可以记录下来。另外,还可以使用Level.ALL开启所有级别,以及Level.OFF关闭所有级别。
logger.setLevel(Level.FINE);
前面的info就是制定了日志级别,将记录信息指定成不同的级别可以使用下面的方法
logger.warning(message);
logger.fine(message);
logger.log(Level.FINE, message);
例如:由于main方法本身就是静态方法,所以logger就已经是静态的了。
package packageName; import java.util.logging.Logger; public class ClassName { public static void main(String[] args) { Logger logger = Logger.getLogger("com.school.iplab"); int x = 100; logger.warning("The value of x is " + x); } }
输出:
七月 20, 2018 9:56:01 上午 packageName.ClassName main
警告: The value of x is 100
默认的日志记录将显示包含日志调用的类名和方法名,为了得到调用类和方法的确切位置,可以使用logp方法,指定日志记录器所在的包名和方法名以及记录信息。
package packageName; import java.util.logging.Level; import java.util.logging.Logger; public class ClassName { public static void main(String[] args) { Logger logger = Logger.getLogger("com.school.iplab"); int x = 100; logger.logp(Level.INFO, "包名", "方法名", "记录信息x=" + x); } }
输出:
七月 20, 2018 10:06:49 上午 包名 方法名
信息: 记录信息x=100
还可以跟踪执行流的方法,例如:entering方法记录一个进入read方法的日志,exiting方法记录一个退出read方法的日志。
int read(String file, String pattern) { Logger logger = Logger.getLogger("com.school.iplab"); logger.entering("packageName.ClassName", "read", new Object[] {file, pattern}); ... logger.exiting("packageName.ClassName", "read", count); return count; }
日志常见的用途值记录那些不可预料的异常,典型的用法是:
- 在try-catch语句块中,用来记录捕捉到的异常对象的日志。
- 在if语句中,用来记录抛出的异常对象的日志。
try {
... } catch (IOException e) { Logger.getLogger("com.horstmann.corejava").log(Level.SEVERE, "Can't create log file handler", e); }
if(...)
{
IOException exception = new IOException("...");
logger.throwing("类名", "方法名", exception);
throw exception;
}
4.修改日志管理器的配置
可以在jre/lib/logging.properties中修改配置文件,使得指定自己的日志记录级别:
com.xyz.foo.level = SEVERE
5.日志处理器
(1)ConsoleHandler处理器
在默认的情况下,日志记录器将记录发送到ConsoleHandler日志处理器中,并由它输出到System.err流中。另外,日志记录器还会将记录发送到父处理器中。与日志记录器一样,日志处理器也有日志记录级别,对于一个要被记录的日志,它的日志记录级别必须高于日志记录器和日志处理器的阈值。
配置文件中日志处理器的日志级别为:
java.util.logging.ConsoleHandler.level = INFO
要想记录比INFO低级别的日志,例如FINE级别的日志,就必须要修改配置文件的默认日志记录器级别和处理器级别,除了这个方法,还可以自己定义自己的处理器:步骤就是,首先创建一个日志记录器logger并设置日志记录器的记录级别,然后由于原始日志记录器将会把所有等于或高于INFO级别的记录发送到控制台,为了不让原始日志记录器发送而只让自己定义的日志记录器发送记录到控制台,调用setUseParentHandlers方法并传递参数false,然后创建自己的日志处理器,并将处理器的处理级别设置为FINE,最后将日志记录器发送到定义的日志处理器。
package packageName;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ClassName {
public static void main(String[] args) {
Logger logger = Logger.getLogger("com.school.iplab");
logger.info("info0");
logger.fine("fine0");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
logger.info("info1");
logger.fine("fine1");
}
}
输出:
七月 20, 2018 11:08:34 上午 packageName.ClassName main
信息: info0
七月 20, 2018 11:08:34 上午 packageName.ClassName main
信息: info1
七月 20, 2018 11:08:34 上午 packageName.ClassName main
详细: fine1
(2)FileHandler处理器用来本地化
package packageName; import java.io.IOException; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; public class ClassName { public static void main(String[] args) throws SecurityException, IOException { Logger logger = Logger.getLogger("com.school.iplab"); logger.info("info0"); logger.fine("fine0"); logger.setLevel(Level.FINE); logger.setUseParentHandlers(false); Handler handler = new FileHandler("myapp.log"); handler.setLevel(Level.FINE); logger.addHandler(handler); logger.info("info1"); logger.fine("fine1"); } }
在项目的文件夹下会生成一个myapp.log文件,文件内容是XML格式的:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE log SYSTEM "logger.dtd"> <log> <record> <date>2018-07-20T11:23:21</date> <millis>1532057001808</millis> <sequence>1</sequence> <logger>com.school.iplab</logger> <level>INFO</level> <class>packageName.ClassName</class> <method>main</method> <thread>1</thread> <message>info1</message> </record> <record> <date>2018-07-20T11:23:21</date> <millis>1532057001808</millis> <sequence>2</sequence> <logger>com.school.iplab</logger> <level>FINE</level> <class>packageName.ClassName</class> <method>main</method> <thread>1</thread> <message>fine1</message> </record> </log>
6.过滤器
在默认情况下,过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。
7.格式化器
ConsoleHandler类和FileHandler类可以生成文本和XML格式的日志记录。但是也可以自定义格式。继承Formatter类并使用setFormatter方法将格式化器安装到处理器中即可。
8.日志总结
(1)在创建应用程序的日志记录器时,最好和主程序的包名一致,为了方便起见,最好设置成静态域:
private static Logger logger = Logger.getLogger(包名);
(2)默认的日志配置文件将级别等于或高于INFO级别的所有日志消息输出到控制台,用户可以覆盖默认的配置文件,但是最好还是安装一个更加适合的自己定义的日志处理器,下面的代码确保将所有的消息记录到应用程序特定的文件中。
if (System.getProperty("java.util.logging.config.class") == null && System.getProperty("java.util.logging.config.file") == null) { try { Logger.getLogger("com.horstmann.corejava").setLevel(Level.ALL); final int LOG_ROTATION_COUNT = 10; Handler handler = new FileHandler("%h/LoggingImageViewer.log", 0, LOG_ROTATION_COUNT); Logger.getLogger("com.horstmann.corejava").addHandler(handler); } catch (IOException e) { Logger.getLogger("com.horstmann.corejava").log(Level.SEVERE, "Can't create log file handler", e); } }
(3)必须牢记的是,如果没有修改默认配置,那么所有级别为INFO、WARNING和SEVERE的消息都将被记录,因此最好只将对程序用户有意义的消息设置为这几个级别,将程序员想要的日志记录,设置为低级别的FINE是一个很好的选择。
(4)还要牢记的是,日志最大的用处是来记录异常,可以记录捕获的异常对象和抛出的异常对象。