Динамически добавлять appender с slf4j и log4j2

Я хочу динамически создать приложение и добавить его в журнал. Однако это не представляется возможным с slf4j. Я могу добавить свой appender в log4j logger, но затем я не могу получить регистратор с помощью 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));
      }

  }

Что я могу сделать, чтобы заставить это работать? Возможно, я приближаюсь к этому с неправильного угла, но я бы хотел, чтобы не создавать свой собственный класс журнала. Любая помощь приветствуется.

Ответ 1

Доступ и управление log4j2 по slf4j по коду/во время выполнения:

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();
        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

Ответ 2

Daniele, ListAppender существует в Log4J-2.0 (пакет org.apache.logging.log4j.test.appender). Он является частью дистрибутива, но он находится в банке log4j-core-tests. В основном он используется для тестов 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>

Ответ 3

Я думаю, у вас такой же сценарий, как у нас. Более сложное ведение журнала в производстве, но более простое при тестировании 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.