Динамически добавлять приложение с помощью slf4j и log4j2

Я хочу динамически создать приложение и добавить его в регистратор. Однако это кажется невозможным с slf4j. Я могу добавить свой аппендер в регистратор log4j, но тогда мне не удается получить регистратор с помощью LoggerFactoy slf4j.

Что я хочу сделать: я создаю тестовый класс (не тест 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));
      }

  }

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


person Daniele Torino    schedule 04.04.2014    source источник
comment
Привет, Даниэле, у тебя получилось? Если бы вы это сделали, вы бы опубликовали свое решение? Здоровья Марк.   -  person mark    schedule 06.08.2015
comment
В то время у меня не было другой идеи, кроме как написать собственный класс, реализующий интерфейс Logger. Это не совсем то, что я искал, но это сделало работу. К сожалению, сейчас я работаю над другим проектом, и у меня не было времени просматривать здесь другие решения.   -  person Daniele Torino    schedule 06.08.2015


Ответы (4)


Доступ и управление 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

person SilentMax    schedule 07.11.2014
comment
это не будет работать с slf4j, вам нужно вызвать getContext как getContext(false), иначе вы получите другой контекст. - person lqbweb; 04.08.2016
comment
как вы собираетесь добавить Appender? slf4j не волнует, когда я добавляю appenderObj в конфигурацию. на самом деле все здесь ссылается на log4j, и вы печатаете только материал из объекта регистратора slf4j. Я не уверен, распознает ли этот ответ разницу между slf4j и log4j. - person Makan Tayebi; 10.03.2020
comment
Согласитесь, несколько лет спустя это все еще единственный источник, который я нашел для этого, и он не работает. - person Johnyb; 13.11.2020

Я думаю, что у вас похожая ситуация, как у нас. Более сложное ведение журнала в продакшене, но более простое во время тестирования 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());
}

Важно отметить, что нам нужно передать «false» при вызове getContext, иначе он не получит тот же контекст, что и slf4j.

person lqbweb    schedule 04.08.2016
comment
Важно отметить, что нам нужно передать false при вызове getContext, так как в противном случае он, похоже, не получит тот же контекст, что и slf4j. О, это действительно важно! - person Cedric Reichenbach; 26.07.2018
comment
Возможно, стоит уточнить, что getLoggerConfig("") является ключевым, а getRootLogger() не помогает. - person Charlie; 02.09.2019
comment
Обратите внимание, что существующая конфигурация регистратора также применяется, а сообщения, которые отфильтровываются перед попаданием в корневой регистратор, отсутствуют. Измените конфигурацию вашего журнала или измените ее здесь. - person Benjamin Peter; 23.06.2021

Если кто-то еще наткнется на это, с 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) 
person Newt0n    schedule 10.08.2020

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>
person Remko Popma    schedule 07.04.2014
comment
Спасибо Вам за информацию. Создать приложение было довольно легко, это не моя проблема. Проблема заключается в динамическом создании регистратора и приложения, а затем в доступе к этому регистратору через slf4j. - person Daniele Torino; 08.04.2014