Joshua
Joshua

Reputation: 15

Why is the new Java Logger failing to work consistently here?

Note how most of the other calls Logger calls work, and all the System.out.println() works, but please someone explain to me why the Logger.info() calls in stop() and in destroy() never print with the rest of the log, since those functions are clearly running!? With java12, even the destroy() isn't showing. Is this a bug, am I using it weird, or what?

package test;
import java.util.logging.Logger;

public class Test {
    private static Logger LOGGER = Logger.getLogger(Test.class.getCanonicalName());

    public static void main(String[] args) {
        System.out.println("main()");
        LOGGER.info("main()");
        new Test();
    }

    private Test() {
        LOGGER.info("Test()");
        System.out.println("Test()");
        Runtime.getRuntime().addShutdownHook(new ShutdownThread());
    }

    public void shutdown() throws Exception {
        LOGGER.info("shutdown()");
        System.out.println("shutdown()");
        stop();
        destroy();
    }

    public void stop() throws Exception {
        LOGGER.info("stop()");
        System.out.println("stop()");
    }

    public void destroy() {
        LOGGER.info("destroy()");
        System.out.println("destroy()");
    }

    class ShutdownThread extends Thread {
        ShutdownThread() {
            super("app-shutdown-hook");
        }

        @Override
        public void run() {
            try {
                shutdown();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("Bye! 🙇‍♂️👋🖖");
        }
    }
}

OUTPUT with both java 10 and java 11 (OpenJDK):

main()
Mar 14, 2020 1:53:59 PM test.Test main
INFO: main()
Mar 14, 2020 1:53:59 PM test.Test <init>
INFO: Test()
Test()
Mar 14, 2020 1:53:59 PM test.Test shutdown
INFO: shutdown()
shutdown()
stop()
destroy()
Bye! 🙇‍♂️👋🖖

OUTPUT with java 12 (OpenJDK):

main()
Mar 14, 2020 2:17:13 PM test.Test main
INFO: main()
Mar 14, 2020 2:17:13 PM test.Test <init>
INFO: Test()
Test()
shutdown()
stop()
destroy()
Bye! 🙇‍♂️👋🖖

Upvotes: 0

Views: 552

Answers (1)

jmehrens
jmehrens

Reputation: 11045

This issue is coverted in: JDK-8161253 - LogManager$Cleaner() can prevent logging in other shutdown hooks.

Per the ticket:

As a workaround to creating a custom shutdown hook you can create a custom handler and install it on the root logger. The first action of the LogManager$Cleaner is to close all the installed handlers on the logger. Once the cleaner calls the close on the custom handler you can then do one of the following:

  1. Have cleaner run your shutdown code inside the handler.
  2. Find your custom shutdown hook using the Thread API and join with it.

Here is solution #1:

import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

public class Test {
    private static Logger LOGGER = Logger.getLogger(Test.class.getCanonicalName());

    public static void main(String[] args) {
        System.out.println("main()");
        LOGGER.info("main()");
        new Test();
    }

    private Test() {
        LOGGER.info("Test()");
        System.out.println("Test()");
        addShutdownHandler();
    }

    private void addShutdownHandler() {
        Logger root = Logger.getLogger("");
        Handler[] handlers = root.getHandlers();

        for(Handler h : handlers) {
            if (h.getClass() == ShutdownHandler.class) {
                return;
            }
        }

        for(Handler h : handlers) {
            root.removeHandler(h);
        }

        root.addHandler(new ShutdownHandler());

        for(Handler h : handlers) {
            root.addHandler(h);
        }
    }

    public void shutdown() throws Exception {
        LOGGER.info("shutdown()");
        System.out.println("shutdown()");
        stop();
        destroy();
    }

    public void stop() throws Exception {
        LOGGER.info("stop()");
        System.out.println("stop()");
    }

    public void destroy() {
        LOGGER.info("destroy()");
        System.out.println("destroy()");
    }

    class ShutdownHandler extends Handler {
        ShutdownHandler() {
        }

        @Override
        public void close() {
            final Thread t = Thread.currentThread();
            final String old = t.getName();
            t.setName("app-shutdown-hook");
            try {
                shutdown();
                System.out.println("Bye! 🙇‍♂️👋🖖");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                t.setName(old);
            }
        }

        @Override
        public void flush() {
        }

        @Override
        public void publish(LogRecord r) {
            isLoggable(r);
        }
    }
}

Solution #2 gets tricky as we can't ensure that from a shutdown hook that another shutdown hook was started. This means extra coding if you want to use Thread::join. So to workaround this issue we simply use the Future API:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.logging.ErrorManager;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

public class Test {
    private static Logger LOGGER = Logger.getLogger(Test.class.getCanonicalName());

    public static void main(String[] args) {
        System.out.println("main()");
        LOGGER.info("main()");
        new Test();
    }

    private Test() {
        LOGGER.info("Test()");
        System.out.println("Test()");
        addShutdownHandler();
    }

    private void addShutdownHandler() {
        Logger root = Logger.getLogger("");
        Handler[] handlers = root.getHandlers();

        for(Handler h : handlers) {
            if (h.getClass() == CleanerJoin.class) {
                return;
            }
        }

        for(Handler h : handlers) {
            root.removeHandler(h);
        }

        root.addHandler(new CleanerJoin());

        for(Handler h : handlers) {
            root.addHandler(h);
        }
    }

    public void shutdown() throws Exception {
        LOGGER.info("shutdown()");
        System.out.println("shutdown()");
        stop();
        destroy();
    }

    public void stop() throws Exception {
        LOGGER.info("stop()");
        System.out.println("stop()");
    }

    public void destroy() {
        LOGGER.info("destroy()");
        System.out.println("destroy()");
    }

    class ShutdownTask implements Callable<Void> {
        ShutdownTask() {
        }

        @Override
        public Void call() throws Exception {
            shutdown();
            System.out.println("Bye! 🙇‍♂️👋🖖");
            return null;
        }
    }

    class CleanerJoin extends Handler {
        private final FutureTask<Void> sdt = new FutureTask<>(new ShutdownTask());

        CleanerJoin() {
            Runtime.getRuntime().addShutdownHook(new Thread(sdt, "app-shutdown-hook"));
        }


        @Override
        public void close() {
            boolean interrupted = false;
            try {
                for(;;) {
                    try { //Could use LogManager to lookup timeout values and use a timed join.
                        sdt.get();
                        break;
                    } catch (ExecutionException e) {
                        reportError("Shutdown hook failed.", e, ErrorManager.CLOSE_FAILURE);
                        break;
                    } catch (InterruptedException retry) {
                        interrupted = true;
                    }
                }
            } finally {
                if (interrupted) {
                    Thread.currentThread().interrupt();
                }
            }
        }

        @Override
        public void flush() {
        }

        @Override
        public void publish(LogRecord r) {
            isLoggable(r);
        }
    }
}

Upvotes: 3

Related Questions