Tyrel
Tyrel

Reputation: 727

Can I prevent the `java.util.logging.LogManager` from shutting down too quickly?

My Java program, which is running on Linux, needs to close some file handles (devices, actually) before it shuts down, so I've got a shutdown hook added on the JVM. However, I noticed that the java.util.logging.LogManager also has a shutdown hook, and it tends to shut down before I have shut down, which means I can't log anything about my shutdown process.

Is there a way to prevent LogManager from installing its shutdown hook so that I can perform logging while shutting down, and then tell it to clean up when I'm ready?

Upvotes: 4

Views: 1103

Answers (4)

jmehrens
jmehrens

Reputation: 11045

Make the LogManager cleaner thread either join with your shutdown hook or perform your file clean up directly by installing a custom handler. The cleaner thread will enumerate the loggers in order and try to close the attached handlers. As long as your handler is the first one that it tries to close you can control when the cleaner executes.

public class ShutdownHandler extends Handler {

    public static void main(String[] args) {
        install();
    }

    private static void install() {
        LogManager lm = LogManager.getLogManager();
        Logger first = null;
        ShutdownHandler sh = new ShutdownHandler();
        for (;;) {
            try {
                Enumeration<String> e = lm.getLoggerNames();
                while (e.hasMoreElements()) {
                    first = lm.getLogger(e.nextElement());
                    if (first != null) {
                        break;
                    }
                }
                break;
            } catch (ConcurrentModificationException olderJvm) {
            }
        }

        addHandlerFirst(Logger.getLogger(""), sh);
        if (first != null) {
            addHandlerFirst(first, sh);
        }
    }

    private static void addHandlerFirst(Logger l, Handler h) {
       Handler[] handlers = l.getHandlers();
       for (int i = handlers.length - 1; i >= 0; --i) {
           l.removeHandler(handlers[i]);
       }
       
       l.addHandler(h);

       for(Handler existing : handlers) {
           l.addHandler(existing);
       }
    }

    public ShutdownHandler() {
        super.setLevel(Level.ALL);
    }

    @Override
    public void publish(LogRecord record) {
    }

    @Override
    public void flush() {
    }

    @Override
    public void close() throws SecurityException {
        if (!Level.OFF.equals(super.getLevel())) {
            super.setLevel(Level.OFF);
            shutDown();
        }
    }

    private void shutDown() {
        System.out.println(this + " shutdown by "
                + Thread.currentThread().getClass().getName());
        //Close your files or join with your other shutdown hook.
    }
}

Upvotes: 2

schlm3
schlm3

Reputation: 108

Another workaround by exchanging the default LogManager with a custom one.

/**
 * This code shows how you can fix your JUL Logging inside Shutdown Hooks
 * to workaround Bug JDK-8161253.
 */
public class FixedShutdownHookLoggingDemoApp {
  private static final Logger log;
  static{
    //Unfortunately, in the installed application, setting the system-property here might already be too late.
    //It needs to be set that OUTSIDE on application start! But do it here for development nevertheless
    System.setProperty("java.util.logging.manager", LogManagerFixed.class.getName());
    log = Logger.getLogger(FixedShutdownHookLoggingDemoApp.class.getName());
  }

  public static void main(String[] args) {
    FixedShutdownHookLoggingDemoApp myApplication = new FixedShutdownHookLoggingDemoApp();
    myApplication.start(args);
  }


  private final MyShutdownHook shutdownHook = new MyShutdownHook();

  public FixedShutdownHookLoggingDemoApp(){
    Runtime.getRuntime().addShutdownHook(shutdownHook);
    checkFixedLogConfig();
  }

  /**
   * Checks that the LogManager has correctly been set to {@link LogManagerFixed}.
   * If not, issues a warning to the log
   */
  private void checkFixedLogConfig() {
    if (!(LogManager.getLogManager() instanceof LogManagerFixed)) {
      log.warning("LogManager has not been correctly initialized. Shutdowns initiated from outside - aka service stops and restarts - will not be able to log progress.");
      log.warning("Please set SystemProperty -Djava.util.logging.manager="+LogManagerFixed.class.getName());
    }else{
      log.info("LogManager was successfully initialized to "+LogManagerFixed.class);
    }
  }

  private volatile boolean doWork = true;
  private void start(String[] arg0) {
    //startup your Application here...
    //just some demo code
    while(doWork){
      try {Thread.sleep(500);} catch (InterruptedException e) {}
      log.info("Doing stupid work");
    }
  }

  private void shutdown(){
    //shutdown your Application here...
    //just some demo code
    log.info("shutting down the application");
    for (int i=0;i<5;i++){
      try {Thread.sleep(200);} catch (InterruptedException e) {}
      log.info("working on shutdown... "+i);
    }
    doWork = false;
  }

  /**
   * The shutdown hook is required when using for example the Install4J Service Launcher.
   * It needs to be used together with LogManagerFixed such that the log messages get logged during the shutdown phase.
   */
  private class MyShutdownHook extends Thread{
    MyShutdownHook() {
      super("My Shutdown-Hook");
    }
    @Override public void run(){
      log.log(Level.INFO, "Shutdown initiated.");
      shutdown();

      //at the very end, cleanup the logging API
      LogManager logManager = LogManager.getLogManager();
      if (logManager instanceof LogManagerFixed){
        ((LogManagerFixed) logManager).resetFinally();
        //no more java util.logging possible after this point!
      }
    }
  };

  /**
   * Java util Logging has a Bug - known since 2005 - which prevents logging inside other shutdown hooks.
   * See https://bugs.openjdk.org/browse/JDK-8161253 and all of its duplicates...
   * and https://stackoverflow.com/questions/36388969/can-i-prevent-the-java-util-logging-logmanager-from-shutting-down-too-quickly
   * This is a workaround against it, inspired by https://coderanch.com/t/654750/java/resoliving-shutdown-hook-logger-LogManager
   */
  public static final class LogManagerFixed extends LogManager {
    public LogManagerFixed(){
      super();
    }
    @Override public void reset() {
      try {
        Field f = LogManager.class.getDeclaredField("deathImminent");
        f.setAccessible(true);
        boolean deathImminent = f.getBoolean(this);
        if (deathImminent){
          //is inside the shutdown sequence, don't reset yet.
          log.log(Level.FINER, "LogManagerFixed: attempt to shutdown prohibited");
          return;
        }
      } catch (NoSuchFieldException|IllegalAccessException e) {
        throw new RuntimeException(e);
      }
      //if getting to here, this is likely a valid reset call (this is required during initialisation of the Logging System)
      super.reset();
    }
    public  void resetFinally() {
      log.log(Level.INFO, "LogManagerFixed: shutting down now");
      super.reset();
    }
  }
}

Upvotes: 0

Tyrel
Tyrel

Reputation: 727

This is more of a workaround... but... since I'm running on Linux (this is a program dedicated to a specific system), I ended up using signal handlers using sun.misc.Signal. A signal handler gets run before the JVM runs the shutdown hooks (presumably it also has its own signal handlers that start that process).

So instead of Runtime.getRuntime().addShutdownHook I'm now doing this:

private void installSignalHandlers() {
    SignalHandler signalHandler = signal -> shutDown();
    Signal.handle(new Signal("INT"), signalHandler);
    Signal.handle(new Signal("TERM"), signalHandler);
}

It seems to work great; I can close all my open handles and such in shutDown while logging is still operating.

Upvotes: 1

markspace
markspace

Reputation: 11030

Hmm, you could define the system property "java.util.logging.manager" with the name of a class you created. That will allow you to use a logging manager of your own devising.

https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html

I've never tried this before however so I don't know how well it will work or how much work it'll be.

Upvotes: 1

Related Questions