Skip to content

Testing logs

Guicey provide RecordLogsHook for capturing logged messages.

Important

Works only with logback (default dropwizard logger).

For example, suppose some service logs some technical hint at some condition:

public class Service {
    private final Logger logger = LoggerFactory.getLogger(Service.class);

    public void foo() {
        ...
        if (someCondition) {
            logger.info("Some technical note");
        }
    }
}

Testing that technical note actually logged:

public class LogsTest {

    @Test
    public void testLog() {
        RecordedLogHook hook = new RecordedLogHook();
        // start recorder registration
        RecordedLogs logs = hook.record()
                // listen only for one logger
                .logger(Service.class)
                // start recording all logger messages of level INFO and above
                .start(Level.INFO);

        // run application with hook
        TestsSupport.build(MyApp.class)
                .hooks(hook)
                .runCore(injector -> {
                    // run method foo (assume log message must appear)
                    injector.getInstance(Service.class).foo();
                });

        // one log recorded
        Assertions.assertEquals(1, logs.count());
        // log message appears
        Assertions.assertEquals(1, logs.containing("Some technical note").count());
        // alternative: last message was a technical hint
        Assertions.assertEquals("Some technical note", logs.lastMessage());
    }
}

Warning

Such tests could not be run in parallel because logger configuration is global

Registration

You can register as many recorders as you like. Each recorder could listen one or more loggers.

To listen all warnings (root logger):

hook.register().start(Level.WARN);

To listen all loggers in package:

hook.register().loggers("com.my.package").start(Level.WARN);

To listen exact class and package:

hook.register()
        .loggers(SomeClass.class)
        .loggers("com.my.package")
        .start(Level.INFO);

Implementation details

Each recorder registration leads to logging appender registration for a target logger (or multiple loggers).

If the current logger configuration is higher than required, then logger would be re-configured. For example, if default logger level is INFO and recorder requires TRACE messages, then it would change logger configuration to receive required messages.

Tip

Recorder might be used just to enable required logs, without application logging configuration. This is very useful in tests (to enable DEBUG or TRACE messages for exact service (or package)): hook.register().loggers(MyClass.class).start(Level.TRACE)

During application startup dropwizard resets loggers two times and hook would re-attach appenders to compensate it. You should be able to record all messages from application startup, except logs from dropwizard bundles, registered BEFORE GuiceBundle.

If required, actual recorder object is accessible with RecordedLog#getRecorder(): it provides attach() and destroy() methods (for attaching and detaching appender). The hook would call these methods automatically.

Querying

RecordedLogs used to query recorded logs. Root object always contains all recorded events (for configured loggers).

Recorded logs are accessible in form of raw event (ILoggingEvent) or pure string message (formatted messages with arguments).

Method Description Example
count() Recorded logs count assertEquals(1, logs.count())
empty() Events recorded assertFalse(logs.empty())
events() All recorded events List<ILoggingEvent> events = logs.events()
messages() Messages of all recorded messages List<String> messages = logs.messages()
has(loggerName) Checks if messages from target logger available assertTrue(logs.has(Service.class)), assertTrue(logs.has("com.some"))
has(level) Checks if messages of level available assertTrue(logs.has(Level.WARN))
lastEvent() Last recorded event or null assertEquals(Level.WARN, logs.lastEvent().getLevel())
lastMessage() Message of the last recorded event or null assertEquals("Something", logs.lastMessage())

Also, logs could be filtered:

Filter Description Example
level(level) Select events with level logs.level(Level.WARN)
logger(loggerName) Select events of required loggers logs.logger(Service.class), logs.logger("com.some")
containing(String) Events where messages contains provided string logs.containing("Substring")
matching(regex) Events where messages match provided regex logs.matching("something \\d+")
select(predicate) General events matching predicate logs.select(event -> event.getLevel().equals(Level.TRACE))

Filters return another matcher object where all verification and filter methods above could be called (multiple filters could be applied consequently).

For example, verify count of all messages containing string:

assertEquals(1, logs.containing("Something").count());

Or filtering by logger and level (if recorder records multiple loggers):

assertEquals(12, logs.logger("com.some.package").level(Level.WARN).count())

Clear recordings

Recorded logs could be cleared at any time (to simplify exact method logs matching):

// clear logs, recorded during application startup
logs.clear();
// call method
service.foo();
// verify logs appeared during method call
assertEquals(1, logs.containing("Something").count());

// clear again to check logs of another method
logs.clear();
service.boo();
...

Hook methods

Logs from all recorders could be cleared with hook:

hook.clearLogs()

To detach all registered appenders:

hook.destroy()

Note

Dropwizard resets loggers during startup so manual detach should not be required (to avoid keeping stale appenders between tests).