2011-08-15 11 views
5

Jak skonfigurować slf4j, aby przekierować wszystkie zapisane informacje na ciąg znaków Java?Przekieruj slf4j na ciąg

Jest to czasami przydatne w testach jednostkowych, np. aby testować, żadne ostrzeżenia nie są drukowane podczas ładowania serwletu lub aby upewnić się, że zabroniona tabela SQL nigdy nie jest używana.

+1

Zrobiłem przekierowanie dziennika w testach jednostkowych i innym możliwym rozwiązaniem jest po prostu dołączyć dziennik 'Record's się do' List', zamiast dołączyć sformatowane wiadomości do String/StringBuffer. –

+0

@AlistairIsrael, dobry pomysł, dzięki. Łatwiej będzie o nich myśleć jako o "Liście ". –

Odpowiedz

3

Jak widzę, masz dwie możliwości.

Najpierw możesz zaimplementować niestandardowego Appendera (w zależności od implementacji slf4j, z której korzystasz), która po prostu dołącza każde zalogowane wyrażenie do StringBuffer. W takim przypadku prawdopodobnie będziesz musiał trzymać statyczne odwołanie do StringBuffer, aby twoje klasy testowe mogły uzyskać do niego dostęp.

Po drugie możesz napisać własną implementację ILoggerFactory i Logger. Znowu twój Logger po prostu dołączy wszystkie wiadomości do wewnętrznych StringBufferów, chociaż w tym przypadku prawdopodobnie będziesz miał wiele buforów, po jednym dla każdego poziomu dziennika. Jeśli zrobiłeś to w ten sposób, będziesz miał łatwy sposób na odzyskanie instancji Loggera, ponieważ byłbyś właścicielem fabryki, która je dystrybuowała.

0

Nie ma sensu przekierowywać wszystkich dzienników, aby oglądać oddzielny plik dziennika? W ten sposób masz odpowiednią kontrolę (możesz usunąć plik dziennika przed uruchomieniem testu i sprawdzić, czy plik został utworzony w dowolnym momencie) bez utraty korzyści z logowania (przekierowanie wyjścia do łańcucha może spowodować przecieki pamięci i mniej wydajny)

+0

Jest przeznaczony do uruchamiania tylko w testach jednostkowych. A testy jednostkowe, które nie dotykają dysku, są znacznie szybsze. –

3

Trochę późno, ale nadal ...

Jak konfiguracji rejestrowania powinny być łatwe do wymiany, gdy testowanie jednostkowe, można skonfigurować tak, aby zalogować się na stdout i uchwycić, że przed wykonaniem przedmiotu rejestrowania. Następnie ustaw rejestrator jako cichy dla wszystkich prócz testowanego przedmiotu.

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

a jeśli stosowane slf4j nad log4j w testach, proste 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 

Lub jeśli niechętnie konfiguracja jako zewnętrzne zależności w testach jednostkowych, następnie programowo skonfigurować 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); 
} 
+0

Napisałem prosty moduł do tego, dostępny w maven central jako [io.earcam.utilitarian.log.slf4j] (http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io. earcam.utilitarian% 22% 20AND% 20a% 3A% 22io.earcam.utilitarian.log.slf4j% 22) i lekko udokumentowane tutaj https://utilitarian.earcam.io/log/slf4j/ – earcam

0

jest to prosty sposób, aby zalogować się do konsoli:

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

Nie do końca dokładnie to, co robisz, ale napisałem LogInterceptingTestHarness, który umożliwia potwierdzanie określonych instrukcji dziennika. W podobny sposób możesz go użyć (lub coś podobnego), aby stwierdzić, że nic nie zostało zarejestrowane na pewnym poziomie.

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); 
} 
} 
Powiązane problemy