关于Java:如何验证(使用单元测试)错误堆栈已打印在日志文件中?

How to verify (with unit test) that error stack is printed in the log file?

在继续回答此问题时,我编写了一个单元测试,以验证在发生错误的情况下,堆栈将被打印在日志文件中。

测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
    }
}

单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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<ILoggingEvent> 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<ILoggingEvent> 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());
}

好吧,尽管我可以看到堆栈确实已打印在日志文件中,但是由于
logsList仅包含一项(仅第一行打印[无法计算SomeThing。错误:])。

java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1

为什么会发生以及如何对其进行测试?

编辑

答案:
答案(都是@Gavin的答案和评论的引号,谢谢):

关于第一个问题(为什么会发生)的答案是:

It looks to me that exceptions is stored separately from the message in the log event

关于第二个问题(如何进行测试),答案是:

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

最后,我要验证的代码是:

1
Assert.assertEquals(logsList.get(0).getThrowableProxy().getClassName(),"java.lang.NullPointerException");


这是我过去捕获日志消息的方式,这是基于一个似乎不再可用的旧博客(不是我写的)。

这是为Java 7/8和Junit4编写的相当老的代码。

我会尽量保持简短:)

首先,您需要一个Appender,最好扩展AppenderSkeleton,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class RuleAppender extends AppenderSkeleton {
    private final List<LoggingEvent> 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";
    }
}

LogExpectation只是在某个地方也定义了期望/条件以与存储的日志事件相匹配。

然后将其包裹在Junit规则中,以使将Appender添加到Log4J变得更加容易,我通过实现TestRule并扩展Statement来做到这一点,确保第一件事Statement s evaluate方法确实是:

1
2
LogManager.getRootLogger().addAppender(ruleAppender);
LogManager.getRootLogger().setLevel(Level.ALL);

注意:
只要在测试之前执行了以上两行,以确保将追加添加到Log4J(仍然需要自定义追加器),就可以在没有JUnit规则的情况下完成此操作。

我还没有进入JUnit规则代码,因为我们可能应该转到不支持Rules的JUnit 5,并且我也没有进入LogExpecation,因为这很容易在列表中找到所需的内容。日志事件,可以用适合您的域的方式表示,例如,检查是否记录了Throwable,也许在org.apache.log4j.spi.LoggingEvent中查找适当的方法