Перенаправить slf4j в строку - PullRequest
5 голосов
/ 15 августа 2011

Как я могу настроить slf4j для перенаправления всей зарегистрированной информации в строку Java?

Это иногда полезно в модульных тестах, например, чтобы проверить, не выводятся ли предупреждения при загрузке сервлета, или чтобы убедиться, чтозапрещенная таблица SQL никогда не используется.

Ответы [ 5 ]

4 голосов
/ 16 февраля 2016

Немного поздно, но все же ...

Поскольку конфигурации журналирования должны легко заменяться при модульном тестировании, вы можете просто настроить регистрацию через стандартный вывод, а затем перехватить ее перед выполнением темы ведения журнала. Затем установите регистратор в режим молчания для всех, кроме испытуемого.

@Test
public void test()
{
    String log = captureStdOut(() -> {
        // ... invoke method that shouldn't log
    });
    assertThat(log, is(emptyString()));
}



public static String captureStdOut(Runnable r)
{
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PrintStream out = System.out;
    try {
        System.setOut(new PrintStream(baos, true, StandardCharsets.UTF_8.name()));
        r.run();
        return new String(baos.toByteArray(), StandardCharsets.UTF_8);
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException("End of the world, Java doesn't recognise UTF-8");
    } finally {
        System.setOut(out);
    }
}

А если в тестах использовать slf4j вместо log4j, просто log4j.properties :

log4j.rootLogger=OFF, out
log4j.category.com.acme.YourServlet=INFO, out
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%-5p %c{1}:%L - %m%n

Или, если вы не хотите настраивать конфигурацию как внешние зависимости в модульных тестах, то программно настройте log4j:

//...

static final String CONSOLE_APPENDER_NAME = "console.appender";

private String pattern = "%d [%p|%c|%C{1}] %m%n";
private Level threshold = Level.ALL;
private Level defaultLevel = Level.OFF;

//...

public void configure()
{
    configureRootLogger();
    configureConsoleAppender();
    configureCustomLevels();
}


private void configureConsoleAppender()
{
    ConsoleAppender console = new ConsoleAppender();
    console.setName(CONSOLE_APPENDER_NAME);
    console.setLayout(new PatternLayout(pattern));
    console.setThreshold(threshold);
    console.activateOptions();
    Logger.getRootLogger().addAppender(console);
}


private void configureRootLogger()
{
    Logger.getRootLogger().getLoggerRepository().resetConfiguration();
    Logger.getRootLogger().setLevel(defaultLevel);
}
3 голосов
/ 15 августа 2011

На мой взгляд, у вас есть два варианта.

Сначала вы можете реализовать пользовательский Appender (в зависимости от используемой реализации slf4j), который просто добавляет каждый зарегистрированный оператор в StringBuffer.В этом случае вам, вероятно, придется хранить статическую ссылку на ваш StringBuffer, чтобы ваши тестовые классы могли получить к ней доступ.

Во-вторых, вы можете написать собственную реализацию ILoggerFactory и Logger.И снова ваш Logger просто добавит все сообщения во внутренние StringBuffers, хотя в этом случае у вас, вероятно, будет несколько буферов, по одному для каждого уровня журнала.Если бы вы сделали это таким образом, у вас был бы простой способ получить экземпляры Logger, так как у вас была бы фабрика, которая их распространяла.

0 голосов
/ 16 декабря 2017

Не совсем то, что вы делаете, но я написал LogInterceptingTestHarness, который позволяет утверждать определенные операторы журнала.Вы также можете использовать его (или что-то в этом роде), чтобы утверждать, что ничего не записано на определенном уровне.

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.List;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.junit.After;
import org.junit.Before;
import org.mockito.ArgumentCaptor;

import lombok.Getter;

/**
 * Use this class to intercept logs for the purposes of unit testing     log output.
 * <p>
 * On {@link Before} of the unit test, call {@link #initHarness(Class, Level)} or {@link #initHarness(Class, Level, String)} to get a new harness and hold onto reference to it in a class-level
 * variable of your unit test
 * <p>
 * On {@link After} of the unit test, you MUST call {@link #teardown()} in order to remove the mocked {@link #appender}
 *
 * @author jeff.nelson
 *
 */
@Getter
public class LogInterceptingTestHarness {

private final Appender appender;
private final ArgumentCaptor<LogEvent> logEventCaptor;
private final Logger itsLogger;

private LogInterceptingTestHarness(Class<?> classInterceptLogsFor, Level logLevel, String appenderName) {
    logEventCaptor = ArgumentCaptor.forClass(LogEvent.class);

    appender = mock(Appender.class);
    doReturn("testAppender").when(appender).getName();
    doReturn(true).when(appender).isStarted();

    itsLogger = (Logger) LogManager.getLogger(classInterceptLogsFor);
    itsLogger.addAppender(appender);
    itsLogger.setLevel(logLevel);
}

public void teardown() {
    itsLogger.removeAppender(appender);
}

public List<LogEvent> verifyNumLogEvents(int numEvents) {
    verify(appender, times(numEvents)).append(logEventCaptor.capture());
    return logEventCaptor.getAllValues();
}

public LogEvent verifyOneLogEvent() {
    return verifyNumLogEvents(1).get(0);
}

public void assertLoggedMessage(String message) {
    assertLogMessage(message, logEventCaptor.getValue());
}

public void assertLoggedMessage(String message, int messageIndex) {
    assertLogMessage(message, logEventCaptor.getAllValues().get(messageIndex));
}

public static void assertLogMessage(String message, LogEvent event) {
    assertEquals(message, event.getMessage().getFormattedMessage());
}

public static LogInterceptingTestHarness initHarness(Class<?> classInterceptLogsFor, Level logLevel) {
    return initHarness(classInterceptLogsFor, logLevel, "testAppender");
}

public static LogInterceptingTestHarness initHarness(Class<?> classInterceptLogsFor, Level logLevel, String appenderName) {
    return new LogInterceptingTestHarness(classInterceptLogsFor, logLevel, appenderName);
}
}
0 голосов
/ 01 августа 2017

Это простой способ входа в консоль:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.BasicConfigurator;
import ch.qos.logback.classic.LoggerContext;

private void LogToConsole() {
    BasicConfigurator bc = new BasicConfigurator();
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
    lc.reset();
    bc.configure(lc);
}
0 голосов
/ 15 августа 2011

Не имеет ли смысла перенаправлять все журналы для просмотра в отдельный файл журнала?Таким образом, вы получаете нужный элемент управления (вы можете удалить файл журнала перед запуском теста и проверить, был ли файл создан в любой момент), не теряя преимущества ведения журнала (перенаправление вашего вывода в строку может привести к утечке памяти именее производительный)

...