Mega Code Archive

 
Categories / Java Tutorial / Development
 

Methods for logging events

/*  * Log.java - A class for logging events  * :tabSize=8:indentSize=8:noTabs=false:  * :folding=explicit:collapseFolds=1:  *  * Copyright (C) 1999, 2003 Slava Pestov  *  * This program is free software; you can redistribute it and/or  * modify it under the terms of the GNU General Public License  * as published by the Free Software Foundation; either version 2  * of the License, or any later version.  *  * This program is distributed in the hope that it will be useful,  * but WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  * GNU General Public License for more details.  *  * You should have received a copy of the GNU General Public License  * along with this program; if not, write to the Free Software  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.  */ //{{{ Imports import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.Writer; import java.text.DateFormat; import java.util.*; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.ListModel; import javax.swing.SwingUtilities; import static java.text.DateFormat.MEDIUM; //}}} /**  * This class provides methods for logging events. In terms of functionality,  * it is somewhere in between <code>System.out.println()</code> and  * full-blown logging packages such as log4j.  *  * All events are logged to an in-memory buffer and optionally a stream,  * and those with a high urgency (warnings and errors) are also printed  * to standard output.  *  * Logging of exception tracebacks is supported.  *  * This class can also optionally redirect standard output and error to the log.  *  * @author Slava Pestov  * @version $Id: Log.java 12789 2008-06-04 21:23:10Z kpouer $  */ public class Log {   //{{{ Constants   /**    * The maximum number of log messages that will be kept in memory.    * @since jEdit 2.6pre5    */   public static final int MAXLINES = 500;   /**    * Debugging message urgency. Should be used for messages only    * useful when debugging a problem.    * @since jEdit 2.2pre2    */   public static final int DEBUG = 1;   /**    * Message urgency. Should be used for messages which give more    * detail than notices.    * @since jEdit 2.2pre2    */   public static final int MESSAGE = 3;   /**    * Notice urgency. Should be used for messages that directly    * affect the user.    * @since jEdit 2.2pre2    */   public static final int NOTICE = 5;   /**    * Warning urgency. Should be used for messages that warrant    * attention.    * @since jEdit 2.2pre2    */   public static final int WARNING = 7;   /**    * Error urgency. Should be used for messages that signal a    * failure.    * @since jEdit 2.2pre2    */   public static final int ERROR = 9;   //}}}   //{{{ init() method   /**    * Initializes the log.    * @param stdio If true, standard output and error will be    * sent to the log    * @param level Messages with this log level or higher will    * be printed to the system console    * @since jEdit 3.2pre4    */   public static void init(boolean stdio, int level)   {     if(stdio)     {       if(System.out == realOut && System.err == realErr)       {         System.setOut(createPrintStream(NOTICE,null));         System.setErr(createPrintStream(ERROR,null));       }     }     Log.level = level;     // Log some stuff     log(MESSAGE,Log.class,"When reporting bugs, please"       + " include the following information:");     String[] props = {       "java.version", "java.vm.version", "java.runtime.version",       "java.vendor", "java.compiler", "os.name", "os.version",       "os.arch", "user.home", "java.home",       "java.class.path",       };     for(int i = 0; i < props.length; i++)     {       log(MESSAGE,Log.class,         props[i] + '=' + System.getProperty(props[i]));     }   } //}}}   //{{{ setLogWriter() method   /**    * Writes all currently logged messages to this stream if there was no    * stream set previously, and sets the stream to write future log    * messages to.    * @param stream The writer    * @since jEdit 3.2pre4    */   public static void setLogWriter(Writer stream)   {     if(Log.stream == null && stream != null)     {       try       {         if(wrap)         {           for(int i = logLineCount; i < log.length; i++)           {             stream.write(log[i]);             stream.write(lineSep);           }         }         for(int i = 0; i < logLineCount; i++)         {           stream.write(log[i]);           stream.write(lineSep);         }         stream.flush();       }       catch(Exception e)       {         // do nothing, who cares       }     }     Log.stream = stream;   } //}}}   //{{{ flushStream() method   /**    * Flushes the log stream.    * @since jEdit 2.6pre5    */   public static void flushStream()   {     if(stream != null)     {       try       {         stream.flush();       }       catch(IOException io)       {         io.printStackTrace(realErr);       }     }   } //}}}   //{{{ closeStream() method   /**    * Closes the log stream. Should be done before your program exits.    * @since jEdit 2.6pre5    */   public static void closeStream()   {     if(stream != null)     {       try       {         stream.close();         stream = null;       }       catch(IOException io)       {         io.printStackTrace(realErr);       }     }   } //}}}   //{{{ getLogListModel() method   /**    * Returns the list model for viewing the log contents.    * @since jEdit 4.2pre1    */   public static ListModel getLogListModel()   {     return listModel;   } //}}}   //{{{ log() method   /**    * Logs an exception with a message.    *    * If an exception is the cause of a call to {@link #log}, then    * the exception should be explicitly provided so that it can    * be presented to the (debugging) user in a useful manner    * (not just the exception message, but also the exception stack trace)    *    * @since jEdit 4.3pre5    */   public static void log(int urgency, Object source, Object message,     Throwable exception)   {     // We can do nicer here, but this is a start...     log(urgency,source,message);     log(urgency,source,exception);   } //}}}   //{{{ log() method   /**    * Logs a message. This method is thread-safe.    *    * The following code sends a typical debugging message to the activity    * log:    * <pre>Log.log(Log.DEBUG,this,"counter = " + counter);</pre>    * The corresponding activity log entry might read as follows:    * <pre>[debug] JavaParser: counter = 15</pre>    *    * @param urgency The urgency; can be one of    * <code>Log.DEBUG</code>, <code>Log.MESSAGE</code>,    * <code>Log.NOTICE</code>, <code>Log.WARNING</code>, or    * <code>Log.ERROR</code>.    * @param source The source of the message, either an object or a    * class instance. When writing log messages from macros, set    * this parameter to <code>BeanShell.class</code> to make macro    * errors easier to spot in the activity log.    * @param message The message. This can either be a string or    * an exception    *    * @since jEdit 2.2pre2    */   public static void log(int urgency, Object source, Object message)   {     String _source;     if(source == null)     {       _source = Thread.currentThread().getName();       if(_source == null)       {         _source = Thread.currentThread().getClass().getName();       }     }     else if(source instanceof Class)       _source = ((Class)source).getName();     else       _source = source.getClass().getName();     int index = _source.lastIndexOf('.');     if(index != -1)       _source = _source.substring(index+1);     if(message instanceof Throwable)     {       _logException(urgency,source,(Throwable)message);     }     else     {       String _message = String.valueOf(message);       // If multiple threads log stuff, we don't want       // the output to get mixed up       synchronized(LOCK)       {         StringTokenizer st = new StringTokenizer(           _message,"\r\n");         int lineCount = 0;         boolean oldWrap = wrap;         while(st.hasMoreTokens())         {           lineCount++;           _log(urgency,_source,st.nextToken()             .replace('\t',' '));         }         listModel.update(lineCount,oldWrap);       }     }   } //}}}   //{{{ Private members   //{{{ Instance variables   private static final Object LOCK;   private static final String[] log;   private static int logLineCount;   private static boolean wrap;   private static int level;   private static Writer stream;   private static final String lineSep;   private static final PrintStream realOut;   private static final PrintStream realErr;   private static final LogListModel listModel;   private static final DateFormat timeFormat;   private static final int MAX_THROWABLES = 10;   public static final List<Throwable> throwables;   //}}}   //{{{ Class initializer   static   {     LOCK = new Object();     level = WARNING;     realOut = System.out;     realErr = System.err;     log = new String[MAXLINES];     lineSep = System.getProperty("line.separator");     listModel = new LogListModel();          timeFormat = DateFormat.getTimeInstance(MEDIUM);     throwables = Collections.synchronizedList(new ArrayList<Throwable>(MAX_THROWABLES));   } //}}}   //{{{ createPrintStream() method   private static PrintStream createPrintStream(final int urgency,     final Object source)   {     return new LogPrintStream(urgency, source);   } //}}}   //{{{ _logException() method   private static void _logException(final int urgency,     final Object source,     final Throwable message)   {     PrintStream out = createPrintStream(urgency,source);     if (urgency >= level)     {       synchronized (throwables)       {         if (throwables.size() == MAX_THROWABLES)         {           throwables.remove(0);         }         throwables.add(message);       }     }     synchronized(LOCK)     {       message.printStackTrace(out);     }   } //}}}   //{{{ _log() method   private static void _log(int urgency, String source, String message)   {     String fullMessage = timeFormat.format(new Date()) + " ["+Thread.currentThread().getName()+"] [" + urgencyToString(urgency) + "] " + source       + ": " + message;     try     {       log[logLineCount] = fullMessage;       if(++logLineCount >= log.length)       {         wrap = true;         logLineCount = 0;       }       if(stream != null)       {         stream.write(fullMessage);         stream.write(lineSep);       }     }     catch(Exception e)     {       e.printStackTrace(realErr);     }     if(urgency >= level)     {       if(urgency == ERROR)         realErr.println(fullMessage);       else         realOut.println(fullMessage);     }   } //}}}   //{{{ urgencyToString() method   private static String urgencyToString(int urgency)   {     switch(urgency)     {     case DEBUG:       return "debug";     case MESSAGE:       return "message";     case NOTICE:       return "notice";     case WARNING:       return "warning";     case ERROR:       return "error";     }     throw new IllegalArgumentException("Invalid urgency: " + urgency);   } //}}}   //}}}   //{{{ LogListModel class   static class LogListModel implements ListModel   {     final List<ListDataListener> listeners = new ArrayList<ListDataListener>();     //{{{ fireIntervalAdded() method     private void fireIntervalAdded(int index1, int index2)     {       for(int i = 0; i < listeners.size(); i++)       {         ListDataListener listener = listeners.get(i);         listener.intervalAdded(new ListDataEvent(this,           ListDataEvent.INTERVAL_ADDED,           index1,index2));       }     } //}}}     //{{{ fireIntervalRemoved() method     private void fireIntervalRemoved(int index1, int index2)     {       for(int i = 0; i < listeners.size(); i++)       {         ListDataListener listener = listeners.get(i);         listener.intervalRemoved(new ListDataEvent(this,           ListDataEvent.INTERVAL_REMOVED,           index1,index2));       }     } //}}}     //{{{ addListDataListener() method     public void addListDataListener(ListDataListener listener)     {       listeners.add(listener);     } //}}}     //{{{ removeListDataListener() method     public void removeListDataListener(ListDataListener listener)     {       listeners.remove(listener);     } //}}}     //{{{ getElementAt() method     public Object getElementAt(int index)     {       if(wrap)       {         if(index < MAXLINES - logLineCount)           return log[index + logLineCount];         else           return log[index - MAXLINES + logLineCount];       }       else         return log[index];     } //}}}     //{{{ getSize() method     public int getSize()     {       if(wrap)         return MAXLINES;       else         return logLineCount;     } //}}}     //{{{ update() method     void update(final int lineCount, final boolean oldWrap)     {       if(lineCount == 0 || listeners.isEmpty())         return;       SwingUtilities.invokeLater(new Runnable()       {         public void run()         {           if(wrap)           {             if(oldWrap)               fireIntervalRemoved(0,lineCount - 1);             else             {               fireIntervalRemoved(0,                 logLineCount);             }             fireIntervalAdded(               MAXLINES - lineCount + 1,               MAXLINES);           }           else           {             fireIntervalAdded(               logLineCount - lineCount + 1,               logLineCount);           }         }       });     } //}}}   } //}}}   //{{{ LogPrintStream class   /**    * A print stream that uses the "Log" class to output the messages,    * and has special treatment for the printf() function. Using this    * stream has one caveat: printing messages that don't have a line    * break at the end will have one added automatically...    */   private static class LogPrintStream extends PrintStream {     private final ByteArrayOutputStream buffer;     private final OutputStream orig;     //{{{ LogPrintStream constructor     LogPrintStream(int urgency, Object source)     {       super(new LogOutputStream(urgency, source));       buffer = new ByteArrayOutputStream();       orig = out;     } //}}}     //{{{ printf() method     /**      * This is a hack to allow "printf" to not print weird      * stuff to the output. Since "printf" doesn't seem to      * print the whole message in one shot, our output      * stream above would break a line of log into several      * lines; so we buffer the result of the printf call and      * print the whole thing in one shot. A similar hack      * would be needed for the "other" printf method, but      * I'll settle for the common case only.      */     public PrintStream printf(String format, Object... args)     {       synchronized (orig)       {         buffer.reset();         out = buffer;         super.printf(format, args);         try         {           byte[] data = buffer.toByteArray();           orig.write(data, 0, data.length);           out = orig;         }         catch (IOException ioe)         {           // don't do anything?         }         finally         {           buffer.reset();         }       }       return this;     } //}}}   } //}}}   //{{{ LogOutputStream class   private static class LogOutputStream extends OutputStream   {     private final int   urgency;     private final Object  source;     //{{{ LogOutputStream constructor     LogOutputStream(int urgency, Object source)     {       this.urgency  = urgency;       this.source   = source;     } //}}}     //{{{ write() method     public synchronized void write(int b)     {       byte[] barray = { (byte)b };       write(barray,0,1);     } //}}}     //{{{ write() method     public synchronized void write(byte[] b, int off, int len)     {       String str = new String(b,off,len);       log(urgency,source,str);     } //}}}   } //}}} }