设计日志API主要是为了给像servlet, applet, EJB,等java应用,产生对于终端用户
系统管理员、领域工程师和软件开发者提供他们感兴趣的信息。
尤其在软件产品中,程序不能在调试器中运行,或者在调试器中运行时掩盖了一些在实际运行时出现的问题(例如与时间相关的问题). 因此对于程序来说,日志文件通常是一个包含其运行信息的一大资源。
简而言之,日志记录了那些不可预料的异常,可供软件开发与维护的相关人员分析,来更好的调整和改善系统。
为了确保在软件产品中使用日志,java的这些API尽量使记录日志经济可行,且不降低软件正常使用时速度,这些API都具有以下几个特点:
1)可以动态地调整记录的内容,打开、取消或者关闭日志记录
2)可以被定向到不同处理器,以不同形式处理记录,例如输出到控制台,写入到文件
3)可以过滤日志记录,也可以将日志记录格式化为不同格式,例如文本或者xml
4)可以灵活的配置日志系统,可以使用多个日志记录器,允许定制个性化记录方式
简而言之,日志API的目标就是要满足: 功能齐全,灵活使用,效率高。
java记录器,有7个记录级别,如下表所示。在默认情况下只有前三个级别,可以通过:setLevel函数设置。
注意,记录器级别有一个特点,只记录当前级别及以上级别的记录。
也就是说,logger.setLevel(Level.FINE)的话,只有FINE和更高级别的记录被记录下来,其余被忽视。另外还有两个辅助级别OFF用来关闭日志记录, ALL则表示记录一切内容。
Logging levels (降序排列) |
|
SEVERE |
The highest value;intended for extremely important messages (e.g. fatal programerrors). (严重,最高级别) |
WARNING |
Intended for warningmessages. |
INFO |
Informational runtimemessages. |
CONFIG |
Informational messagesabout configuration settings/setup. |
FINE |
Used for greater detail,when debugging/diagnosing problems. |
FINER |
Even greater detail. |
FINEST |
The lowest value;greatest detail. (最低级别) |
(表格来源:http://www.onjava.com/pub/a/onjava/2002/06/19/log.html?page=1)
日志记录器(Logger)可以对应多个处理器(Handler),每个处理器接受日志消息并按自己的方式处理它。
日志记录器与处理器的关系如下图所示:
Handler的继承关系如下:
java.util.logging.Handler
java.util.logging.MemoryHandler
java.util.logging.StreamHandler
java.util.logging.ConsoleHandler
java.util.logging.FileHandler
java.util.logging.SocketHandler
注意,对于一个要被记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阀值。
默认情况下,日志记录器会将记录发送给自己的处理器和父处理器,最终的处理器命名为""(即空串),它有一个ConsoleHandler。可以使用SetUseParentHandlers(boolean flag)来设置是否启用父处理器。
内置的处理器ConsoleHandler和FileHandler类可以生成文本和XML格式的日志记录,但是也可以自定义日志格式。
通过继承java.util.logging.Formatter类并覆盖
String format(LogRecord record)
方法来按照自己定义方式对信息进行格式化。另外可能会调用
String formatMessage(LogRecord record)
方法来完成LogRecord格式化,或者需要在格式化后文件提供额外头部时使用
String getHead(Handler h)
String getTail(Handler h)
方法。具体参见案例代码部分。
每个日志记录器和处理器可以有一个可选的过滤器来完成附加的过滤,另外也可以通过Filter接口自定义过滤器。
该接口只有一个方法:
boolean isLoggable(LogRecord record) 用来筛选那些信息需要被记录。
注意,同一时刻只能有一个过滤器。
LogManager用来创建和管理记录器,并维护配置文件(可以通过配置文件,修改日志记录器的级别等)。
日志管理器控制着那些内容被记录,在哪里记录。通过
LogManager.setLevel(String name, Level level) 方法
它可以设置一个包或一系列包的记录级别。
例如: LogManager.getLogManager().setLevel("com.learningjava", Level.FINE);设置了com.learningjava包中的每个记录器被设置为FINE级别。
下面的案例示例了自定义处理器,格式化器,过滤器的使用。
package com.learningjava;
import java.util.*;
import java.util.logging.*;
import java.util.logging.Formatter;
import java.io.*;
import java.text.SimpleDateFormat;
import javax.swing.*;
/**
* this program demonstrate using Log API
*/
public class LoggingDemo7 {
public static void main(String[] args) {
//use global logger
Logger logger = Logger.getGlobal();
logger.setLevel(Level.INFO);
logger.info("using assert ");
assert (logger.getLevel() == Level.INFO);
//user define logger
Logger myLogger = Logger.getLogger("com.learningjava");
myLogger.setLevel(Level.ALL);
try {
//using userdefine Window Handler
WindowHandler windowHandler = new WindowHandler();
windowHandler.setLevel(Level.ALL);
myLogger.addHandler(windowHandler);
//use simple fomatter in plain text
FileHandler fileTxt = new FileHandler("Logging.txt");
fileTxt.setFormatter(new SimpleFormatter());
fileTxt.setLevel(Level.INFO);
//use user define filter
fileTxt.setFilter(new MyWeekdayFilter());
myLogger.addHandler(fileTxt);
//user define formatter in html
FileHandler fileHTML = new FileHandler("Logging.html");
fileHTML.setFormatter(new MyHtmlFormatter());
fileHTML.setLevel(Level.ALL);
myLogger.addHandler(fileHTML);
//default formatter in xml
FileHandler fileXML = new FileHandler("Logging.xml");
fileTxt.setLevel(Level.ALL);
myLogger.addHandler(fileXML);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//use console handler
myLogger.setUseParentHandlers(true);
myLogger.info("out of main");
}
}
/**
* custom Handler : a window Handler
* @author Cay s.Horstmann
*/
class WindowHandler extends StreamHandler {
public WindowHandler() {
frame = new JFrame("Viewing Log");
final JTextArea output= new JTextArea();
output.setEditable(false);
frame.setSize(300, 200);
frame.add(new JScrollPane(output));
frame.setFocusableWindowState(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
this.setOutputStream(new OutputStream(){
@Override
public void write(int b) throws IOException {
}
@Override
public void write(byte[] b, int off, int len) {
output.append(new String(b,off,len));
}
});
}
@Override
public void publish(LogRecord record) {
if(!frame.isVisible()) return;
super.publish(record);
flush();
}
private JFrame frame;
}
/**
* Custom formatter : in html
* @author Lars Vogel
*/
class MyHtmlFormatter extends Formatter {
// This method is called for every log records
public String format(LogRecord rec) {
StringBuffer buf = new StringBuffer(1000);
buf.append("");
buf.append("");
if (rec.getLevel().intValue() >= Level.WARNING.intValue()) {
buf.append("");// Bold any levels >= WARNING
buf.append(rec.getLevel());
buf.append("");
} else {
buf.append(rec.getLevel());
}
buf.append(" ");
buf.append("");
buf.append(calcDate(rec.getMillis()));
buf.append('\n');
buf.append(" ");
buf.append("");
buf.append(' ');
buf.append(formatMessage(rec));//correct it
buf.append(" ");
buf.append(" \n");
return buf.toString();
}
private String calcDate(long millisecs) {
SimpleDateFormat date_format = new SimpleDateFormat("MMM dd,yyyy HH:mm");
Date resultdate = new Date(millisecs);
return date_format.format(resultdate);
}
/**
* This method is called just after the handler using this
* formatter is created
*/
public String getHead(Handler h) {
return "\n\n" + (new Date())
+ "\n\n\n\n"
+ "
\n "
+ "Level " +
"Time " +
"Log Message " +
" \n";
}
/**
* This method is called just after the handler using this
* formatter is closed
*/
public String getTail(Handler h) {
return "
\n
\n\n";
}
}
/**
* a filter only log weekday record
* @author wangdq
*/
class MyWeekdayFilter implements Filter {
@Override
public boolean isLoggable(LogRecord record) {
//get time form record
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis(record.getMillis());
int weekday = cal.get(Calendar.DAY_OF_WEEK);
//check if it is weekend
if(weekday == Calendar.SATURDAY || weekday == Calendar.SUNDAY) {
return false;
} else {
return true;
}
}
}
1)java API规范建议使用类的全名来构造记录器,例如:
Logger LOGGER = Logger.getLogger(MyClass.class .getName());
2)SocketHandler可以将日志记录发往制定地址的log服务器,
需要配置服务器,在这里未列出。
3)日志记录还有关于权限的问题,未作介绍。
4)日志记录还有关于本地化部分,未作介绍。
[1]:Java Logging API - Tutorial
http://www.vogella.com/articles/Logging/article.html#general_manager
[2]:An Introduction to the Java Logging API
http://www.onjava.com/pub/a/onjava/2002/06/19/log.html?page=2
[3]:《java核心技术:卷一》