java 在测试期间捕获Log4J(2)日志条目的最佳方法是什么?

hc2pp10m  于 2023-05-15  发布在  Java
关注(0)|答案(2)|浏览(141)

我正在写一个日志测试框架,我打算在其中支持多个日志后端和测试工具。在每个测试工具中,都有一个安装/拆卸周期,这使我有机会将某种形式的“日志捕获”示例注入到日志系统中。
在JDK日志记录中,很容易为特定的日志记录器安装一个新的处理程序来捕获它看到的所有内容,但在Log4J 2中,我很难获得具有相同行为的东西。
我已经阅读了https://logging.apache.org/log4j/log4j-2.1/manual/architecture.html中的所有文档,我完全理解为什么编程式配置日志是一个坏主意,但对于测试来说,这确实是唯一合理的方法。
在Log4J中,我猜添加一个Appender是最好的方法,但是将Appender添加(然后删除)到配置层次结构中的正确位置是很困难的。
特别是,我需要能够为一个包添加一个Appender(即在测试代码中不存在记录器),所以我想这样说:
“获取此名称的记录器配置,如果不存在则创建它”
相反,我发现的各种配置获取器只会搜索层次结构并找到父配置(在我的情况下通常是根配置)。
我的要求是:
1.在config命名空间中的特定位置添加捕获appender。
1.让appender在config名称空间中的该点或该点以下捕获所有日志记录器的日志。
1.有一个健壮的方法来删除appender当我完成。
1.不影响实际的日志级别或现有日志记录器配置的任何其他部分。
我已经设法为一个现有的记录器安装了一个Appender,但是任何“子”记录器似乎都不使用它。

// The "loggerName" can be a package name and no logger need exist for it before this point.
    Logger logger = (Logger) LogManager.getLogger(loggerName);
    Configuration configuration = ((LoggerContext) LogManager.getContext()).getConfiguration();
    configuration.addLoggerAppender(logger, appender);
    LoggerConfig config = configuration.getLoggerConfig(loggerName);
    // A callback to remove the appendr after the test.
    return () -> {
      try {
        // I don't understand why there's no way to remove the append instance via its reference,
        // so I hacked it to use a random name string since I don't want to get into issues
        // with multiple tests running in parallel. I'd like a solution that avoids this.
        config.removeAppender(probablyUniqueAppenderName);
      } catch (RuntimeException e) {
        // Ignored on close().
      }
    };

我注意到的一件不幸的事情是,在其他尝试中,我在不同的地方看到了一个日志记录器的现有日志级别被添加一个Appender的看似简单的操作修改了。这显然是不可接受的,我试图做什么,我不知道为什么这是可取的行为(当一个新的配置是创建它继承父配置的水平,而不是使用任何日志记录器上已经设置的日志水平)。
我也不相信我问的问题与我在这里看到的关于Log4J的任何一个问题(通常是几年前的问题)明显相同,所以请不要只是假设我应该使用其中一个(我试过的那些不起作用)。
编辑:我首先尝试的另一个代码片段(也不起作用)是:

Logger logger = (Logger) LogManager.getLogger(loggerName);
    // This *changes* the logger's level because there's no existing config.
    // For example, existing loggers set to TRACE level become set to ERROR after
    // this call because the act of creating a new config for this logger inherits
    // from the parent (root) instead of using the level set on the instance.
    logger.addAppender(appender);
    return () -> {
      try {
        logger.removeAppender(appender);
      } catch (RuntimeException e) {
        // Ignored on close().
      }
    };

编辑2:我还研究了通过将现有配置与我以编程方式创建的自定义配置合并来重置配置,但在尝试时有几件事不起作用(特别是,CompositeConfiguration类不允许我合并Configuration接口的示例,只允许合并AbstractConfiguration类的子类(这不是LogManager返回的)。
编辑3:我现在找到了https://logging.apache.org/log4j/2.x/manual/customconfig.html#programmatically-modifying-the-current-configuration-after-initi,它看起来很有希望(如果有点复杂),但文档没有解释:

  1. AppenderRef的作用,或者为什么需要它
    1.如何在测试完成后撤消任何修改以重新安装原始配置(尽管我可能猜到了)。
mcvgt66p

mcvgt66p1#

我发现使用Configurator(内部API)可以很好地更改日志记录器的级别,而不会在添加appender时将其还原。
所以在我的测试中,我们不使用:

  • logger.setLevel(Level.FOO)

我现在用途:

  • Configurator.setLevel(logger, Level.FOO)

然后,为了在测试期间添加/删除appender,我可以通过logger.addAppender()/removeAppender()(使用“核心”API)使用我最初的想法。
基本上,我从中发现的是Log4J对编程配置有非常强烈的意见(这很好,它们大多与我的一致),但没有很好地解释一些日志记录器状态是暂时的,并且会被配置上的任何操作的副作用覆盖。
因此,如果您需要进行这种编程操作,那么您最终将徒劳无功。这不是我想要的100%,但它确实帮助我前进。
我还不得不假设他们永远不会真正删除配置器(Hyrum定律可能意味着它太常用了,在这一点上是可移动的)。

e0bqpujr

e0bqpujr2#

你的代码中有几个问题:
1.正如javadoc中所述,LogManager#getContext()通常不会给予与LogManager#getLogger(...)相同的日志记录器上下文:

警告-此方法返回的LoggerContext可能不是用于为调用类创建Logger的LoggerContext。

使用LogManager.getContext(false)Logger.getContext()代替。
1.完成配置更改后,需要调用LoggerContext#updateLoggers()来提交配置更改。
如您所言,推荐编程配置。从语义上讲,一个较小的版本更改可能会破坏它。但是,在当前版本(2.20.0)上,您可以用途:

final org.apache.logging.log4j.spi.LoggerContext c = LogManager.getContext(false);
if (c instanceof LoggerContext) {
    final LoggerContext context = (LoggerContext) c;
    final LoggerConfig oldConfig = context.getConfiguration().getLoggerConfig(loggerName);
    final LoggerConfig newConfig;
    // Add a new logger config or use the existent one
    if (loggerName.equals(oldConfig.getName())) {
        newConfig = oldConfig;
    } else {
        newConfig = new LoggerConfig(loggerName, level, true);
    }
    // Actually adds an AppenderRef
    newConfig.addAppender(appender, null, null);
    // Sets the level of the LoggerConfig
    newConfig.setLevel(level);
    context.getConfiguration().addLogger(loggerName, newConfig);
    context.updateLoggers();
}

**备注:**您的设置可能无法用于并行测试。参见this mailing list thread以获得一些替代方案。

相关问题