- 记录日志API的优点:
- 容易取消全部日志或者某个级别的日志,打开和关闭的操作都容易。
- 日志代码留在程序中的开销很小。
- 可定向到不同的处理器(控制台显示、存储于文件)。
- 日志记录器和处理器都可以对记录进行过滤。
- 日志记录可以采用不同的方式格式化,如:纯文本或XML。
- 应用程序可以使用多个日志记录器,它们使用类似包名的层次结构的名字。
- 默认,日志系统的配置由配置文件控制。如需要,应用程序可替换这个配置。
- 基本日志:
- 使用 全局日志记录器(global logger)并调用info方法。
- Logger.getGlobal().info("...");
- 调用 Logger.getGlobal().setLevel( Level.OFF ) 将会取消所有的日志。
- 高级日志:
- 企业级日志。
- 可以调用 getLogger方法 创建或获取记录器。
- private static final Logger myLogger = Logger.getLogger("com.company.myapp");
- 未被任何变量引用的日志记录器可能会被垃圾回收。故,需用一个静态变量存储日志记录器的一个引用。
- 通常有七个日志记录级别:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
- 默认只记录前三个级别。也可以设置其他级别。
- 记录方法:
- logger.warning(message)
- logger.fine(message)
- logger.log( Level.FINE, message)
- 调用logp方法获得类和方法的确切位置。
- entering方法 和 exiting方法:生成 FINER级别和以字符串 ENTRY 和 RETURN 开始的日志记录。
- 日志的 常见用途 是记录那些不可预料的异常。
- void throwing( String className, String methodName, Throwable t ) //记录FINER级别以THROW开始的信息。
- void log( Level l, String message, Throwable t )
- 修改日志管理器配置:
- 默认情况下,配置文件存在于: jre/lib/logging.properties 。
- 若想使用另一个配置文件,就要将 java.util.logging.config.file 特性设置为配置文件的存储位置,并用下列命令启动应用程序:java - Djava.util.logging.config.file=configFile MainClass 。
- 要想修改默认的日志记录级别或者控制台处理器显示级别,需要编辑配置文件。
- 处理器:
- 默认情况下,日志记录器将记录发送到ConsoleHandler中,并由它输出到 System.err流 中。日志记录器还会将记录发送到父处理器中。我们的日志记录器是原始日志记录器(命名为“”)的子类。
- 处理器也有日志记录级别。对于一个要被记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阈值。
- 默认控制台处理器的日志记录级别为INFO,可通过修改配置文件更改。
- 还可以绕过配置文件,安装自己的处理器:
Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
- 日志API提供了两个很有用的处理器: FileHandler 和 SocketHandler 。SocketHandler 将记录发送到特定的主机和端口。FileHandler 可以收集文件中的记录。
- 过滤器:
- 默认,过滤器根据日志记录的级别进行过滤。
- 每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。
- 可以通过实现 Filter接口 并定义 boolean isLoggable( LogRecord record ) 方法来自定义过滤器。返回true表示这些记录应该包含在日志中。
- 要将一个过滤器安装到一个日志记录器或者处理器中,只需要调用setFilter方法就可以了,同一时刻最多只能有一个过滤器。
- 格式化器:
- ConsoleHandler 类 和 FileHandler 类可以生成文本和XML格式的日志记录。也可以自定义格式。
- 扩展 Formatter类并覆盖 String format( LogRecord record ) 方法。
- String formatMessage( LogRecord record )
- String getHead( Handler h )
- String getTail( Handler h )
- setFormatter方法 将格式化器安装到处理器中。
- 日志记录说明(最常用的操作):
- 为一个简单的应用程序,选择一个日志记录器,并把日志记录器命名为与与主应用程序包一样的名字。
- 改变配置需要做相当多的工作,最好在应用程序中安装一个更加适宜的默认配置。
- 所有级别为INFO、WARNING 和 SEVERE 的消息都将显示到控制台上。故,最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为FINE是一个很好的选择。
//下列代码确保将所有的消息记录到应用程序特定的文件中
if( System.getProperty("java.util.logging.config.class") == null
&& System.getProperty("java.util.logging.config.file") == null )
{
try
{
Logger.getLogger("").setLevel(Level.ALL);
final int LOG_ROTATION_COUNT = 10;
Handler handler = new FileHandler( "%h/myapp.log", 0, LOG_ROTATION_COUNT );
Logger.getLogger("").addHandler(handler);
}
catch( IOException e )
{
logger.log( Level.SEVERE, "cant create log file handler", e );
}
}
- java.util.logging.Logger
- java.util.logging.Handler
- java.util.logging.ConsoleHandler
- java.util.logging.FileHandler
- java.util.logging.LogRecord
- java.util.logging.Filter
- java.util.logging.Formatter
- 示例程序(#纯copy,测试日志功能,帮助理解)
package logging;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.logging.*;
import javax.swing.*;
public class LoggingImageViewer {
public static void main( String[] args ) {
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 not create log file handler", e );
}
}
EventQueue.invokeLater( () ->
{
Handler windowHandler = new WindowHandler();
windowHandler.setLevel(Level.ALL);
Logger.getLogger("com.horstmann.corejava").addHandler(windowHandler);
JFrame frame = new ImageViewerFrame();
frame.setTitle("LoggingImageViewer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Logger.getLogger("com.horstmann.corejava").fine("Showing frame");
frame.setVisible(true);
});
}
}
class ImageViewerFrame extends JFrame{
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 400;
private JLabel label;
private static Logger logger = Logger.getLogger("com.horstmann.corejava");
public ImageViewerFrame() {
logger.entering("ImageViewerFrame", "<init>");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu menu = new JMenu("File");
menuBar.add(menu);
JMenuItem openItem = new JMenuItem("Open");
menu.add(openItem);
openItem.addActionListener( new FileOpenListener() );
JMenuItem exitItem = new JMenuItem("Exit");
menu.add(exitItem);
exitItem.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent event ) {
logger.fine("Exiting.");
System.exit(0);
}
});
label = new JLabel();
add(label);
logger.exiting("ImageViewerFrame", "<init>");
}
private class FileOpenListener implements ActionListener{
public void actionPerformed( ActionEvent event ) {
logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", event );
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("."));
chooser.setFileFilter( new javax.swing.filechooser.FileFilter()
{
public boolean accept(File f) {
return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory();
}
public String getDescription() {
return "GIF Images";
}
});
int r = chooser.showOpenDialog(ImageViewerFrame.this);
if( r == JFileChooser.APPROVE_OPTION ) {
String name = chooser.getSelectedFile().getPath();
logger.log(Level.FINE, "Reading file {0}", name );
label.setIcon( new ImageIcon(name) );
}else logger.fine("File open dialog canceled.");
logger.exiting("ImageViewerFrame", "actionPerformed");
}
}
}
class WindowHandler extends StreamHandler{
private JFrame frame;
public WindowHandler() {
frame = new JFrame();
final JTextArea output = new JTextArea();
output.setEditable(false);
frame.setSize(200,200);
frame.add(new JScrollPane(output) );
frame.setFocusableWindowState(false);
frame.setVisible(true);
setOutputStream( new OutputStream()
{
public void write( int b ) { }
public void write( byte[] b, int off, int len ) {
output.append( new String(b, off, len) );
}
});
}
public void publish( LogRecord record ) {
if( !frame.isVisible() ) return;
super.publish(record);
flush();
}
}
- 调试技巧:
- 可以用下面的方法打印或记录任意变量的值:
- System.out.println("x=" + x);
- Logger.getGlobal().info("x="+x);
- 每个类都放置一个单独的main方法,就可以对每个类进行单元测试。
- JUint 是一个非常常见的单元测试框架。
- 日志代理(logging proxy)是一个子类的对象,截获方法调用并进行日志记录,然后调用超类的方法,例子(每次调用nextDouble方法时,就会产生一个消息日志):
Random generator = new Random()
{
public double nextDouble(){
double result = super.nextDouble();
Logger.getGlobal().info("nextDouble: " + result);
return result;
}
};
- 利用Throwable类提供的printStackTrace方法,可以从任一个异常对象中获得堆栈情况。
- 要观察类的加载过程,可以用 -verbose 标志启动Java虚拟机。有时候,这种诊断方法有助于诊断由于类路径引发的问题。
- -Xlint 选项:
- -Xlint 或 -Xlint:all 执行所有的检查
- -Xlint:fallthrough 与-deprecation一样,检查废弃的方法
- -Xlint:finally 警告finally子句不能正常地执行
- -Xlint:none 不执行任何检查
- -Xlint:path 检查类路径和源代码路径上的所有目录是否存在
- -Xlint:serial 警告没有 serialVersionUID 的串行化类
- -Xlint:unchecked 对通用类型与原始类型之间的危险转换给与警告
- Java虚拟机增加了对Java应用程序进行 监控 和 管理 的支持。跟踪 内存消耗、线程使用、类加载等 情况。jconsole processID 。
- 可以使用 jmap实用工具获得一个堆的转储,其中显示了堆中的每个对象。
- ...