【问题标题】:Redirect slf4j to string将 slf4j 重定向到字符串
【发布时间】:2011-08-15 13:54:57
【问题描述】:

如何配置 slf4j 以将所有记录的信息重定向到 Java 字符串?

这有时在单元测试中很有用,例如测试加载 servlet 时不打印警告,或确保从未使用过禁止的 SQL 表。

【问题讨论】:

  • 我已经在单元测试中完成了日志重定向,另一种可能的方法是将日志 Records 本身附加到 List,而不是将格式化的消息附加到 String/StringBuffer。
  • @AlistairIsrael,好主意,谢谢。用List<Record> 来推理它们会更容易。

标签: java logging slf4j


【解决方案1】:

有点晚了,不过还是……

由于在单元测试时日志配置应该很容易替换,您可以配置为通过标准输出进行日志记录,然后在执行日志记录主题之前捕获它。 然后将记录器设置为对除被测对象以外的所有人保持静音。

@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);
}

【讨论】:

【解决方案2】:

在我看来,您有两个选择。

首先,您可以实现一个自定义 Appender(取决于您使用的 slf4j 实现),它只是将每个记录的语句附加到 StringBuffer。在这种情况下,您可能必须持有对 StringBuffer 的静态引用,以便您的测试类可以访问它。

其次,您可以编写自己的 ILoggerFactory 和 Logger 实现。同样,您的 Logger 只会将所有消息附加到内部 StringBuffers,尽管在这种情况下您可能有多个缓冲区,每个日志级别一个。如果您这样做,您将有一种简单的方法来检索 Logger 实例,因为您拥有分发它们的工厂。

【讨论】:

    【解决方案3】:

    将所有日志重定向到单独的日志文件不应该有意义吗?这样您就拥有了所需的控制权(您可以在运行测试之前删除日志文件并随时检查文件是否已创建)而不会失去日志记录的好处(将输出重定向到字符串可能会导致内存泄漏并且是性能较差)

    【讨论】:

    • 它只能在单元测试中运行。而且不接触磁盘的单元测试要快得多。
    【解决方案4】:

    这是登录控制台的简单方法:

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

    【讨论】:

      【解决方案5】:

      不完全是你在做什么,但我写了一个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);
      }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-06-22
        • 1970-01-01
        • 2011-07-22
        • 1970-01-01
        • 1970-01-01
        • 2014-10-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多