使用slf4j和log4j2动态添加appender

26

我想动态创建一个appender并将其添加到一个logger中。然而,使用slf4j似乎不可能实现这一点。我可以将我的appender添加到log4j logger中,但是无法使用slf4j LoggerFactoy检索到该logger。

我想做的事情:我创建了一个测试类(不是jUnit测试),并在构造函数中传递一个logger供测试类使用。每个测试类实例都需要自己的logger和appender,以便将日志保存以后用于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));
      }

  }

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


2
嗨Daniele,你搞定这个了吗?如果搞定了,你能发一下你的解决方案吗?谢谢!Cheers Mark. - mark
当时我没有其他想法,只能编写一个实现Logger接口的自定义类。虽然这不是我真正想要的,但它完成了工作。 不幸的是,我现在正在处理另一个项目,没有时间审查这里的其他解决方案。 - Daniele Torino
4个回答

8

我认为你们的情况与我们类似。在生产环境下需要更复杂的日志记录,但在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相同的上下文。

3
需要注意的是,在调用getContext时需要传递"false"参数,否则似乎无法获取与slf4j相同的上下文。哦,这真的非常重要! - Cedric Reichenbach
可能需要澄清getLoggerConfig("")是关键,而getRootLogger()并不能解决问题。 - Charlie
请注意,现有的日志记录器配置也会被应用,因此在到达根记录器之前被过滤掉的消息将会丢失。请更改您的日志配置或在此处进行修改。 - Benjamin Peter

8

通过代码/运行时访问和操作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

这篇文章介绍了如何在Log4j 2中自定义配置。通过实现特定的接口和使用相应的注解,您可以创建自己的组件,如Appenders、Filters和Layouts。在自定义配置中使用这些组件,可以更好地控制日志记录过程。总的来说,自定义配置可以让您更加灵活地管理和记录应用程序日志。


4
这个方法不能和slf4j一起使用,你需要调用getContext方法并加上false参数,否则会得到一个不同的上下文。 - lqbweb
1
你如何添加Appender?slf4j不关心我何时将appenderObj添加到配置中。实际上,这里的所有内容都引用了log4j,而你只能从slf4j logger对象中打印东西。我不确定这个答案是否认识到了slf4j和log4j之间的区别。 - Makan
1
同意,几年后这仍然是我找到的唯一来源,但它并不起作用。 - Johnyb

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)

Scala:
    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) 

0

在Log4J-2.0中存在一个ListAppender(包名为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>

1
谢谢提供信息。创建一个appender相当容易,这不是我的问题。问题在于动态创建logger和appender,然后通过slf4j访问该logger。 - Daniele Torino

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