enoeht的Java源码系列(6)--调试信息与日志文件

来源:互联网 发布:敌在本能寺什么梗 知乎 编辑:程序博客网 时间:2024/06/11 19:50

在程序中我们常常用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时间。刷屏的输出会严重影响性能。