【问题标题】:Dynamically add appender with slf4j and log4j2使用 slf4j 和 log4j2 动态添加 appender
【发布时间】:2014-04-04 14:59:55
【问题描述】:

我想动态创建一个附加程序并将其添加到记录器。但是,对于 slf4j,这似乎是不可能的。我可以将 appender 添加到 log4j 记录器,但是我无法使用 slf4j LoggerFactoy 检索记录器。

我想要做什么:我创建一个测试类(不是 jUnit 测试)并在构造函数中传递一个记录器供测试类使用。测试类的每个实例都需要它自己的记录器和附加程序来保存日志,以便以后在 HTML 报告中使用。

我尝试了什么(为简单起见,我创建了一个 jUnit 测试):

  import static org.junit.Assert.assertEquals;

  import java.util.LinkedList;
  import java.util.List;

  import org.apache.logging.log4j.core.LogEvent;
  import org.junit.Test;
  import org.slf4j.helpers.Log4jLoggerFactory;

  import ch.fides.fusion.logging.ListAppender;

  public class ListAppenderTest {

      @Test
      public void test() {

          String testName = "test1";

          // the log messages are to be inserted in this list
          List<LogEvent> testLog = new LinkedList<>();

          // create log4j logger
          org.apache.logging.log4j.core.Logger log4jlogger = (org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager
                                          .getLogger("Test:" + testName);

          // create appender and add it to the logger
          ListAppender listAppender = new ListAppender("Test:" + testName + ":MemoryAppender", testLog);
          log4jlogger.addAppender(listAppender);

          // get the slf4j logger
          org.slf4j.helpers.Log4jLoggerFactory loggerFactory = new Log4jLoggerFactory();
          org.slf4j.Logger testLogger = loggerFactory.getLogger("Test:" + testName);

          // test it
          final String TEST_MESSAGE = "test message";
          testLogger.info(TEST_MESSAGE);

          assertEquals(1, testLog.size());
          LogEvent logEvent = testLog.get(0);
          assertEquals(TEST_MESSAGE, logEvent.getMessage().getFormattedMessage() );
      }

  }

这是我非常基本的附加程序:

 package ch.fides.fusion.logging;

  import java.util.List;

  import org.apache.logging.log4j.core.LogEvent;
  import org.apache.logging.log4j.core.appender.AbstractAppender;

  public class ListAppender extends AbstractAppender {

      private final List<LogEvent> log;

      public ListAppender(String name, List<LogEvent> testLog) {
          super(name, null, null);
          this.log = testLog;
      }

      @Override
      public void append(LogEvent logEvent) {
          log.add(new TestLogEvent(logEvent));
      }

  }

我该怎么做才能让它发挥作用?也许我从错误的角度来处理这个问题,但我想避免创建自己的记录器类。非常感谢任何帮助。

【问题讨论】:

  • 嗨 Daniele,你让这个工作了吗?如果你这样做了,你会发布你的解决方案吗?干杯马克。
  • 当时除了写一个实现Logger接口的自定义类,我没有别的想法。这不是我真正想要的,但它确实完成了工作。不幸的是,我现在正在做一个不同的项目,我没有时间在这里查看其他解决方案。

标签: java logging slf4j log4j2


【解决方案1】:

我认为您的情况与我们的情况相似。生产中更复杂的日志记录,但在 JUnit 测试期间更简单,因此我们可以断言没有错误。

如果您使用的是 log4j2 > 2.4(但随后不支持 Java6),则有使用构建器的更简洁的解决方案,但这是我使用 log4j2 2.3 的解决方案:

@Test
public void testClass() {
    LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);

    Configuration configuration = loggerContext.getConfiguration();
    LoggerConfig rootLoggerConfig = configuration.getLoggerConfig("");
    ListAppender listAppender = new ListAppender("testAppender");

    rootLoggerConfig.addAppender(listAppender, Level.ALL, null);

    new TestClass();    //this is doing writing an error like org.slf4j.LoggerFactory.getLogger(TestClass.class).error("testing this");

    assertEquals(1, listAppender.getEvents().size());
}

需要注意的是,我们需要在调用 getContext 时传递“false”,否则它似乎无法获得与 slf4j 相同的上下文。

【讨论】:

  • "需要注意的是,我们在调用getContext的时候需要传递"false",否则它似乎不能得到和slf4j一样的上下文。"啊,这是这里真的很重要!
  • 可能想澄清getLoggerConfig("") 是关键,而getRootLogger() 不能解决问题。
  • 请注意,现有的记录器配置也会被应用,并且在到达根记录器之前被过滤掉的消息会丢失。更改您的日志配置或在此处修改。
【解决方案2】:

通过代码/在运行时通过 slf4j 访问和操作 log4j2:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2OverSlf4jConfigurator {

    final private static Logger LOGGER = LoggerFactory.getLogger(Log4j2OverSlf4jConfigurator.class);

    public static void main(final String[] args) {
        LOGGER.info("Starting");
        LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
        Configuration configuration = loggerContext.getConfiguration();

        LOGGER.info("Filepath: {}", configuration.getConfigurationSource().getLocation());
        // Log4j root logger has no name attribute -> name == ""
        LoggerConfig rootLoggerConfig = configuration.getLoggerConfig("");

        rootLoggerConfig.getAppenders().forEach((name, appender) -> {
            LOGGER.info("Appender {}: {}", name, appender.getLayout().toString());
            // rootLoggerConfig.removeAppender(a.getName());
        });

        rootLoggerConfig.getAppenderRefs().forEach(ar -> {
            System.out.println("AppenderReference: " + ar.getRef());
        });

        // adding appenders
        configuration.addAppender(null);
    }
}

参考:https://logging.apache.org/log4j/2.x/manual/customconfig.html

【讨论】:

  • 这不适用于slf4j,需要像getContext(false)一样调用getContext,否则会得到不同的上下文。
  • 如何添加 Appender?当我将 appenderObj 添加到配置时,slf4j 不在乎。实际上这里的所有内容都引用了 log4j,而您只打印来自 slf4j 记录器对象的内容。我不确定这个答案是否能识别 slf4j 和 log4j 之间的区别
  • 同意,几年后这仍然是我找到的唯一来源,但它不起作用
【解决方案3】:

如果其他人对此感到困惑,那么对于 slf4j 版本 1.7.26,需要一个构建器模式,该模式与已接受的答案有一些变化。以下实现适用于 java 8+ 和 scala 2.13(与lqbweb 的答案略有不同):

Java:

import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.LoggerContext

LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false)

Configuration configuration = loggerContext.getConfiguration()
LoggerConfig rootLoggerConfig = configuration.getLoggerConfig("")

PatternLayout.Builder layoutBuilder = PatternLayout.newBuilder()
layoutBuilder.withPattern("%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n")

RandomAccessFileAppender.Builder<?> appenderBuilder =
(RandomAccessFileAppender.Builder<?>) RandomAccessFileAppender
      .newBuilder()

appenderBuilder.setLayout(layoutBuilder.build())
appenderBuilder.withBufferedIo(true)
appenderBuilder.setFileName(logFile.getAbsolutePath)
appenderBuilder.setName("OutputDirFileLogger")

RandomAccessFileAppender appender = appenderBuilder.build()

appender.start() // important to make the appender usable instantly 


rootLoggerConfig.addAppender(appender, Level.DEBUG, null)

斯卡拉:

    import org.apache.logging.log4j.LogManager
    import org.apache.logging.log4j.Level
    import org.apache.logging.log4j.core.LoggerContext

    val loggerContext = LogManager.getContext(false).asInstanceOf[LoggerContext]

    val configuration: Configuration = loggerContext.getConfiguration
    val rootLoggerConfig: LoggerConfig = configuration.getLoggerConfig("")

    val layoutBuilder: PatternLayout.Builder = PatternLayout.newBuilder()
    layoutBuilder.withPattern("%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n")

    val appenderBuilder: RandomAccessFileAppender.Builder[_] = RandomAccessFileAppender
      .newBuilder()
      .asInstanceOf[RandomAccessFileAppender.Builder[_]]

    appenderBuilder.setLayout(layoutBuilder.build())
    appenderBuilder.withBufferedIo(true)
    appenderBuilder.setFileName(logFile.getAbsolutePath)
    appenderBuilder.setName("OutputDirFileLogger")

    val appender: RandomAccessFileAppender = appenderBuilder.build()
    appender.start()

    rootLoggerConfig.addAppender(appender, Level.DEBUG, null) 

【讨论】:

    【解决方案4】:

    Daniele,一个 ListAppender 存在于 Log4J-2.0(包org.apache.logging.log4j.test.appender)中。它是发行版的一部分,但它位于 log4j-core-tests jar 中。它主要用于 JUnit 测试。 JUnit 测试源还具有示例配置,显示如何使用此 ListAppender 进行配置。 示例配置如下所示:

    <Configuration status="warn" packages="org.apache.logging.log4j.test">
      <Appenders>
        <List name="MyList">
        </List>
      </Appenders>
      <Loggers>
        <Root level="error">
          <AppenderRef ref="MyList"/>
        </Root>
      </Loggers>
    </Configuration>
    

    【讨论】:

    • 感谢您提供的信息。创建一个 appender 很简单,这不是我的问题。问题是动态创建记录器和附加程序,然后通过 slf4j 访问该记录器。
    猜你喜欢
    • 2019-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多