【问题标题】:Spring's @PreDestroy results in logging randomly not loggingSpring 的 @PreDestroy 导致随机记录而不是记录
【发布时间】:2015-08-06 05:42:06
【问题描述】:

我正在使用 Spring,并在终止时让 @PreDestroy 清理 bean。我不明白为什么日志记录有时会随机成功,而另一些则失败。

// Using Log4j2
Logger log = LogManager.getLogger(MyClass.class);

@PreDestroy
public void close() {
    log.warn("Test");
}

有时我什么也得不到(没有记录“测试”),有时我会得到:

[13:48:44] INFO  MyClass: Test

如果我在 close() 方法中包含System.out.println("Is this run?");,它将始终打印。

我实际上不确定发生了什么。我不知道是不是因为JVM正在关闭并且记录器被杀死......但我认为这会引发某种异常?

请注意,日志记录到文件+标准输出,我不知道这是否会影响任何事情。其他无数行代码的日志记录工作正常,但不是这个。

注意:如果它最终成为这个特定的库,我愿意切换日志库。

编辑:MyClass 将是 spring.xml 文档中的一个 bean。

【问题讨论】:

  • 你在使用异步日志吗?
  • @KErlandsson 我不知道有这样的选择,我很确定我没有。谷歌搜索后,我可以告诉你,我的配置文件中没有任何地方 "immediateFlush="false" 行,更不用说任何地方的命令了,如果有帮助的话。
  • 你的bean的作用域是什么?
  • @Skizzo 是单身

标签: java spring logging


【解决方案1】:

与@Devon_C_Miller 的答案完全相同,但已更新以反映最新版本的 log4j2

public class MonolithApplication {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.refresh();
    Runtime.getRuntime().addShutdownHook(new Log4j2AwareShutdownHook(context));
  }
}

class Log4j2AwareShutdownHook extends Thread {
  private AbstractApplicationContext ctx;
  Log4j2AwareShutdownHook(AbstractApplicationContext ctx) {
    this.ctx = ctx;
  }
  public void run() {
    ctx.close();
    LogManager.shutdown();
  }
}

并且在 log4j2 配置中

<?xml version="1.0" encoding="UTF-8"?>
<Configuration shutdownHook="disable">
    <Properties>
        <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xEx</Property>
        <Property name="LOG_LEVEL_PATTERN">%5p</Property>
        <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
        <Property name="CONSOLE_LOG_PATTERN">%d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} %pid --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.acme" level="debug"/>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

【讨论】:

    【解决方案2】:

    我认为归根结底,来自Runtime.addShutdownHook:

    当虚拟机开始其关闭序列时,它将以一些未指定的顺序启动所有已注册的关闭挂钩,并让它们同时运行。

    所以,只要 LogManager 和 Spring IOC 容器都被 JVM 关闭钩子关闭,就无法确保消息会被记录。如果先关闭 LogManager,则消息丢失。如果先关闭 IOC 容器,则记录消息。

    如果您在 JEE 容器中运行,您可能几乎无法更改它。

    但是,如果您在独立环境中运行,则可以将 shutdownHook="disable" 添加到 Log4j 2 &lt;configuration&gt; 标记中。这可以防止 Log4j 2 注册它自己的关闭挂钩。然后,您注册自己的关闭挂钩,而不是调用ctx.registerShutdownHook()(关闭 IOC 的推荐方法)。比如:

    class MyShutdownHook extends Thread {
        private AbstractApplicationContext ctx;
        public MyShutdownHook(AbstractApplicationContext ctx) {
            this.ctx = ctx;
        }
        public void run() {
            ctx.close();
            Set<LoggerContext> contexts = new HashSet<>();
            for (Logger logger : LoggerContext.getLoggers()) {
                contexts.add(logger.getContext());
            }
            for (LoggerContext ctx : contexts) {
                Configurator.shutdown(LogManager.getContext());
            }
        }
    }
    
    AbstractApplicationContext ctx = /* create context */
    Runtime.getRunTime().addShutdownHook(new MyShutdownHook(ctx);
    

    更新:更正了关闭 Log4j2 的过程。
    警告:我远离我通常的构建机器,所以我没有编译这个,但我相信它击中了正确的 API。

    【讨论】:

    • 到目前为止看起来不错,尽管 Log4j2 api 中没有适合我的 LogManager.shutdown() 函数。目前,由于我有一个标准输出和文件附加程序,我担心我会让它们保持打开状态。有没有办法解决这个问题?如果是这样,这看起来可能是正确的答案,并且会获得赏金。
    • @Water:我认为我有正确的 API,但我没有手边的构建环境,所以我还没有真正编译过这个。
    • 这对我有用,遗憾的是,直到我尝试了stackoverflow.com/questions/17400136/…,我才看到你的答案——但是,你的答案确实包含了那里的内容。感谢您的解决方案,到目前为止它看起来很棒。
    • 从 Log4j 2.6 开始,LogManager 具有关闭方法。
    【解决方案3】:

    我没有足够的声誉来添加评论,所以我会在这里回答:

    根据 JavaEE API javadoc:

    • 在非拦截器类上定义的方法必须具有以下签名:void ()
    • 应用 PreDestroy 的方法可以是 public、protected、package private 或 private。
    • 方法不能是静态的。
    • 方法可能是最终的。
    • 如果方法抛出未经检查的异常,则忽略该异常,但 EJB 可以处理异常的 EJB 除外。

    检查是否在其他地方“静默”抛出异常,这可能是一个原因。

    【讨论】:

    • 它发生在我身上,当在 spring 数据存储库函数调用上执行疯狂的 try-catch 块时,只是保存一个对象,并且在不同的对象和函数调用中引发了未经检查的异常触发了 catch ,乍一看他们似乎没有关系,但不知何故,这就是为什么我建议这是原因。
    • 我将我的代码包装在一个 try/catch 中,它显示没有抛出异常,并且代码仍然会随机打印或打印失败。我很欣赏这个答案,但因为我不知道。
    猜你喜欢
    • 1970-01-01
    • 2021-03-30
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 2013-05-31
    • 2021-11-11
    • 2011-02-14
    相关资源
    最近更新 更多