UncaughtExceptionHandlers.java
package net.gini.dropwizard.gelf.logging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
/**
* Simple factory for a {@link Thread.UncaughtExceptionHandler} which logs the last uncaught exception with a SLF4J {@link
* Logger} on ERROR level.
*/
public final class UncaughtExceptionHandlers {
private UncaughtExceptionHandlers() {
}
/**
* Returns a builder for an exception handler that bootstraps a GELF log appender, logs the uncaught exception
* and then exits the system. This is particularly useful for the main thread, which may start up other,
* non-daemon threads, but fail to fully initialize the application successfully.
* <br>
* Example usage:
* <pre>public static void main(String[] args) {
* Thread.currentThread().setUncaughtExceptionHandler(
* UncaughtExceptionHandlers.loggingSystemExitBuilder("some-service", "log.example.com").build());
* ...
* </pre>
*
* @param facility The facility to use in the GELF messages
* @param host The host of the Graylog server
* @return builder object for building the exception handler
*/
public static LoggingSystemExitBuilder loggingSystemExitBuilder(final String facility, final String host) {
return new LoggingSystemExitBuilder(facility, host);
}
/**
* Returns an exception handler that logs the uncaught exception to {@code System.err} and then exits the system.
* This is particularly useful for the main thread, which may start up other, non-daemon threads, but fail to fully
* initialize the application successfully. <br> Example usage:
* <pre>public static void main(String[] args) {
* Thread.currentThread().setUncaughtExceptionHandler(
* UncaughtExceptionHandlers.systemExit());
* ...
* </pre>
*
* @return exception handler
*/
public static Thread.UncaughtExceptionHandler systemExit() {
return new Exiter(Runtime.getRuntime());
}
public static final class LoggingSystemExitBuilder {
private String facility;
private String host;
private int port = 12201;
private boolean cleanRootLogger = false;
private boolean logToStderr = true;
LoggingSystemExitBuilder(final String facility, final String host) {
this.facility = requireNonNull(facility);
this.host = requireNonNull(host);
}
/**
* Sets the port of the Graylog server.
*
* @param port The port of the Graylog server.
* @return {@link LoggingSystemExitBuilder} instance
*/
public LoggingSystemExitBuilder port(final int port) {
this.port = port;
return this;
}
/**
* Sets whether all existing appenders should be detached from the root logger.
*
* @param cleanRootLogger If true, detach and stop all other appenders from the root logger
* @return {@link LoggingSystemExitBuilder} instance
*/
public LoggingSystemExitBuilder cleanRootLogger(final boolean cleanRootLogger) {
this.cleanRootLogger = cleanRootLogger;
return this;
}
/**
* Sets whether the stacktrace of the uncaught exception should be printed to {@code System.err}.
*
* @param logToStderr If true, print the stacktrace to {@code System.err}
* @return {@link LoggingSystemExitBuilder} instance
*/
public LoggingSystemExitBuilder logToStderr(final boolean logToStderr) {
this.logToStderr = logToStderr;
return this;
}
public Thread.UncaughtExceptionHandler build() {
return new LoggingExiter(Runtime.getRuntime(), facility, host, port, cleanRootLogger, logToStderr);
}
}
/**
* Exception handler that exits the system. Bootstrap a GELF log appender and logs the uncaught exception.
* Optionally prints the stacktrace to {@code System.err} as well.
*/
private static final class LoggingExiter implements Thread.UncaughtExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(Exiter.class);
private final Runtime runtime;
private final String name;
private final String host;
private final int port;
private final boolean cleanRootLogger;
private final boolean logToStderr;
private LoggingExiter(final Runtime runtime, final String name, final String host, final int port,
final boolean cleanRootLogger, final boolean logToStderr) {
this.runtime = runtime;
this.name = name;
this.host = host;
this.port = port;
this.cleanRootLogger = cleanRootLogger;
this.logToStderr = logToStderr;
}
@Override
public void uncaughtException(final Thread t, final Throwable e) {
// Re-initialize logging system (as dropwizard likely has stopped it)
GelfBootstrap.bootstrap(name, host, port, cleanRootLogger);
// Log the exception
final String msg = format("Caught an exception in %s. Shutting down", t);
LOGGER.error(msg, e);
if (logToStderr) {
System.err.print(msg);
System.err.print("! ");
e.printStackTrace(System.err);
}
// Stop appenders (should flush all existing messages)
getRootLogger().detachAndStopAllAppenders();
// Terminate the JVM
runtime.exit(1);
}
private ch.qos.logback.classic.Logger getRootLogger() {
return (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
}
}
private static final class Exiter implements Thread.UncaughtExceptionHandler {
private final Runtime runtime;
private Exiter(final Runtime runtime) {
this.runtime = requireNonNull(runtime);
}
@Override
public void uncaughtException(final Thread t, final Throwable e) {
System.err.print(format("Caught an exception in %s. Shutting down! ", t));
e.printStackTrace(System.err);
runtime.exit(1);
}
}
}