Skip to content

Commit 5ab3705

Browse files
committed
Fix #180: output stack trace
`ObjectExceptionCatcher` now optionally outputs a stack trace. Set the `logStackTrace` property to true to enable stack trace logging. The syntax of the log messages created by `ObjectExceptionCatcher` has changed to better separate end user related messages and developer related messages. This commit also refactors the test case for `ObjectExceptionCatcher` so that the presence of log message is now properly checked for.
1 parent a55475c commit 5ab3705

File tree

3 files changed

+110
-48
lines changed

3 files changed

+110
-48
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@
254254
</exclusion>
255255
</exclusions>
256256
</dependency>
257+
258+
<dependency>
259+
<groupId>org.hamcrest</groupId>
260+
<artifactId>hamcrest-core</artifactId>
261+
<version>1.3</version>
262+
<scope>test</scope>
263+
</dependency>
257264
</dependencies>
258265

259266
<profiles>

src/main/java/org/culturegraph/mf/stream/pipe/ObjectExceptionCatcher.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.culturegraph.mf.stream.pipe;
1717

18+
import java.io.PrintWriter;
19+
20+
import org.apache.commons.io.output.StringBuilderWriter;
1821
import org.culturegraph.mf.framework.DefaultObjectPipe;
1922
import org.culturegraph.mf.framework.ObjectReceiver;
2023
import org.culturegraph.mf.framework.annotations.Description;
@@ -43,10 +46,11 @@ public final class ObjectExceptionCatcher<T> extends
4346
DefaultObjectPipe<T, ObjectReceiver<T>> {
4447

4548
private static final Logger LOG = LoggerFactory.getLogger(ObjectExceptionCatcher.class);
46-
private static final String MSG_PATTERN = "{}{}";
47-
49+
4850
private final String logPrefix;
49-
51+
52+
private boolean logStackTrace;
53+
5054
public ObjectExceptionCatcher() {
5155
this("");
5256
}
@@ -55,7 +59,15 @@ public ObjectExceptionCatcher(final String logPrefix) {
5559
super();
5660
this.logPrefix = logPrefix;
5761
}
58-
62+
63+
public void setLogStackTrace(final boolean logStackTrace) {
64+
this.logStackTrace = logStackTrace;
65+
}
66+
67+
public boolean isLogStackTrace() {
68+
return logStackTrace;
69+
}
70+
5971
@Override
6072
public void process(final T obj) {
6173
try {
@@ -65,8 +77,14 @@ public void process(final T obj) {
6577
// This module is supposed to intercept _all_ exceptions
6678
// thrown by downstream modules. Hence, we have to catch
6779
// Exception.
68-
LOG.error(MSG_PATTERN, logPrefix, obj);
69-
LOG.error(MSG_PATTERN, logPrefix, e);
80+
81+
LOG.error("{}'{}' while processing object: {}", logPrefix, e.getMessage(), obj);
82+
83+
if (logStackTrace) {
84+
final StringBuilderWriter stackTraceWriter = new StringBuilderWriter();
85+
e.printStackTrace(new PrintWriter(stackTraceWriter));
86+
LOG.error("{}Stack Trace:\n{}", logPrefix, stackTraceWriter.toString());
87+
}
7088
}
7189
}
7290

src/test/java/org/culturegraph/mf/stream/pipe/ObjectExceptionCatcherTest.java

Lines changed: 79 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,67 +17,104 @@
1717

1818

1919

20-
import org.culturegraph.mf.framework.DefaultObjectReceiver;
21-
import org.junit.Assert;
20+
import static org.hamcrest.CoreMatchers.containsString;
21+
import static org.hamcrest.CoreMatchers.is;
22+
import static org.junit.Assert.assertThat;
23+
import static org.mockito.Matchers.anyString;
24+
import static org.mockito.Mockito.doThrow;
25+
import static org.mockito.Mockito.times;
26+
import static org.mockito.Mockito.verify;
27+
28+
import org.apache.log4j.Appender;
29+
import org.apache.log4j.Level;
30+
import org.apache.log4j.Logger;
31+
import org.apache.log4j.spi.LoggingEvent;
32+
import org.culturegraph.mf.framework.ObjectReceiver;
33+
import org.junit.After;
34+
import org.junit.Before;
2235
import org.junit.Test;
36+
import org.mockito.ArgumentCaptor;
37+
import org.mockito.Captor;
38+
import org.mockito.Mock;
39+
import org.mockito.MockitoAnnotations;
2340

2441

2542
/**
2643
* Tests {@link ObjectExceptionCatcher}.
27-
*
44+
*
2845
* @author Christoph Böhme
2946
*/
3047
public final class ObjectExceptionCatcherTest {
3148

32-
private static final String OBJECT = "Test Object";
33-
49+
private static final String OBJECT_STRING = "TEST OBJECT REPRESENTATION";
50+
private static final String EXCEPTION_MESSAGE = "TEST EXCEPTION MESSSAGE";
51+
3452
/**
3553
* A special exception to make sure the test
36-
* is not passed accidentally on a different
54+
* is not passed accidentally on a different
3755
* exception.
3856
*/
39-
protected static final class ModuleException
57+
private static final class TestException
4058
extends RuntimeException {
4159

42-
private static final long serialVersionUID = 1L;
43-
}
44-
45-
/**
46-
* A module whose {@code process()} method always throws
47-
* an exception.
48-
*
49-
* @param <T> object type
50-
*/
51-
protected static final class FailingModule<T>
52-
extends DefaultObjectReceiver<T> {
53-
54-
@Override
55-
public void process(final T obj) {
56-
throw new ModuleException();
60+
private static final long serialVersionUID = 1L;
61+
62+
public TestException(final String msg) {
63+
super(msg);
5764
}
65+
66+
}
67+
68+
private ObjectExceptionCatcher<String> systemUnderTest;
69+
70+
@Mock
71+
private ObjectReceiver<String> exceptionThrowingModule;
72+
73+
@Mock
74+
private Appender logAppender;
75+
@Captor
76+
private ArgumentCaptor<LoggingEvent> loggingEventCaptor;
77+
78+
@Before
79+
public void setup() {
80+
MockitoAnnotations.initMocks(this);
81+
doThrow(new TestException(EXCEPTION_MESSAGE))
82+
.when(exceptionThrowingModule).process(anyString());
83+
84+
final Logger logger = Logger.getLogger(ObjectExceptionCatcher.class);
85+
logger.addAppender(logAppender);
86+
87+
systemUnderTest = new ObjectExceptionCatcher<String>();
88+
systemUnderTest.setReceiver(exceptionThrowingModule);
5889
}
59-
60-
@Test(expected=ModuleException.class)
61-
public void testSetup() {
62-
final FailingModule<String> failingModule = new FailingModule<String>();
63-
64-
failingModule.process(OBJECT);
65-
failingModule.closeStream();
90+
91+
@After
92+
public void cleanup() {
93+
final Logger logger = Logger.getLogger(ObjectExceptionCatcher.class);
94+
logger.removeAppender(logAppender);
6695
}
67-
96+
6897
@Test
69-
public void testCatcher() {
70-
final ObjectExceptionCatcher<String> catcher = new ObjectExceptionCatcher<String>();
71-
final FailingModule<String> failingModule = new FailingModule<String>();
72-
73-
catcher.setReceiver(failingModule);
74-
75-
try {
76-
catcher.process(OBJECT);
77-
catcher.closeStream();
78-
} catch (final ModuleException e) {
79-
Assert.fail(e.toString());
80-
}
98+
public void shouldCatchAndLogException() {
99+
systemUnderTest.process(OBJECT_STRING);
100+
101+
verify(logAppender).doAppend(loggingEventCaptor.capture());
102+
final LoggingEvent loggingEvent = loggingEventCaptor.getValue();
103+
assertThat(loggingEvent.getLevel(), is(Level.ERROR));
104+
assertThat(loggingEvent.getRenderedMessage(), containsString(EXCEPTION_MESSAGE));
105+
assertThat(loggingEvent.getRenderedMessage(), containsString(OBJECT_STRING));
106+
}
107+
108+
@Test
109+
public void shouldLogStackTraceIfConfigured() {
110+
systemUnderTest.setLogStackTrace(true);
111+
112+
systemUnderTest.process(OBJECT_STRING);
113+
114+
verify(logAppender, times(2)).doAppend(loggingEventCaptor.capture());
115+
final LoggingEvent loggingEvent = loggingEventCaptor.getValue();
116+
assertThat(loggingEvent.getLevel(), is(Level.ERROR));
117+
assertThat(loggingEvent.getRenderedMessage(), containsString("Stack Trace"));
81118
}
82119

83120
}

0 commit comments

Comments
 (0)