为什么新的 Java Logger 无法在此处始终如一地工作?

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

请注意大多数其他调用 Logger 调用的工作原理,以及所有 System.out.println() 调用的工作原理,但请有人向我解释为什么 Logger.info() 调用在 stop() 中并且在 destroy() 中永远不要打印日志的其余部分,因为这些函数显然是 运行!?使用 java12,甚至 destroy() 也不会显示。这是一个错误,我使用它很奇怪还是什么?

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! ‍♂️");
        }
    }
}

输出 java 10 和 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! ‍♂️

输出 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! ‍♂️

此问题隐藏在:JDK-8161253 - LogManager$Cleaner() can prevent logging in other shutdown hooks

每张票:

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. 让处理程序中的关闭代码更干净运行。
  2. 使用线程 API 找到您的自定义关闭挂钩并加入它。

这是解决方案 #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);
        }
    }
}

解决方案 #2 变得棘手,因为我们无法确保从关闭挂钩启动了另一个关闭挂钩。如果您想使用 Thread::join,这意味着额外的编码。因此,为了解决这个问题,我们只需使用 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);
        }
    }
}