在程序中我们常常用System.out.println来输出调试信息,方便且简单,但是往往是因为它太简单、太方便了,以致于让我们需要用一个更强大更灵活的方法来替代它,这篇文章的目的就是提供了一个这样的方法。
在我的开发过程中,我会对输出的调试信息有这样的期望:在开发的时候,输出大量的细节信息让我能很方便的调试,而到了发布的时候,不用改任何代码,就能让程序只去显示客户会感兴趣的信息;同样不需要改动代码,就能让调试信息输出到屏幕、文件、甚至套接字里;除了可以输出调试信息外,还可以输出这条调试信息所在的类和行数能让我很快的定位。
package org.kyle.util;
import java.util.logging.*;
import java.util.Date;
import java.io.IOException;
import java.text.SimpleDateFormat;
public class Debug
{
private static Logger logger;
/**
* handler stores three handle, console, file and socket.
*/
private static Handler[] handler = new Handler[3];
/**
* specify the console logger.
*/
public static final int CONSOLE = 0;
/**
* specify the file logger.
*/
public static final int FILE = 1;
/**
* specify the socket logger.
*/
public static final int SOCKET = 2;
/**
* Return a handler's logging Level.
* @param hIdx must be one of CONSOLE,FILE or SOCKET.
* @return Level the Level of designated Handler. null if the index is invalid.
*/
public static Level getLevel(int hIdx)
{
switch(hIdx)
{
case CONSOLE:
case FILE:
case SOCKET:
return handler[hIdx].getLevel();
default:
return null;
}
}
/**
* show the finest message. You can print any unused message such as "i=5". We never give
* these messages to customers. And we seldom rely finest messages to check errors. Just feel
* free to use finest at any code line when you are in programing. Note: finest messges may
* be removed by any others without prior inform.
*/
public static void finest(Object obj)
{
if( obj instanceof Throwable)
{
logger.finest(getExceptionStack((Throwable)obj));
}
else
logger.finest((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* show the finer message. These messages are minor useful than fine.
*/
public static void finer(Object obj)
{
if( obj instanceof Throwable)
{
logger.finer(getExceptionStack((Throwable)obj));
}
else
logger.finer((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* show the fine message. A lot of correct operation can be written down. For example, receive
* a RAW pdu from remote computer, receive or send some message from/to internet. Fine level is
* only for inner use. Detail messages which could help developper find out problems. When we
* shipped the products to customer, developper could use fine or finer level for remote debugging.
*/
public static void fine(Object obj)
{
if( obj instanceof Throwable)
{
logger.fine(getExceptionStack((Throwable)obj));
}
else
logger.fine((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* show the config message. Some correct operation but worth to record. Such as an administrator
* changes some environment, logged on or off, system startup or receive some commands
* from authentication server, etc.
* Detail log such as a successful call or shutdown should use fine level.
*/
public static void config(Object obj)
{
if( obj instanceof Throwable)
{
logger.config(getExceptionStack((Throwable)obj));
}
else
logger.config((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* show the info message. Some important message should be print out. For example, register fails
* or call fails.
*/
public static void info(Object obj)
{
if( obj instanceof Throwable)
{
logger.info(getExceptionStack((Throwable)obj));
}
else
logger.info((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* show the warning message. Most error message such socket lost or connection fail should use this method.
* If the socket lost, some operate may can not continue.
* Only ocaasional errors can be print out by this methods. If we can't resolve a problem and it happens
* frequently, we use info instead of warning.
* Waring level is the default level in the final release when this product is shipped to customers.
* While if customers wants to see logs, we show them Config level.
* Developpers should be careful with the spelling or grammer errors in level bigger than Config level.
*/
public static void warning(Object obj)
{
if( obj instanceof Throwable)
{
logger.warning(getExceptionStack((Throwable)obj));
}
else
logger.warning((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* show the severe message, which may cause system exit or cause error. RuntimeException is the
* most common severe message. All severe messages must be resolved by administrator or developper.
*/
public static void severe(Object obj)
{
if( obj instanceof Throwable)
{
logger.severe(getExceptionStack((Throwable)obj));
}
else
logger.severe((obj == null ? "Logging message object is null" : obj.toString() ));
}
/**
* add a console Handler. You can remove it by using removeHandler();
* @see #removeHandler
*/
public static void addConsoleHandler()
{
if (handler[CONSOLE] != null) return;
handler[CONSOLE] = new ConsoleHandler();
addhandler(handler[CONSOLE]);
}
/**
* add a file Handler. You can remove it by using removeHandler();
* @param filename The log file name.
* @see #removeHandler
*/
public static void addFileHandler(String filename)
{
if (handler[FILE] != null) return;
try{
handler[FILE] = new FileHandler(filename, 500000, 1000, true);
addhandler(handler[FILE]);
}catch(IOException ioe){
warning("Can't open log file: " + filename);
}
}
/**
* add a socket Handler. You can remove it by using removeHandler();
* @param host The remote log server address.
* @param port The remote log server port.
* @see #removeHandler
*/
public static void addSocketHandler(String host, int port)
{
if (handler[SOCKET] != null) return;
try{
handler[SOCKET] = new SocketHandler(host, port);
addhandler(handler[SOCKET]);
}catch(IOException ioe){
warning("Can't connect to log socket: " + host + ":" + port);
}
}
private static void addhandler(Handler handler)
{
handler.setFormatter(new ComplexFormatter());
logger.addHandler(handler);
adjustLoggerLevel();
}
/**
* remove Handler. You can add a handler also.
* @see #addConsoleHandler()
* @see #addFileHandler
* @see #addSocketHandler
*/
public static void removeHandler(int handler_index)
{
if (handler[handler_index] == null) return;
logger.removeHandler(handler[handler_index]);
handler[handler_index] = null;
adjustLoggerLevel();
}
/**
* set handler log level.
* @param handler_index The log handler type.
* @param level The log handler level.
* @see #CONSOLE
* @see #FILE
* @see #SOCKET
* @see Level
*/
public static void setLevel(int handler_index, Level level)
{
if (handler[handler_index] == null) return;
handler[handler_index].setLevel(level);
adjustLoggerLevel();
}
public static void setFormatter(int handler_idx, Formatter nFormatter)
{
try
{
if(handler[handler_idx] == null) return;
handler[handler_idx].setFormatter(nFormatter);
}
catch(Exception e)
{
finest(e);
}
}
private static void adjustLoggerLevel()
{
Level logLevel = Level.OFF;
for (int i=0; i
{
if (handler[i] != null && handler[i].getLevel().intValue() < logLevel.intValue())
logLevel = handler[i].getLevel();
}
logger.setLevel(logLevel);
}
/**
* enable or disable handler log trace.
* @param handler_index The log handler type.
* @param trace enable/disable trace.
* @see #CONSOLE
* @see #FILE
* @see #SOCKET
*/
public static void setTrace(int handler_index, boolean trace)
{
if (handler[handler_index] != null)
((ComplexFormatter)handler[handler_index].getFormatter()).m_trace = trace;
}
/**
* enable or disable handler log time.
* @param handler_index The log handler type.
* @param time enable/disable time.
* @see #CONSOLE
* @see #FILE
* @see #SOCKET
*/
public static void setTime(int handler_index, boolean time)
{
if (handler[handler_index] != null)
((ComplexFormatter)handler[handler_index].getFormatter()).m_time = time;
}
/**
* enable or disable handler log tip.
* @param handler_index The log handler type.
* @param tip enable/disable tip.
* @see #CONSOLE
* @see #FILE
* @see #SOCKET
*/
public static void setTip(int handler_index, boolean tip)
{
if (handler[handler_index] != null)
((ComplexFormatter)handler[handler_index].getFormatter()).m_tip = tip;
}
static private class ComplexFormatter extends Formatter
{
private boolean m_trace = false;
private boolean m_time = false;
private boolean m_tip = false;
public ComplexFormatter()
{
}
public String format(LogRecord record)
{
StringBuffer message = new StringBuffer();
if (m_time)
message.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(new Date(record.getMillis())));
if (m_tip)
message.append(record.getLevel().toString() + ":");
message.append(formatMessage(record));
message.append("\n");
if (m_trace)
{
Throwable e = new Throwable();
StackTraceElement[] elements = e.getStackTrace();
String srcClass = record.getSourceClassName();
StringBuffer buf = new StringBuffer();
int i=0;
while (!srcClass.equals(elements[i].getClassName())) i++;
for (int j= i+1; j
buf.append("At: "+elements[j].toString()+"\n");
message.append(buf);
}
return message.toString();
}
}
public static String getExceptionStack(Throwable e)
{
StackTraceElement[] elements = e.getStackTrace();
StringBuffer buf = new StringBuffer(e.toString()+"\n");
int i=0;
for (int j= 0; j
buf.append("At: "+elements[j].toString()+"\n");
return buf.toString();
}
static
{
logger = Logger.getAnonymousLogger();
logger.setUseParentHandlers(false);
handler[CONSOLE] = new ConsoleHandler();
handler[CONSOLE].setFormatter(new ComplexFormatter());
logger.addHandler(handler[CONSOLE]);
}
public static void main(String[] arg) throws Exception
{
setLevel(Debug.CONSOLE, Level.ALL);
addConsoleHandler();
addFileHandler("ab");
setLevel(Debug.FILE, Level.FINE);
addSocketHandler("localhost", 200);
setLevel(Debug.SOCKET, Level.CONFIG);
removeHandler(Debug.FILE);
setTime(Debug.CONSOLE, true);
setTip(Debug.CONSOLE, false);
setTrace(Debug.CONSOLE, false);
finest("finest");
finer("finer");
fine("fine");
config("config");
info("info");
warning("warning");
severe("severe");
}
}
注:当用setLevel把Level设为CONFIG时,就只有比CONFIG级别高的INFO,WARNING,SERVERE会输出。关于Level的详细信息,可参考JDK文档->java.util.logging->Class Level。
建议:
severe: 非常严重且不能自行恢复的错误,每个独立的线程最外层至少要捕获,RuntimeException异常可以用severe输出,因为这类错误必须在开发阶段保证不会出现。
warning: 严重的错误,可能会导致程序处理不正确,只有不频繁发生的错误才可以用warning输出。经常性的错误应该在程序中自动处理,如服务器连接不上等情况,应该用低一级的输出。
info, config: 一些应当提示的重要信息,如启动、停止一些主要服务,不成功或异常的连接等。
fine,finer,finest: 调试信息,其中fine显示一些有用的信息。
另外一些注意事项:
1 severe,warning,info,config四级尽量避免使用fail,exception等词语,且应当保证语法、拼写正确。这几级是可能被客户看到的。如 "connection lost"可以用"connection stopped"这样中性的词语代替。
2不要用Exception.printStackTrace()函数,如需要调试可以调用Debug.fine(Exception)或Debug.finest(Exception)。
3任何情况下,不要提交会导致刷屏的输出调试信息,如每读到一个数据包就显示一次。不要仅仅降低输出级别,而要永久注释掉。很低的输出级别,也会占用CPU时间。刷屏的输出会严重影响性能。