001: /*
002: * Log.java - A class for logging events
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1999, 2003 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.util;
024:
025: //{{{ Imports
026: import java.io.ByteArrayOutputStream;
027: import java.io.IOException;
028: import java.io.OutputStream;
029: import java.io.PrintStream;
030: import java.io.Writer;
031:
032: import java.text.DateFormat;
033:
034: import java.util.ArrayList;
035: import java.util.Date;
036: import java.util.List;
037: import java.util.StringTokenizer;
038:
039: import javax.swing.event.ListDataEvent;
040: import javax.swing.event.ListDataListener;
041:
042: import javax.swing.ListModel;
043: import javax.swing.SwingUtilities;
044:
045: import static java.text.DateFormat.MEDIUM;
046:
047: //}}}
048:
049: /**
050: * This class provides methods for logging events. In terms of functionality,
051: * it is somewhere in between <code>System.out.println()</code> and
052: * full-blown logging packages such as log4j.<p>
053: *
054: * All events are logged to an in-memory buffer and optionally a stream,
055: * and those with a high urgency (warnings and errors) are also printed
056: * to standard output.<p>
057: *
058: * Logging of exception tracebacks is supported.<p>
059: *
060: * This class can also optionally redirect standard output and error to the log.
061: *
062: * @author Slava Pestov
063: * @version $Id: Log.java 9135 2007-03-13 08:01:11Z Vampire0 $
064: */
065: public class Log {
066: //{{{ Constants
067: /**
068: * The maximum number of log messages that will be kept in memory.
069: * @since jEdit 2.6pre5
070: */
071: public static final int MAXLINES = 500;
072:
073: /**
074: * Debugging message urgency. Should be used for messages only
075: * useful when debugging a problem.
076: * @since jEdit 2.2pre2
077: */
078: public static final int DEBUG = 1;
079:
080: /**
081: * Message urgency. Should be used for messages which give more
082: * detail than notices.
083: * @since jEdit 2.2pre2
084: */
085: public static final int MESSAGE = 3;
086:
087: /**
088: * Notice urgency. Should be used for messages that directly
089: * affect the user.
090: * @since jEdit 2.2pre2
091: */
092: public static final int NOTICE = 5;
093:
094: /**
095: * Warning urgency. Should be used for messages that warrant
096: * attention.
097: * @since jEdit 2.2pre2
098: */
099: public static final int WARNING = 7;
100:
101: /**
102: * Error urgency. Should be used for messages that signal a
103: * failure.
104: * @since jEdit 2.2pre2
105: */
106: public static final int ERROR = 9;
107:
108: //}}}
109:
110: //{{{ init() method
111: /**
112: * Initializes the log.
113: * @param stdio If true, standard output and error will be
114: * sent to the log
115: * @param level Messages with this log level or higher will
116: * be printed to the system console
117: * @since jEdit 3.2pre4
118: */
119: public static void init(boolean stdio, int level) {
120: if (stdio) {
121: if (System.out == realOut && System.err == realErr) {
122: System.setOut(createPrintStream(NOTICE, null));
123: System.setErr(createPrintStream(ERROR, null));
124: }
125: }
126:
127: Log.level = level;
128:
129: // Log some stuff
130: log(MESSAGE, Log.class, "When reporting bugs, please"
131: + " include the following information:");
132: String[] props = { "java.version", "java.vm.version",
133: "java.runtime.version", "java.vendor", "java.compiler",
134: "os.name", "os.version", "os.arch", "user.home",
135: "java.home", "java.class.path", };
136: for (int i = 0; i < props.length; i++) {
137: log(MESSAGE, Log.class, props[i] + '='
138: + System.getProperty(props[i]));
139: }
140: } //}}}
141:
142: //{{{ setLogWriter() method
143: /**
144: * Writes all currently logged messages to this stream if there was no
145: * stream set previously, and sets the stream to write future log
146: * messages to.
147: * @param stream The writer
148: * @since jEdit 3.2pre4
149: */
150: public static void setLogWriter(Writer stream) {
151: if (Log.stream == null && stream != null) {
152: try {
153: if (wrap) {
154: for (int i = logLineCount; i < log.length; i++) {
155: stream.write(log[i]);
156: stream.write(lineSep);
157: }
158: }
159: for (int i = 0; i < logLineCount; i++) {
160: stream.write(log[i]);
161: stream.write(lineSep);
162: }
163:
164: stream.flush();
165: } catch (Exception e) {
166: // do nothing, who cares
167: }
168: }
169:
170: Log.stream = stream;
171: } //}}}
172:
173: //{{{ flushStream() method
174: /**
175: * Flushes the log stream.
176: * @since jEdit 2.6pre5
177: */
178: public static void flushStream() {
179: if (stream != null) {
180: try {
181: stream.flush();
182: } catch (IOException io) {
183: io.printStackTrace(realErr);
184: }
185: }
186: } //}}}
187:
188: //{{{ closeStream() method
189: /**
190: * Closes the log stream. Should be done before your program exits.
191: * @since jEdit 2.6pre5
192: */
193: public static void closeStream() {
194: if (stream != null) {
195: try {
196: stream.close();
197: stream = null;
198: } catch (IOException io) {
199: io.printStackTrace(realErr);
200: }
201: }
202: } //}}}
203:
204: //{{{ getLogListModel() method
205: /**
206: * Returns the list model for viewing the log contents.
207: * @since jEdit 4.2pre1
208: */
209: public static ListModel getLogListModel() {
210: return listModel;
211: } //}}}
212:
213: //{{{ log() method
214: /**
215: * Logs an exception with a message.
216: *
217: * If an exception is the cause of a call to {@link #log}, then
218: * the exception should be explicitly provided so that it can
219: * be presented to the (debugging) user in a useful manner
220: * (not just the exception message, but also the exception stack trace)
221: *
222: * @since jEdit 4.3pre5
223: */
224: public static void log(int urgency, Object source, Object message,
225: Throwable exception) {
226: // We can do nicer here, but this is a start...
227: log(urgency, source, message);
228: log(urgency, source, exception);
229: } //}}}
230:
231: //{{{ log() method
232: /**
233: * Logs a message. This method is thread-safe.<p>
234: *
235: * The following code sends a typical debugging message to the activity
236: * log:
237: * <pre>Log.log(Log.DEBUG,this,"counter = " + counter);</pre>
238: * The corresponding activity log entry might read as follows:
239: * <pre>[debug] JavaParser: counter = 15</pre>
240: *
241: * @param urgency The urgency; can be one of
242: * <code>Log.DEBUG</code>, <code>Log.MESSAGE</code>,
243: * <code>Log.NOTICE</code>, <code>Log.WARNING</code>, or
244: * <code>Log.ERROR</code>.
245: * @param source The source of the message, either an object or a
246: * class instance. When writing log messages from macros, set
247: * this parameter to <code>BeanShell.class</code> to make macro
248: * errors easier to spot in the activity log.
249: * @param message The message. This can either be a string or
250: * an exception
251: *
252: * @since jEdit 2.2pre2
253: */
254: public static void log(int urgency, Object source, Object message) {
255: String _source;
256: if (source == null) {
257: _source = Thread.currentThread().getName();
258: if (_source == null) {
259: _source = Thread.currentThread().getClass().getName();
260: }
261: } else if (source instanceof Class)
262: _source = ((Class) source).getName();
263: else
264: _source = source.getClass().getName();
265: int index = _source.lastIndexOf('.');
266: if (index != -1)
267: _source = _source.substring(index + 1);
268:
269: if (message instanceof Throwable) {
270: _logException(urgency, source, (Throwable) message);
271: } else {
272: String _message = String.valueOf(message);
273: // If multiple threads log stuff, we don't want
274: // the output to get mixed up
275: synchronized (LOCK) {
276: StringTokenizer st = new StringTokenizer(_message,
277: "\r\n");
278: int lineCount = 0;
279: boolean oldWrap = wrap;
280: while (st.hasMoreTokens()) {
281: lineCount++;
282: _log(urgency, _source, st.nextToken().replace('\t',
283: ' '));
284: }
285: listModel.update(lineCount, oldWrap);
286: }
287: }
288: } //}}}
289:
290: //{{{ Private members
291:
292: //{{{ Instance variables
293: private static final Object LOCK;
294: private static final String[] log;
295: private static int logLineCount;
296: private static boolean wrap;
297: private static int level;
298: private static Writer stream;
299: private static final String lineSep;
300: private static final PrintStream realOut;
301: private static final PrintStream realErr;
302: private static final LogListModel listModel;
303: private static final DateFormat timeFormat;
304: //}}}
305:
306: //{{{ Class initializer
307: static {
308: LOCK = new Object();
309: level = WARNING;
310:
311: realOut = System.out;
312: realErr = System.err;
313:
314: log = new String[MAXLINES];
315: lineSep = System.getProperty("line.separator");
316: listModel = new LogListModel();
317:
318: timeFormat = DateFormat.getTimeInstance(MEDIUM);
319: } //}}}
320:
321: //{{{ createPrintStream() method
322: private static PrintStream createPrintStream(final int urgency,
323: final Object source) {
324: return new LogPrintStream(urgency, source);
325: } //}}}
326:
327: //{{{ _logException() method
328: private static void _logException(final int urgency,
329: final Object source, final Throwable message) {
330: PrintStream out = createPrintStream(urgency, source);
331:
332: synchronized (LOCK) {
333: message.printStackTrace(out);
334: }
335: } //}}}
336:
337: //{{{ _log() method
338: private static void _log(int urgency, String source, String message) {
339: String fullMessage = timeFormat.format(new Date()) + " ["
340: + urgencyToString(urgency) + "] " + source + ": "
341: + message;
342:
343: try {
344: log[logLineCount] = fullMessage;
345: if (++logLineCount >= log.length) {
346: wrap = true;
347: logLineCount = 0;
348: }
349:
350: if (stream != null) {
351: stream.write(fullMessage);
352: stream.write(lineSep);
353: }
354: } catch (Exception e) {
355: e.printStackTrace(realErr);
356: }
357:
358: if (urgency >= level) {
359: if (urgency == ERROR)
360: realErr.println(fullMessage);
361: else
362: realOut.println(fullMessage);
363: }
364: } //}}}
365:
366: //{{{ urgencyToString() method
367: private static String urgencyToString(int urgency) {
368: switch (urgency) {
369: case DEBUG:
370: return "debug";
371: case MESSAGE:
372: return "message";
373: case NOTICE:
374: return "notice";
375: case WARNING:
376: return "warning";
377: case ERROR:
378: return "error";
379: }
380:
381: throw new IllegalArgumentException("Invalid urgency: "
382: + urgency);
383: } //}}}
384:
385: //}}}
386:
387: //{{{ LogListModel class
388: static class LogListModel implements ListModel {
389: final List<ListDataListener> listeners = new ArrayList<ListDataListener>();
390:
391: //{{{ fireIntervalAdded() method
392: private void fireIntervalAdded(int index1, int index2) {
393: for (int i = 0; i < listeners.size(); i++) {
394: ListDataListener listener = listeners.get(i);
395: listener.intervalAdded(new ListDataEvent(this ,
396: ListDataEvent.INTERVAL_ADDED, index1, index2));
397: }
398: } //}}}
399:
400: //{{{ fireIntervalRemoved() method
401: private void fireIntervalRemoved(int index1, int index2) {
402: for (int i = 0; i < listeners.size(); i++) {
403: ListDataListener listener = listeners.get(i);
404: listener
405: .intervalRemoved(new ListDataEvent(this ,
406: ListDataEvent.INTERVAL_REMOVED, index1,
407: index2));
408: }
409: } //}}}
410:
411: //{{{ addListDataListener() method
412: public void addListDataListener(ListDataListener listener) {
413: listeners.add(listener);
414: } //}}}
415:
416: //{{{ removeListDataListener() method
417: public void removeListDataListener(ListDataListener listener) {
418: listeners.remove(listener);
419: } //}}}
420:
421: //{{{ getElementAt() method
422: public Object getElementAt(int index) {
423: if (wrap) {
424: if (index < MAXLINES - logLineCount)
425: return log[index + logLineCount];
426: else
427: return log[index - MAXLINES + logLineCount];
428: } else
429: return log[index];
430: } //}}}
431:
432: //{{{ getSize() method
433: public int getSize() {
434: if (wrap)
435: return MAXLINES;
436: else
437: return logLineCount;
438: } //}}}
439:
440: //{{{ update() method
441: void update(final int lineCount, final boolean oldWrap) {
442: if (lineCount == 0 || listeners.isEmpty())
443: return;
444:
445: SwingUtilities.invokeLater(new Runnable() {
446: public void run() {
447: if (wrap) {
448: if (oldWrap)
449: fireIntervalRemoved(0, lineCount - 1);
450: else {
451: fireIntervalRemoved(0, logLineCount);
452: }
453: fireIntervalAdded(MAXLINES - lineCount + 1,
454: MAXLINES);
455: } else {
456: fireIntervalAdded(logLineCount - lineCount + 1,
457: logLineCount);
458: }
459: }
460: });
461: } //}}}
462: } //}}}
463:
464: //{{{ LogPrintStream class
465: /**
466: * A print stream that uses the "Log" class to output the messages,
467: * and has special treatment for the printf() function. Using this
468: * stream has one caveat: printing messages that don't have a line
469: * break at the end will have one added automatically...
470: */
471: private static class LogPrintStream extends PrintStream {
472:
473: private final ByteArrayOutputStream buffer;
474: private final OutputStream orig;
475:
476: //{{{ LogPrintStream constructor
477: LogPrintStream(int urgency, Object source) {
478: super (new LogOutputStream(urgency, source));
479: buffer = new ByteArrayOutputStream();
480: orig = out;
481: } //}}}
482:
483: //{{{ printf() method
484: /**
485: * This is a hack to allow "printf" to not print weird
486: * stuff to the output. Since "printf" doesn't seem to
487: * print the whole message in one shot, our output
488: * stream above would break a line of log into several
489: * lines; so we buffer the result of the printf call and
490: * print the whole thing in one shot. A similar hack
491: * would be needed for the "other" printf method, but
492: * I'll settle for the common case only.
493: */
494: public PrintStream printf(String format, Object... args) {
495: synchronized (orig) {
496: buffer.reset();
497: out = buffer;
498: super .printf(format, args);
499:
500: try {
501: byte[] data = buffer.toByteArray();
502: orig.write(data, 0, data.length);
503: out = orig;
504: } catch (IOException ioe) {
505: // don't do anything?
506: } finally {
507: buffer.reset();
508: }
509: }
510: return this ;
511: } //}}}
512: } //}}}
513:
514: //{{{ LogOutputStream class
515: private static class LogOutputStream extends OutputStream {
516: private final int urgency;
517: private final Object source;
518:
519: //{{{ LogOutputStream constructor
520: LogOutputStream(int urgency, Object source) {
521: this .urgency = urgency;
522: this .source = source;
523: } //}}}
524:
525: //{{{ write() method
526: public synchronized void write(int b) {
527: byte[] barray = { (byte) b };
528: write(barray, 0, 1);
529: } //}}}
530:
531: //{{{ write() method
532: public synchronized void write(byte[] b, int off, int len) {
533: String str = new String(b, off, len);
534: log(urgency, source, str);
535: } //}}}
536: } //}}}
537: }
|