// $Id: UncaughtExceptionLogger.java,v 1.1 2005/03/05 18:32:53 Dave Exp $ /* * UncaughtExceptionLogger.java * Copyright (C) 2005 David Clausen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later * version. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.dclausen.util; import java.util.logging.*; /** *
* A ThreadGroup which logs its child Threads'
* uncaught exceptions using the java logging API. This overrides the default
* ThreadGroup behavior of printing the stack trace to
* System.err. This is particularly handy when dealing with
* threads started by libraries which are outside of your control. For example,
* when you have a Swing app and a misbehaving component is crashing the AWT
* event thread.
*
* The recommended way to use this class is to construct an instance
* very early in your application's bootstrap process -- ideally before
* most classes are initialized -- and then continue your bootstrapping
* on a new child thread. The reason for doing this early on is because
* some classes start threads in their static initializers, and you probably
* want those threads to be owned by the UncaughtExceptionLogger.
* Even the java.util.logging.* classes can start threads, which
* is why the constructors for this class take String arguments
* rather than Logger and Level instances. Those
* names are resolved on a child thread.
*
* UPDATE: As of Java 1.5, there is a better way of solving this problem.
* Take a look at ThreadGroup.setUncaughtExceptionHandler
*
* Here is a simple example of how to bootstrap your app: *
* *
*
* ----------
*
* // MyAppLauncher.java
*
* // Keep it simple. Don't reference any unnecessary classes.
*
* public class MyAppLauncher {
*
* // Start a new thread which will have its (and all of its children's)
* // exceptions logged. This thread will call MyApp.main(args)...
* public static void main(String[] args) {
* UncaughtExceptionLogger.invokeMain("", "WARNING", "MyApp", args);
* }
*
* }
*
* ----------
*
* // MyApp.java
*
* import java.util.logging.*;
* import javax.swing.*;
* import oracle.jdbc.*;
*
* public class MyApp extends JFrame {
*
* public static void main(String[] args) {
* // Start the real app here. Initiaize classes, create threads,
* // go crazy...
* MyApp app = new MyApp();
* ...
* }
*
* }
*
* ----------
*
*
*
* @see java.util.logging.Logger
* @see java.lang.Thread#run
* @see java.lang.ThreadGroup#uncaughtException
*/
public class UncaughtExceptionLogger extends ThreadGroup {
private final String loggerName, levelName;
private Logger logger;
private Level level;
/**
* Construct a new UncaughtExceptionLogger.
*
* @param loggerName
* the name of the logger to use; defaults to "" if null
* @param levelName
* the name of the logging level to use; defaults to "WARNING" if
* null
*/
public UncaughtExceptionLogger(String loggerName, String levelName) {
this(loggerName, levelName, null);
}
/**
* Construct a new UncaughtExceptionLogger, and run runnable
* on a child thread.
*
* @param loggerName
* the name of the logger to use; defaults to "" if null
* @param levelName
* the name of the logging level to use; defaults to "WARNING" if
* null
* @param runnable
* an optional Runnable to run on a new child
* thread
*/
private UncaughtExceptionLogger(String loggerName, String levelName,
final Runnable runnable) {
super("UncaughtExceptionLogger");
if (loggerName == null) {
loggerName = "";
}
if (levelName == null) {
levelName = "WARNING";
}
this.loggerName = loggerName;
this.levelName = levelName;
// initialize the Logger on a child thread, and then run "runnable"
new Thread(this, Thread.currentThread().getName() + "-logged") {
public void run() {
System.err.println("Starting: " + this);
UncaughtExceptionLogger.this.init();
if (runnable != null) {
runnable.run();
}
}
}.start();
}
/**
* Initialize the java.util.logging classes. This needs to be done on a
* spawned thread because these classes could initialize the AWT event
* thread, and we want that to happen on a thread which is owned by this
* ThreadGroup (so the AWT thread will a member of this group).
*/
private void init() {
if (logger != null) {
throw new IllegalStateException();
}
try {
logger = Logger.getLogger(loggerName);
level = Level.parse(levelName);
} catch (Throwable e) {
logger = Logger.getLogger("");
level = Level.parse("WARNING");
logger.log(level, "Error constructing logger or level", e);
}
}
/**
* Log the Throwable using the java logging API. This
* method is called by the JVM when a child thread's run()
* throws an Exception or Error.
*
* @see Thread#run
*/
public void uncaughtException(Thread t, Throwable e) {
logger.log(level, "Thread[\"" + t.getName() + "\"] threw an exception",
e);
}
/**
* Construct a new UncaughtExceptionLogger, and invoke
* mainClass.main(args) on a child thread.
*
* @param loggerName
* the name of the logger to use
* @param levelName
* the name of the logging level to use
* @param mainClass
* the name of a class which contains a static main method
* @param args
* the arguments to pass to mainClass.main
*/
public static void invokeMain(String loggerName, String levelName,
String mainClass, String[] args) {
new UncaughtExceptionLogger(loggerName, levelName,
new ReflectiveInvoker(mainClass, "main",
new Class[] { new String[0].getClass() },
new Object[] { args }));
}
}