001: // $Id: Log.java 1912 2006-07-24 13:19:15Z twall $
002: // Copyright (c) Oculus Technologies Corporation, all rights reserved
003: // ----------------------------------------------------------------------------
004: package abbot;
005:
006: import java.io.ByteArrayOutputStream;
007: import java.io.File;
008: import java.io.FileOutputStream;
009: import java.io.IOException;
010: import java.io.OutputStream;
011: import java.io.PrintStream;
012: import java.lang.reflect.InvocationTargetException;
013: import java.lang.reflect.UndeclaredThrowableException;
014: import java.util.ArrayList;
015: import java.util.Date;
016: import java.util.HashMap;
017: import java.util.HashSet;
018: import java.util.Locale;
019: import java.util.Map;
020: import java.util.Set;
021: import java.util.Vector;
022: import abbot.util.Tee;
023:
024: /**
025: Various logging, assertion, and debug routines. Typical usage is to
026: include the following code
027: <blockquote><code><pre>
028: public static void main(String[] args) {
029: args = Log.{@link Log#init(String[]) init}(args)
030: ...
031: }
032: </pre></code></blockquote>
033: at an application's main entry point. This way the Log class can remove its
034: options from the full set passed into the application. See the
035: {@link #init(String[]) Log.init} method for initialization options. <p>
036:
037: General usage notes on public functions:<p>
038:
039: <ul>
040: <li>{@link #warn(String)}<br>
041: Programmer warnings; things that you think shouldn't be happening or
042: indicate something might be wrong. Warnings typically mean "Something
043: happened that I didn't expect would happen".<p>
044: <li>{@link #log(String)}<br>
045: Important information that might be needed for later reference; things the
046: user or debugger might be interested in. By default, all messages go
047: here. Logs are made available so that the customer may provide us with an
048: accurate record of software activity.<br>
049: All warnings and failed assertions are written to the log. Debug
050: statements are also written to log in non-release code.<p>
051: <li>{@link #debug(String)}<br>
052: Any messages which might be useful for debugging.
053: </ul>
054: <p>
055:
056: Per-class stack trace depth can be specified when adding a class, e.g.
057: classname[:stack-depth].<p>
058:
059: Extraction of the stack trace and logging to file are performed on a
060: separate thread to minimize performance impact due to filesystem delays.
061: Use {@link #setSynchronous(boolean) setSynchronous(false)} if you want
062: the output to be asynchronous with program execution.<p>
063:
064: @author twall@users.sf.net
065: */
066: public class Log {
067: /** No instantiations. */
068: protected Log() {
069: }
070:
071: private static final int LOG = 0x0001;
072: private static final int WARN = 0x0002;
073: private static final int DEBUG = 0x0004;
074:
075: private static final class StdErrTee extends Tee {
076: private StdErrTee(PrintStream p1) {
077: super (STDERR, p1);
078: }
079:
080: public String toString() {
081: return "<stderr> and log";
082: }
083: }
084:
085: private static final class StdOutTee extends Tee {
086: private StdOutTee(PrintStream p1) {
087: super (STDOUT, p1);
088: }
089:
090: public String toString() {
091: return "<stdout> and log";
092: }
093: }
094:
095: private static class Context extends Throwable {
096: public Throwable thrown;
097: public int type;
098:
099: public Context(int type) {
100: this .type = type;
101: }
102:
103: public Context(int type, Throwable t) {
104: this (type);
105: this .thrown = t;
106: }
107: }
108:
109: private static final String NL = System
110: .getProperty("line.separator");
111:
112: /** Mnemonic to print all lines of a stack trace. */
113: public static final int FULL_STACK = 0;
114: /** No stack, just a message. */
115: public static final int NO_STACK = -1;
116: /** Mnemonic to print the default number of lines of stack trace. */
117: private static final int CLASS_STACK_DEPTH = -2;
118: private static final String STDOUT_NAME = "<stdout>";
119: /** Basic warning categories. FIXME use these.
120: public static final int ERROR = 0x0001;
121: public static final int WARNING = 0x0002;
122: public static final int DEBUG = 0x0004;
123: public static final int INFO = 0x0008;*/
124:
125: /** Whether log output is synchronous */
126: private static boolean synchronous = true;
127: /** Whether to log messages. Default on so that we capture output until
128: * the log file has been set or not set in <code>#init()</code>.
129: */
130: private static boolean logMessages = true;
131: /** Whether to send anything to the console. */
132: private static boolean echoToConsole = true;
133: private static final PrintStream STDOUT = System.out;
134: private static final PrintStream STDERR = System.err;
135: /** Whether to show threads in debug output. */
136: private static boolean showThreads = false;
137: /** Default number of lines of stack trace to print. */
138: private static int debugStackDepth;
139: /** Default number of lines of stack trace to log. */
140: private static int logStackDepth;
141: /** Default number of lines of exception stack trace to print. */
142: private static int excStackDepth;
143: /** Show timestamps in the log? */
144: private static boolean showTimestamp = true;
145: private static java.text.DateFormat timestampFormat = new java.text.SimpleDateFormat(
146: "yyMMdd HH:mm:ss:SSS");
147:
148: /** Strip this out of output, since it doesn't add information to see it
149: repeatedly. */
150: private static final String COMMON_PREFIX = "com.oculustech.DOME.client";
151: private static final boolean ECLIPSE = System.getProperty(
152: "java.class.path").indexOf("eclipse") != -1;
153: /** Store which class names we want to see debug info for. */
154: private static Map debugged = new HashMap();
155: /** Store which class names we don't want to see debug info for */
156: private static Set notdebugged = new HashSet();
157: /** Debug all classes? */
158: private static boolean debugAll;
159: /** Treat inner/anonymous classes as outer class? */
160: private static boolean debugInner = true;
161:
162: private static final String DEFAULT_LOGFILE_NAME = "co-log.txt";
163: private static ByteArrayOutputStream preInitLog = new ByteArrayOutputStream();
164: private static PrintStream logStream;
165: /** Stream which ensures a copy goes to stdout */
166: private static PrintStream debugStream;
167: /** Stream which ensures a copy goes to stderr */
168: private static PrintStream warnStream;
169: private static LogThread logThread;
170: private static String logFilename;
171: private static boolean showWarnings = true;
172: private static PrintStream BUFFER = new PrintStream(preInitLog);
173: //private static PrintStream STDOUT_AND_BUFFER = new Tee(STDOUT, BUFFER);
174:
175: static {
176: setDestination(BUFFER);
177: logThread = new LogThread();
178: logThread.start();
179:
180: debugStackDepth = Integer.getInteger("co.debug_stack_depth", 1)
181: .intValue();
182: logStackDepth = Integer.getInteger("co.log_stack_depth",
183: NO_STACK).intValue();
184: excStackDepth = Integer.getInteger("co.exception_stack_depth",
185: FULL_STACK).intValue();
186:
187: // Make sure the log gets closed on System.exit
188: Runtime.getRuntime().addShutdownHook(
189: new Thread("Log shutdown hook") {
190: public void run() {
191: close();
192: }
193: });
194: }
195:
196: /** Debug/log initialization, presumably from the command line.
197: <br>Recognized options:
198: <pre>
199: --debug all | className[:depth] | *.partialClassName[:depth]
200: --no-debug className | *.partialClassName
201: --log <log file name>
202: --no-timestamp
203: --enable-warnings
204: --show-threads
205: --stack-depth <depth>
206: --exception-depth <depth>
207: </pre>
208: */
209: public static String[] init(String[] args) {
210: logMessages = false;
211: ArrayList newArgs = new ArrayList();
212: for (int i = 0; i < args.length; i++) {
213: if (args[i].equals("--enable-warnings")) {
214: showWarnings = true;
215: setEchoToConsole(true);
216: } else if (args[i].equals("--no-timestamp")) {
217: showTimestamp = false;
218: } else if (args[i].equals("--show-threads")) {
219: showThreads = true;
220: } else if (args[i].equals("--keep-console")) {
221: setEchoToConsole(true);
222: } else if (args[i].equals("--stack-depth")) {
223: if (++i < args.length) {
224: try {
225: debugStackDepth = Integer.parseInt(args[i]);
226: } catch (Exception exc) {
227: }
228: } else {
229: warn("Ignoring --stack-depth with no argument");
230: }
231: } else if (args[i].equals("--exception-depth")) {
232: if (++i < args.length) {
233: try {
234: excStackDepth = Integer.parseInt(args[i]);
235: } catch (Exception exc) {
236: }
237: } else {
238: warn("Ignoring --exception-depth with no argument");
239: }
240: } else if (args[i].equals("--debug")
241: || args[i].equals("--no-debug")) {
242: if (++i < args.length) {
243: boolean exclude = args[i].startsWith("--no");
244: if (exclude)
245: removeDebugClass(args[i]);
246: else {
247: addDebugClass(args[i]);
248: setEchoToConsole(true);
249: }
250: } else {
251: warn("Ignoring " + args[i - 1]
252: + " with no argument");
253: }
254: } else if (args[i].equals("--log")) {
255: String filename = DEFAULT_LOGFILE_NAME;
256: if (++i < args.length) {
257: filename = args[i];
258: }
259: initLogging(filename);
260: } else {
261: newArgs.add(args[i]);
262: }
263: }
264:
265: return (String[]) newArgs.toArray(new String[newArgs.size()]);
266: }
267:
268: /** Is log output enabled? */
269: public static boolean loggingInitialized() {
270: return logMessages && logStream != null;
271: }
272:
273: private static String hostname = null;
274: private static final String DEFAULT_HOSTNAME = "unknown";
275:
276: public static String getHostName() {
277: if (hostname == null) {
278: try {
279: hostname = java.net.InetAddress.getLocalHost()
280: .getHostName();
281: } catch (java.net.UnknownHostException e) {
282: hostname = DEFAULT_HOSTNAME;
283: warn("Cannot get hostname, using " + hostname);
284: }
285: }
286: return hostname;
287: }
288:
289: public static String getLogFilename() {
290: return logFilename;
291: }
292:
293: public static PrintStream getLog() {
294: return logStream;
295: }
296:
297: /** Enable log output to the given file. A filename of "-" means stdout. */
298: public static void initLogging(String filename) {
299: PrintStream ps = STDOUT;
300: logFilename = STDOUT_NAME;
301: if (!"-".equals(filename) && filename != null) {
302: try {
303: ps = new PrintStream(new FileOutputStream(filename),
304: true);
305: logFilename = filename;
306: } catch (IOException e) {
307: STDERR.println("Unable to write to " + filename);
308: STDERR.println("Output will go to the console");
309: }
310: }
311:
312: setDestination(ps);
313:
314: log("Log started on " + getHostName() + " (directed to "
315: + logFilename + ")");
316: // Always insert the system information
317: Log.log(getSystemInfo());
318: }
319:
320: /** Enable log output to the given {@link PrintStream}. */
321: public static void setDestination(PrintStream ps) {
322: logMessages = true;
323: logStream = ps;
324: // If there's a log file, redirect stdout/stderr there
325: if (logStream == STDOUT) {
326: debugStream = logStream;
327: warnStream = STDERR;
328: } else if (logStream == BUFFER) {
329: debugStream = new StdOutTee(BUFFER);
330: warnStream = new StdErrTee(BUFFER);
331: } else {
332: if (preInitLog.size() > 0) {
333: ps.print(preInitLog.toString());
334: preInitLog.reset();
335: }
336: debugStream = new StdOutTee(logStream);
337: warnStream = new StdErrTee(logStream);
338: System.setErr(warnStream);
339: System.setOut(debugStream);
340: if (logFilename != null) {
341: File file = new File(logFilename);
342: STDOUT
343: .println("Output also captured in the log file at "
344: + file.getAbsolutePath());
345: }
346: }
347: setEchoToConsole(logStream != STDOUT);
348: }
349:
350: public static String getSystemInfo() {
351: Locale loc = Locale.getDefault();
352: return "System Details:" + NL + " java: "
353: + System.getProperty("java.vm.name") + " "
354: + System.getProperty("java.vm.version") + NL
355: + " os: " + System.getProperty("os.name") + " "
356: + System.getProperty("os.version") + " "
357: + System.getProperty("os.arch") + NL + " user.dir: "
358: + System.getProperty("user.dir") + NL + " locale: "
359: + loc.getDisplayName() + " " + "[" + loc.getLanguage()
360: + " " + loc.getCountry() + "]" + NL + "classpath: "
361: + System.getProperty("java.class.path");
362: }
363:
364: /** Sets the debug stack depth to the given amount */
365: public static void setDebugStackDepth(int depth) {
366: debugStackDepth = depth;
367: }
368:
369: /** Resets the lists of classes to debug and not debug to be
370: empty, and turns debugAll off.
371: */
372: public static void clearDebugClasses() {
373: debugged.clear();
374: debugAll = false;
375: notdebugged.clear();
376: }
377:
378: /** Indicate that the given class should NOT be debugged
379: (assuming --debug all) */
380: public static void removeDebugClass(String className) {
381: setClassDebugEnabled(className, false);
382: }
383:
384: /** Indicate that debug messages should be output for the given class. */
385: public static void addDebugClass(Class class1) {
386: addDebugClass(class1.getName());
387: }
388:
389: /** Indicate that debug messages should no longer be output for the given
390: * class.
391: */
392: public static void removeDebugClass(Class class1) {
393: removeDebugClass(class1.getName());
394: }
395:
396: /** Indicate the class name[:depth] to add to debug output. */
397: public static void addDebugClass(String className) {
398: if (className.indexOf(":") == -1)
399: addDebugClass(className, CLASS_STACK_DEPTH);
400: else
401: setClassDebugEnabled(className, true);
402: }
403:
404: /** Indicate that debug messages should be output for the given class. */
405: public static void addDebugClass(String className, int depth) {
406: setClassDebugEnabled(className + ":" + depth, true);
407: }
408:
409: /** Parse the given string, which may should be of the format
410: "class[:depth]" */
411: private static void setClassDebugEnabled(String id, boolean enable) {
412: int colon = id.indexOf(":");
413: String className = colon == -1 ? id : id.substring(0, colon);
414: if ("all".equals(className)) {
415: debugAll = enable;
416: if (enable) {
417: notdebugged.clear();
418: } else {
419: debugged.clear();
420: }
421: } else {
422: className = getFullClassName(className);
423: int depth = CLASS_STACK_DEPTH;
424: try {
425: depth = colon == -1 ? debugStackDepth : Integer
426: .parseInt(id.substring(colon + 1));
427: } catch (NumberFormatException nfe) {
428: }
429: if (enable) {
430: debugged.put(className, new Integer(depth));
431: notdebugged.remove(className);
432: debug("Debugging enabled for " + className + " ("
433: + depth + ")");
434: } else {
435: notdebugged.add(className);
436: debugged.remove(className);
437: debug("Debugging disabled for " + className);
438: }
439: }
440: }
441:
442: /** Returns class from given name/descriptor. Descriptor can be either a
443: fully qualified classname, or a classname beginning with *. with
444: client-specific package and classname following.
445: */
446: private static String getFullClassName(String className) {
447: if (COMMON_PREFIX != null && className.startsWith("*.")) {
448: className = COMMON_PREFIX + className.substring(1);
449: }
450: return className;
451: }
452:
453: /** Return the requested number of levels of stack trace, not including
454: this call. Returns the full stack trace if LINES is FULL_STACK.
455: Skip the first POP frames of the trace, which is for excluding the
456: innermost stack frames when debug functions make nested calls.
457: The outermost call of getStackTrace itself is always removed from the
458: trace. */
459: private static String getStackTrace(int pop, int lines) {
460: return getStackTrace(pop, lines, new Throwable("--debug--"));
461: }
462:
463: /** Return the requested number of levels of stack trace, not including
464: this call. Returns the full stack trace if LINES is FULL_STACK.
465: Skip the first POP frames of the trace, which is for excluding the
466: innermost stack frames when debug functions make nested calls.
467: The outermost call of getStackTrace itself is always removed from the
468: trace. Provide an exception to use for the stack trace,
469: rather than using the current program location.
470: */
471: private static String getStackTrace(int pop, int lines,
472: Throwable thr) {
473: if (lines != NO_STACK) {
474: String stack = getStackTrace(pop, thr);
475: if (lines == FULL_STACK)
476: return stack;
477: return trimStackTrace(stack, lines);
478: }
479: return "";
480: }
481:
482: /** Return the stack trace contained in the given Throwable.
483: Skip the first POP frames of the trace, which is for excluding the
484: innermost stack frames when debug functions make nested calls.
485: The outermost call of getStackTrace itself is always removed from the
486: trace.
487: */
488: private static String getStackTrace(int pop, Throwable thr) {
489: OutputStream os = new ByteArrayOutputStream();
490: PrintStream newStream = new PrintStream(os, true);
491: // OUCH! this is a serious performance hit!
492: thr.printStackTrace(newStream);
493: String trace = os.toString();
494:
495: // Pop off getStackTrace itself
496: // Skip over any calls to getStackTrace; the JIT sometimes puts a
497: // spurious entry, so don't just stop at the first one.
498: int getLoc = trace.lastIndexOf("getStackTrace");
499: int at = trace.indexOf("\tat ", getLoc);
500: if (at != -1)
501: trace = trace.substring(at + 3);
502: while (pop-- > 0) {
503: // pop off the calling function
504: at = trace.indexOf("\tat ");
505: if (at != -1)
506: trace = trace.substring(at + 3);
507: }
508:
509: return trace.trim();
510: }
511:
512: /** Trim the given trace to LINES levels. */
513: private static String trimStackTrace(String trace, int lines) {
514: // Keep just as many lines as were requested
515: int end = trace.indexOf(")") + 1;
516: boolean all = (lines == FULL_STACK);
517: while (all || --lines > 0) {
518: int index = trace.indexOf("\tat ", end);
519: if (index < 0)
520: break;
521: end = trace.indexOf(")", index) + 1;
522: }
523: return trace.substring(0, end);
524: }
525:
526: /** Return the class corresponding to the first line in the give stack
527: trace. Treat inner/anonymous classes as the enclosing class. */
528: // FIXME with JIT enabled, stack trace sometimes has spurious junk on the
529: // stack, which will indicate the wrong class...
530: private static String extractClass(String trace) {
531: int paren = trace.indexOf("(");
532: String tmp = paren == -1 ? trace : trace.substring(0, paren);
533: int mstart = tmp.lastIndexOf(".");
534: String cname = mstart == -1 ? tmp : tmp.substring(0, mstart);
535: cname = cname.trim();
536: if (debugInner) {
537: int sub = cname.indexOf("$");
538: if (sub != -1)
539: cname = cname.substring(0, sub).trim();
540: }
541:
542: return cname;
543: }
544:
545: public static boolean isClassDebugEnabled(Class cls) {
546: return isClassDebugEnabled(cls.getName());
547: }
548:
549: public static boolean isClassDebugEnabled(String className) {
550: return (debugAll || debugged.containsKey(className))
551: && !notdebugged.contains(className);
552: }
553:
554: static int getClassStackDepth(String cname) {
555: Integer depth = (Integer) debugged.get(cname);
556: if (depth != null && depth.intValue() != CLASS_STACK_DEPTH)
557: return depth.intValue();
558: return debugStackDepth;
559: }
560:
561: /** Print a debug message. */
562: public static void debug(String event) {
563: internalDebug(event, new Context(DEBUG));
564: }
565:
566: /** Print a debug message with the given number of stack lines. */
567: public static void debug(String event, int lines) {
568: internalDebug(event, new Context(DEBUG), lines);
569: }
570:
571: /** Use this to display debug output for expected or common exceptions. */
572: public static void debug(Throwable thr) {
573: internalDebug("", new Context(DEBUG, thr));
574: }
575:
576: /** Issue a debug statement regarding the given {@link Throwable}. */
577: public static void debug(String m, Throwable e) {
578: internalDebug(m, new Context(DEBUG, e));
579: }
580:
581: private static void internalDebug(String msg, Context context) {
582: internalDebug(msg, context, CLASS_STACK_DEPTH);
583: }
584:
585: private static void internalDebug(String msg, Context context,
586: int lines) {
587: if (debugged.size() > 0 || debugAll) {
588: internalLog(msg, context, lines, 1,
589: echoToConsole ? debugStream : logStream);
590: }
591: }
592:
593: /** Replace all occurrences of a given expresion with a different
594: string. */
595: private static String abbreviate(String msg, String expr, String sub) {
596: // Eclipse uses the full classs name for navigation, so don't hide it
597: if (ECLIPSE)
598: return msg;
599: StringBuffer sb = new StringBuffer(msg);
600: int index = msg.indexOf(expr);
601: int len = expr.length();
602: while (index >= 0) {
603: sb.replace(index, index + len, sub);
604: index = sb.toString().indexOf(expr);
605: }
606: return sb.toString();
607: }
608:
609: /** Strip out stuff we don't want showing in the message. */
610: private static String abbreviate(String msg) {
611: if (COMMON_PREFIX != null)
612: msg = abbreviate(msg, COMMON_PREFIX, "*");
613: return msg;
614: }
615:
616: /** Issue a warning. All warnings go to the log file and the error
617: stream. */
618: private static void internalWarn(String message, Context context,
619: int depth, int pop) {
620: internalLog(message, context, depth, pop,
621: showWarnings ? warnStream : logStream);
622: }
623:
624: /** Retrieve the given number of lines of the current stack, as a
625: string. */
626: public static String getStack(int lines) {
627: return getStackTrace(1, lines);
628: }
629:
630: /** Retrieve the full stack from the given Throwable, as a string. */
631: public static String getStack(Throwable t) {
632: return getStackTrace(Log.FULL_STACK, t);
633: }
634:
635: /** Retrieve the given number of lines of stack from the given Throwable,
636: as a string. */
637: public static String getStack(int lines, Throwable thr) {
638: return getStackTrace(1, lines, thr);
639: }
640:
641: /** Issue a programmer warning, which will include the source line of the
642: warning. */
643: public static void warn(String message) {
644: internalWarn(message, new Context(WARN), debugStackDepth, 1);
645: }
646:
647: /** Issue a programmer warning, which will include the source line of the
648: warning. */
649: public static void warn(String message, Throwable e) {
650: internalWarn(message, new Context(WARN, e), debugStackDepth, 1);
651: }
652:
653: /** Issue a programmer warning, which will include the source line of the
654: warning, and a stack trace with up to the given number of lines. */
655: public static void warn(String message, int lines) {
656: internalWarn(message, new Context(WARN), lines, 1);
657: }
658:
659: /** Issue a programmer warning, which will include the source line of the
660: original thrown object. */
661: public static void warn(Throwable thr) {
662: internalWarn("", new Context(WARN, thr), debugStackDepth, 1);
663: }
664:
665: /** Log an exception. */
666: public static void log(Throwable thr) {
667: internalLog("", new Context(LOG, thr), excStackDepth, 1);
668: }
669:
670: /** Log an exception with a description. */
671: public static void log(String message, Throwable thr) {
672: internalLog(message, new Context(LOG, thr), excStackDepth, 1);
673: }
674:
675: /** Log a message. */
676: public static void log(String message) {
677: internalLog(message, new Context(LOG), logStackDepth, 1);
678: }
679:
680: private static void internalLog(String event, Context context,
681: int depth, int pop) {
682: internalLog(event, context, depth, pop, logStream);
683: }
684:
685: private static void internalLog(String event, Context context,
686: int depth, int pop, PrintStream stream) {
687: String thread = Thread.currentThread().getName();
688: if (synchronous) {
689: logMessage(event, new Date(), context, depth, pop, stream,
690: thread);
691: } else if (logThread != null) {
692: logThread.post(event, thread, new Date(), context, depth,
693: pop, stream);
694: } else {
695: STDERR.println("Message posted after close: " + event);
696: }
697: }
698:
699: static void flush() {
700: while (logThread.queue.size() > 0) {
701: synchronized (logThread.queue) {
702: logThread.queue.notifyAll();
703: }
704: try {
705: Thread.sleep(10);
706: } catch (InterruptedException e) {
707: }
708: }
709: debugStream.flush();
710: warnStream.flush();
711: logStream.flush();
712: }
713:
714: public static void close() {
715: flush();
716: log("Log closed");
717: logStream.close();
718: logThread.terminate();
719: logThread = null;
720: }
721:
722: private static class LogThread extends Thread {
723: private boolean terminate;
724: private Vector queue = new Vector();
725:
726: public LogThread() {
727: super ("Logging thread");
728: setDaemon(true);
729: }
730:
731: public void terminate() {
732: synchronized (queue) {
733: terminate = true;
734: queue.notifyAll();
735: }
736: }
737:
738: public void post(String msg, String threadName, Date date,
739: Context throwable, int depth, int pop,
740: PrintStream output) {
741: synchronized (queue) {
742: if (!terminate) {
743: queue.add(new Object[] { msg, date, throwable,
744: new int[] { depth, pop }, output,
745: threadName });
746: queue.notifyAll();
747: } else {
748: STDERR.println("discarded: " + msg);
749: }
750: }
751: }
752:
753: public void run() {
754: setName("Logging thread (to " + logStream + ")");
755: while (!terminate) {
756: try {
757: while (queue.size() > 0) {
758: Object[] list = (Object[]) queue.get(0);
759: int[] args = (int[]) list[3];
760: logMessage((String) list[0], (Date) list[1],
761: (Context) list[2], args[0], args[1],
762: (PrintStream) list[4], (String) list[5]);
763: queue.remove(0);
764: }
765: synchronized (queue) {
766: if (queue.size() == 0) {
767: queue.wait();
768: }
769: }
770: } catch (InterruptedException e) {
771: break;
772: } catch (Throwable e) {
773: STDERR.println("Error in logging thread: " + e);
774: e.printStackTrace();
775: }
776: }
777: }
778: }
779:
780: private static String lastMessage = null;
781: private static int lastMessageRepeatCount = 0;
782: private static String lastMessageTimestamp = null;
783: private static PrintStream lastMessageStream = null;
784:
785: private static void logMessage(String msg, Date date,
786: Context context, int depth, int pop, PrintStream stream,
787: String threadName) {
788: boolean debug = context.type == DEBUG;
789: String trace;
790: if (debug) {
791: trace = getStackTrace(pop, Log.FULL_STACK, context);
792: String cname = extractClass(trace);
793: if (!isClassDebugEnabled(cname)) {
794: return;
795: }
796: if (depth == CLASS_STACK_DEPTH) {
797: trace = trimStackTrace(trace, getClassStackDepth(cname));
798: }
799: } else {
800: trace = getStackTrace(pop, depth, context);
801: }
802: if (context.thrown != null) {
803: Throwable e = context.thrown;
804: String where = getStackTrace(0, excStackDepth, e);
805: String type = e instanceof Error ? "Error"
806: : "Exception thrown";
807: trace = type + " at " + where + ": " + e + NL
808: + "\t(caught at " + trace + ")";
809: if (e instanceof InvocationTargetException) {
810: e = ((InvocationTargetException) e)
811: .getTargetException();
812: where = getStackTrace(0, excStackDepth, e);
813: trace += NL + "Target exception was " + e + " at "
814: + where;
815: } else if (e instanceof UndeclaredThrowableException) {
816: e = ((UndeclaredThrowableException) e)
817: .getUndeclaredThrowable();
818: where = getStackTrace(0, excStackDepth, e);
819: trace += NL + "Undeclared exception was " + e + " at "
820: + where;
821: } else if (e instanceof ExceptionInInitializerError) {
822: e = ((ExceptionInInitializerError) e).getException();
823: where = getStackTrace(0, excStackDepth, e);
824: trace += NL + "Exception was " + e + " at " + where;
825: }
826: }
827: trace = abbreviate(trace);
828: if (showThreads) {
829: trace = "[" + threadName + "] " + trace;
830: }
831: String timestamp = timestampFormat.format(date);
832: if (showTimestamp) {
833: trace = timestamp + " " + trace;
834: }
835: String output = trace.trim();
836: if (msg != null && !"".equals(msg)) {
837: output += ":\n\t" + msg;
838: }
839: if (stream == lastMessageStream
840: && (msg == lastMessage || (msg != null && msg
841: .equals(lastMessage)))) {
842: ++lastMessageRepeatCount;
843: lastMessageTimestamp = timestamp;
844: } else {
845: if (lastMessageRepeatCount > 0) {
846: lastMessageStream.println(lastMessageTimestamp
847: + ": Last message repeated "
848: + lastMessageRepeatCount + " times");
849: lastMessageStream.flush();
850: }
851: stream.println(output);
852: lastMessage = msg;
853: lastMessageStream = stream;
854: lastMessageRepeatCount = 0;
855: lastMessageTimestamp = timestamp;
856: }
857: }
858:
859: /** Set whether log output is synchronous with program execution. */
860: public static void setSynchronous(boolean b) {
861: synchronous = b;
862: }
863:
864: /** Set whether to display the current thread of execution. */
865: public static void setShowThreads(boolean b) {
866: showThreads = b;
867: }
868:
869: /** Set whether messages are echoed to the console in addition to the log.
870: */
871: public static void setEchoToConsole(boolean b) {
872: echoToConsole = b;
873: }
874: }
|