Java - 如何在JVM关闭钩子中启用日志记录器?

16

我有一个专门使用java.util.logging.Logger类的日志记录器类。 我想在另一个类的关闭挂钩中使用此记录器。 但是,在关闭时似乎没有记录。 根据我的阅读,可能已经为记录器本身激活了关闭挂钩,导致了这个问题。

我该如何使其正常工作?理想情况下,当进程终止时,我希望能在日志文件中看到执行关闭挂钩的记录。


我不确定这是否正确,但您无法控制关闭挂钩发生的顺序。 - Peter Lawrey
1
确实如此,它们是并发运行的 - Miserable Variable
2个回答

21

再次查看源代码,解决方案似乎是定义一个系统属性java.util.logging.manager,它是LogManager的子类,重写了reset();方法,以便记录器在关闭时继续工作。

import java.util.logging.LogManager;
import java.util.logging.Logger;

public class Main {
    static {
        // must be called before any Logger method is used.
        System.setProperty("java.util.logging.manager", MyLogManager.class.getName());
    }

    public static class MyLogManager extends LogManager {
        static MyLogManager instance;
        public MyLogManager() { instance = this; }
        @Override public void reset() { /* don't reset yet. */ }
        private void reset0() { super.reset(); }
        public static void resetFinally() { instance.reset0(); }
    }

    public static void main(String... args) {
        Logger logger1 = Logger.getLogger("Main1");
        logger1.info("Before shutdown");
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Logger logger2 = Logger.getLogger("Main2");
                    logger2.info("Shutting down 2");

                } finally {
                    MyLogManager.resetFinally();
                }
            }
        }));
    }
}

打印

Dec 11, 2012 5:56:55 PM Main main
INFO: Before shutdown
Dec 11, 2012 5:56:55 PM Main$1 run
INFO: Shutting down 2

从LogManager的代码中可以看到有一个关闭挂钩,它会拆除处理程序并关闭它们。如果Logger之前没有被使用过,则只有在关闭时才能正常工作,因此这段代码不会运行。

// This private class is used as a shutdown hook.
// It does a "reset" to close all open handlers.
private class Cleaner extends Thread {

    private Cleaner() {
        /* Set context class loader to null in order to avoid
         * keeping a strong reference to an application classloader.
         */
        this.setContextClassLoader(null);
    }

    public void run() {
        // This is to ensure the LogManager.<clinit> is completed
        // before synchronized block. Otherwise deadlocks are possible.
        LogManager mgr = manager;

        // If the global handlers haven't been initialized yet, we
        // don't want to initialize them just so we can close them!
        synchronized (LogManager.this) {
            // Note that death is imminent.
            deathImminent = true;
            initializedGlobalHandlers = true;
        }

        // Do a reset to close all active handlers.
        reset();
    }
}


/**
 * Protected constructor.  This is protected so that container applications
 * (such as J2EE containers) can subclass the object.  It is non-public as
 * it is intended that there only be one LogManager object, whose value is
 * retrieved by calling Logmanager.getLogManager.
 */
protected LogManager() {
    // Add a shutdown hook to close the global handlers.
    try {
        Runtime.getRuntime().addShutdownHook(new Cleaner());
    } catch (IllegalStateException e) {
        // If the VM is already shutting down,
        // We do not need to register shutdownHook.
    }
}

根据我的测试结果

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Logger logger2 = Logger.getLogger("Main2");
            logger2.info("Shutting down 2");
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}));

打印

Dec 11, 2012 5:40:15 PM Main$1 run
INFO: Shutting down 2

但是如果您添加了

Logger logger1 = Logger.getLogger("Main1");

在这个块之外,你将得不到任何东西。


3
即使使用Logger.getLogger(name),我仍然没有看到任何日志打印。但是,设置系统属性并复制MyLogManager代码后,我成功使它工作了。非常好,谢谢! - user1514879
我使用了MyLogManager的方法,但结果是所有日志突然重复,每个日志行都写了两次。我追踪并验证了MyLogManager只被实例化一次。当我注释掉设置系统属性时,一切恢复正常,即每个日志行只写一次,没有在shutdownHook中记录日志。 - Gunther Schadow

0

关闭挂钩有什么优势吗?

最好在主函数的最后一行添加日志输出: 无论如何,finally块都将被执行, 除非VM崩溃,但此时关闭挂钩也不会被执行。

static void main(String[] argv) {

  try {
    startApp();
  } finally {
    LOGGER.info("shudown");
  }
}

我的进程是全天候运行的,坐等某些事件发生。这不是一次性的事情,我不能只在代码末尾放置所需内容。我相信关闭挂钩是正确的解决方案。 - user1514879
1
所以看起来你不知道代码的结束在哪里?你应该知道并找出来。这与一次性操作无关!我们的软件可以连续运行数周而不关闭,但我们知道何时结束。也许使用Spring或其他框架有点困难。但你应该知道如何清理,因为经常需要有序关闭!LOGGING需要一个关闭处理程序的原因是它是一个库,无法知道“代码的结束”。 - AlexWien
2
该进程不会自动退出,因此也没有逻辑结尾。 - user1514879
在一个更复杂的程序中,可以使用关闭挂钩来调用一些清理代码,该代码可能对记录此清理结果感兴趣。 - Thomas
1
如果应用程序逻辑内部调用了System.exit(1)之类的内容,finally块将不会被执行。据我所见,@Marianna的问题只能通过使用外部监视/日志记录工具来可靠地解决... - Alexander Radchenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接