Details
-
Improvement
-
Resolution: Unresolved
-
Major
-
None
-
None
-
None
Description
My understanding is the main usage of ListAppender is for writing tests that verify expected logs are produced.
Currently, setting this up involves a lot of boilerplate:
package com.mypackage; import static org.junit.jupiter.api.Assertions.assertTrue; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; public class TestMyClass { static ListAppender<ILoggingEvent> listAppender = new ListAppender<>(); static { listAppender.start(); ((Logger) LoggerFactory.getLogger(MyClass.class)).addAppender(listAppender); ((Logger) LoggerFactory.getLogger(AnotherClass.class)).addAppender(listAppender); } @Test public void myTest() { MyClass.doThingThatLogs(); int lastLogIndex = listAppender.list.size() - 1; // Note: When it fails, it unhelpfully says "False is not True". assertTrue( listAppender .list .get(lastLogIndex) .getFormattedMessage() .contains("My expected log")); } }
I believe this is a common use case for ListAppender, and the class should be improved to facilitate doing this kind of thing better and with a lot less code.
Off the top of my head, the following improvements could be made:
- Give ListAppender a constructor that takes in a vararg of classes or Strings to list itself to, and have it start itself.
- Give ListAppender a size method that skips the need to go to the underlying list.
- Give ListAppender a get method that skips asking the underlying list, and also support negative indexes to easily facilitate getting logs from the end rather than the start.
- Give ListAppender an assertContains(int index, String... substrings) method that facilitates easily asserting that specific substrings showed up in the logs, and generates a good error message of what log was actually found vs which expected substrings were missing.
With those proposals, the initial example test could be reduced down to just:
package com.mypackage; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.Test; public class TestMyClass { static ListAppender<ILoggingEvent> listAppender = new ListAppender<>(MyClass.class, AnotherClass.class); @Test public void myTest() { MyClass.doThingThatLogs(); // Note: If the log is missing, the failure message says what it actually found. listAppender.assertContains(-1, "My expected log"); }
I wasn't sure if people maybe thought this should go someplace different (maybe make a subclass of ListAppender?) or if it doesn't belong in Logback. I'm guessing the value:cost ratio is quite good here. Do others have suggestions to improve the proposed method signatures or have additional suggestions of what would be valuable here?
Getting somewhat off-topic, it could utilize of try-with-resources to easily facilitate start/stop and making sure logs aren't leaked between tests.
I'm happy to make a PR that adds the code in + unit tests to cover it, if maintainers think this is a good idea at all.