/* * Log.java - A class for logging events * :tabSize=4:indentSize=4: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. */ package org.gjt.sp.util; //{{{ Imports import java.awt.Toolkit; 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.<p> * * 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.<p> * * Logging of exception tracebacks is supported.<p> * * This class can also optionally redirect standard output and error * to the log, see {@link #init}. * * @author Slava Pestov * @version $Id: Log.java 23221 2013-09-29 20:03:32Z shlomy $ */ public class Log { //{{{ Constants /** * The maximum number of log messages that will be kept in memory. * @since jEdit 2.6pre5 */ public static 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 * intercepted and sent to the log. The <code>urgency</code> * for these implicit log entries is <code>NOTICE</code> * and <code>ERROR</code> accordingly. Note that in such a situation * <code>System.out.print()</code> calls will not appear on standard * output, if default output level is higher than <code>NOTICE</code>. * @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.vm.name", "java.runtime.version", "java.runtime.name", "java.vendor", "java.compiler", "os.name", "os.version", "os.arch", "user.home", "java.home", "java.class.path", }; for (String prop : props) { log(MESSAGE, Log.class, prop + '=' + System.getProperty(prop)); } } //}}} //{{{ 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; } //}}} //{{{ get/setBeepOnOutput method /** * @since jEdit 5.1pre1 */ public static boolean getBeepOnOutput() { return beepOnOutput; } /** * When <code>beepOnOutput</code> is set, every output going to standard * error is signaled by a standard beep. This is intended for debugging * purposes, to allow for immediate problem detection. * @since jEdit 5.1pre1 */ public static void setBeepOnOutput(boolean beepOnOutput) { Log.beepOnOutput = beepOnOutput; } //}}} //{{{ 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.<p> * * 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; // initialized externally through setBeepOnOutput method private static boolean beepOnOutput = false; // to prevent too much beeping we remember last beep time private static long lastBeepTime = 0; //}}} //{{{ 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); if (beepOnOutput) { long time = System.currentTimeMillis(); if (time - lastBeepTime > 1000) { Toolkit.getDefaultToolkit().beep(); lastBeepTime = System.currentTimeMillis(); } } } } //}}} //{{{ 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 (ListDataListener listener : listeners) { listener.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index1, index2)); } } //}}} //{{{ fireIntervalRemoved() method private void fireIntervalRemoved(int index1, int index2) { for (ListDataListener listener : listeners) { 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 String 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); } //}}} } //}}} }