In continuing to this answer I wrote a unit test to verify that in case of error, the stack will be printed in the log file.
The tested method:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final Logger logger = LoggerFactory.getLogger(getClass());
public long getFq(String fi) {
try {
return calcSomeThing(fi.toLowerCase());
} catch (Exception e) {
logger.error("unable to calculate SomeThing. Error: "
, e);
return -1;
}
}
The unit test:
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.slf4j.LoggerFactory;
@Test
public void getFileQualifier() {
// get Logback Logger
Logger logger = (Logger) LoggerFactory.getLogger(QService.class);
// create and start a ListAppender
ListAppender listAppender = new ListAppender<>();
listAppender.start();
// add the appender to the logger
// addAppender is outdated now
logger.addAppender(listAppender);
// call method under test
QService.getFq(null);
// JUnit assertions
List logsList = listAppender.list;
Assert.assertEquals("unable to calculate SomeThing. Error: ", logsList.get(0)
.getFormattedMessage());
Assert.assertEquals(Level.ERROR, logsList.get(0)
.getLevel());
Assert.assertEquals("java.lang.NullPointerException: null", logsList.get(1)
.getMessage());
Assert.assertEquals(Level.ERROR, logsList.get(1)
.getLevel());
Assert.assertThat("(QService.java", containsString(logsList.get(2)
.getMessage()));
Assert.assertEquals(Level.ERROR, logsList.get(2)
.getLevel());
}
Well, although I can see the stack is indeed printed in the log file, the unit test failed because of the
logsList contains only one item (the first printed line only [unable to calculate SomeThing. Error: ]).
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
Why does it happen and how it can be tested?
EDIT
Answers:
the answers (all are quotes from @Gavin's answer and comment, thank you):
On the first question (Why does it happen) the answer is:
It looks to me that exceptions is stored separately from the message in the log event
On the second question (how it can be tested) the answer is:
to find what you are looking for in the list of log events and can be expressed in a manner suitable to your domain, e.g checking the that a Throwable was logged, perhaps looking in org.apache.log4j.spi.LoggingEvent for appropriate methods
Finally, my code to verify it was:
Assert.assertEquals(logsList.get(0).getThrowableProxy().getClassName(), "java.lang.NullPointerException");
解决方案
This is how I have captured log messages in the past, this is based on an old blog (not written by me) that no longer seems to be available.
It is quite old code written for Java 7/8 and Junit4.
I will try to keep this short :)
First you need an Appender it is probably best to extend the AppenderSkeleton, something like:
public class RuleAppender extends AppenderSkeleton {
private final List loggingEvents = new ArrayList<>();
protected RuleAppender() {
super(true);
this.setThreshold(Level.TRACE);
setName("Rule Appender");
}
@Override
public void close() {
// No op in this case
}
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(final LoggingEvent event) {
loggingEvents.add(event);
}
public boolean hasEventsMeeting(LogExpectation logExpectation) {
// Use the LogExpectation to determine if the list of log events contains what you want.
}
@Override
public String toString() {
return "RuleAppender";
}
}
The LogExpectation is simply somewhere too define an expectation/criteria to match against the stored log events.
This is then wrapped up in a Junit Rule to make adding the the Appender to the Log4J a little easier, I did this by implementing TestRule and extending Statement, ensuring the first thing Statements evaluate method does is:
LogManager.getRootLogger().addAppender(ruleAppender);
LogManager.getRootLogger().setLevel(Level.ALL);
Notes:
This can be done without a JUnit rule, so long as the above two lines are executed before the test in order to ensure the appending is added to Log4J (The custom appender is still required).
I have not gone into the JUnit rule code, as we probably should be moving to JUnit 5 which doesnt support Rules and I have not gone into LogExpecation as this is simply away to find what you are looking for in the list of log events and can be expressed in a manner suitable to your domain, e.g checking the that a Throwable was logged, perhaps looking in org.apache.log4j.spi.LoggingEvent for appropriate methods