Skip to content

Commit 43d645b

Browse files
konraddysputKonrad Dysput
andauthored
Nested exception support (#158)
* Nested exception support * Use BacktraceClient to unpack inner exceptions * Use parentId * Clean up the throwable wrapper * Add support for suppressed exceptions + unit tests * Add additional test for error.type attribute and rename variables in docs * Add missing variable name * Documentation - changed Report into report * Start using reports to send inner exceptions * Add error attributes to the report * Add missing exception message * Code review adjustements * Adjusted code to code review suggestions. Null check * Disable feature by default * Enable in tests --------- Co-authored-by: Konrad Dysput <konrad.dysput@saucelabs.com>
1 parent eea053a commit 43d645b

File tree

9 files changed

+578
-157
lines changed

9 files changed

+578
-157
lines changed

backtrace-library/src/androidTest/java/backtraceio/library/BacktraceClientSendTest.java

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import org.junit.runner.RunWith;
2121

2222
import java.io.IOException;
23+
import java.util.ArrayList;
2324
import java.util.HashMap;
25+
import java.util.List;
2426
import java.util.Map;
27+
import java.util.Stack;
2528
import java.util.concurrent.TimeUnit;
2629

2730
import backtraceio.library.common.BacktraceSerializeHelper;
@@ -34,12 +37,12 @@
3437

3538
@RunWith(AndroidJUnit4.class)
3639
public class BacktraceClientSendTest {
37-
private Context context;
38-
private BacktraceCredentials credentials;
3940
private final String resultMessage = "From request handler";
4041
private final Map<String, Object> attributes = new HashMap<String, Object>() {{
4142
put("test", "value");
4243
}};
44+
private Context context;
45+
private BacktraceCredentials credentials;
4346

4447
@Before
4548
public void setUp() {
@@ -91,7 +94,13 @@ public void sendExceptionWithManyCause() {
9194

9295
final BacktraceClient backtraceClient = new BacktraceClient(context, credentials);
9396
final Waiter waiter = new Waiter();
94-
final String mainExceptionExpectedMessage = "java.io.IOException: java.lang.IllegalArgumentException: New Exception";
97+
98+
final Stack<String> expectedExceptionMessages = new Stack<String>() {{
99+
add("New Exception");
100+
add("java.lang.IllegalArgumentException: New Exception");
101+
add("java.io.IOException: java.lang.IllegalArgumentException: New Exception");
102+
}};
103+
95104
RequestHandler rh = data -> {
96105
String jsonString = BacktraceSerializeHelper.toJson(data);
97106

@@ -100,7 +109,7 @@ public void sendExceptionWithManyCause() {
100109
final JSONObject jsonObject = new JSONObject(jsonString);
101110
final JSONObject exceptionProperties = jsonObject.getJSONObject("annotations").getJSONObject("Exception properties");
102111
final String mainExceptionMessage = jsonObject.getJSONObject("annotations").getJSONObject("Exception").getString("message");
103-
112+
final String mainExceptionExpectedMessage = expectedExceptionMessages.pop();
104113
assertEquals(mainExceptionExpectedMessage, mainExceptionMessage);
105114
assertTrue(exceptionProperties.getJSONArray("stack-trace").length() > 0);
106115
assertEquals(mainExceptionExpectedMessage, exceptionProperties.get("detail-message"));
@@ -277,6 +286,46 @@ public void onEvent(BacktraceResult backtraceResult) {
277286
}
278287
}
279288

289+
@Test
290+
public void sendExceptionWithInnerException() {
291+
// GIVEN
292+
final int expectedNumberOfReports = 2;
293+
final String innerExceptionMessage = "inner exception";
294+
final String outerExceptionMessage = "outer exception";
295+
final Exception innerException = new Exception(innerExceptionMessage);
296+
final Exception outerException = new Exception(outerExceptionMessage, innerException);
297+
final List<BacktraceData> reportData = new ArrayList<>();
298+
BacktraceClient backtraceClient = new BacktraceClient(context, credentials);
299+
backtraceClient.sendInnerExceptions(true);
300+
backtraceClient.sendSuppressedExceptions(true);
301+
final Waiter waiter = new Waiter();
302+
303+
304+
backtraceClient.setOnRequestHandler(new RequestHandler() {
305+
@Override
306+
public BacktraceResult onRequest(BacktraceData data) {
307+
reportData.add(data);
308+
if (reportData.size() == expectedNumberOfReports) {
309+
waiter.resume();
310+
}
311+
return new BacktraceResult(data.getReport(), data.getReport().exception.getMessage(),
312+
BacktraceResultStatus.Ok);
313+
}
314+
});
315+
backtraceClient.send(outerException);
316+
317+
try {
318+
waiter.await(5, TimeUnit.SECONDS, 1);
319+
} catch (Exception ex) {
320+
fail(ex.getMessage());
321+
}
322+
assertEquals(expectedNumberOfReports, reportData.size());
323+
BacktraceData outerExceptionData = reportData.get(0);
324+
assertEquals(outerExceptionMessage, outerExceptionData.attributes.get("error.message"));
325+
BacktraceData innerExceptionData = reportData.get(reportData.size() - 1);
326+
assertEquals(innerExceptionMessage, innerExceptionData.attributes.get("error.message"));
327+
}
328+
280329
@Test
281330
public void sendMultipleReports() {
282331
// GIVEN
@@ -313,4 +362,4 @@ public void onEvent(BacktraceResult backtraceResult) {
313362
fail(ex.getMessage());
314363
}
315364
}
316-
}
365+
}

backtrace-library/src/androidTest/java/backtraceio/library/models/UncaughtExceptionHandlerTest.java

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.lang.reflect.Field;
2020
import java.lang.reflect.InvocationTargetException;
2121
import java.lang.reflect.Modifier;
22+
import java.util.ArrayList;
23+
import java.util.List;
2224
import java.util.concurrent.TimeUnit;
2325
import java.util.concurrent.atomic.AtomicReference;
2426

@@ -31,12 +33,6 @@ public class UncaughtExceptionHandlerTest {
3133
private Context context;
3234
private BacktraceCredentials credentials;
3335

34-
@Before
35-
public void setUp() {
36-
context = InstrumentationRegistry.getInstrumentation().getContext();
37-
credentials = new BacktraceCredentials("https://example-endpoint.com/", "");
38-
}
39-
4036
private static void setRootHandler(Thread.UncaughtExceptionHandler customRootHandler, Thread.UncaughtExceptionHandler newRootHandler) {
4137
try {
4238
Field field = BacktraceExceptionHandler.class.getDeclaredField("rootHandler");
@@ -47,7 +43,6 @@ private static void setRootHandler(Thread.UncaughtExceptionHandler customRootHan
4743
}
4844
}
4945

50-
5146
private static BacktraceExceptionHandler createBacktraceExceptionHandler(BacktraceClient client) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
5247
Constructor<BacktraceExceptionHandler> constructor = BacktraceExceptionHandler.class.getDeclaredConstructor(BacktraceClient.class);
5348
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
@@ -57,6 +52,12 @@ private static BacktraceExceptionHandler createBacktraceExceptionHandler(Backtra
5752
return exceptionHandler;
5853
}
5954

55+
@Before
56+
public void setUp() {
57+
context = InstrumentationRegistry.getInstrumentation().getContext();
58+
credentials = new BacktraceCredentials("https://example-endpoint.com/", "");
59+
}
60+
6061
@Test()
6162
public void testUncaughtException() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
6263
// GIVEN
@@ -131,4 +132,93 @@ public void testUncaughtError() throws InvocationTargetException, NoSuchMethodEx
131132
assertEquals("Unhandled Exception", testedReportData.getReport().attributes.get("error.type"));
132133
assertTrue(testedReportData.getReport().exceptionTypeReport);
133134
}
135+
136+
@Test()
137+
public void testUncaughtInnerExceptionsGeneration() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
138+
// GIVEN
139+
final int expectedNumberOfExceptions = 2;
140+
final Waiter waiter = new Waiter();
141+
final String innerExceptionMessage = "Cause exception message";
142+
final Exception cause = new IllegalArgumentException(innerExceptionMessage);
143+
final String outerExceptionMessage = "Outer exception";
144+
final Exception exception = new IllegalArgumentException(outerExceptionMessage, cause);
145+
final BacktraceClient client = new BacktraceClient(context, credentials);
146+
client.sendInnerExceptions(true);
147+
client.sendSuppressedExceptions(true);
148+
149+
final List<BacktraceData> unhandledExceptionData = new ArrayList<>();
150+
client.setOnRequestHandler(data -> {
151+
unhandledExceptionData.add(data);
152+
if (unhandledExceptionData.size() == expectedNumberOfExceptions) {
153+
waiter.resume();
154+
}
155+
return new BacktraceResult(data.getReport(), data.getReport().message,
156+
BacktraceResultStatus.Ok);
157+
});
158+
159+
final BacktraceExceptionHandler handler = createBacktraceExceptionHandler(client);
160+
161+
// WHEN
162+
handler.uncaughtException(Thread.currentThread(), exception);
163+
164+
// WAIT FOR THE RESULT FROM ANOTHER THREAD
165+
try {
166+
waiter.await(5, TimeUnit.SECONDS);
167+
} catch (Exception ex) {
168+
fail(ex.getMessage());
169+
}
170+
171+
// THEN
172+
assertEquals(expectedNumberOfExceptions, unhandledExceptionData.size());
173+
174+
final BacktraceData outerException = unhandledExceptionData.get(0);
175+
final BacktraceData innerException = unhandledExceptionData.get(unhandledExceptionData.size() - 1);
176+
assertEquals(outerExceptionMessage, outerException.attributes.get("error.message"));
177+
assertEquals(innerExceptionMessage, innerException.attributes.get("error.message"));
178+
}
179+
180+
@Test()
181+
public void testUncaughtInnerExceptionsErrorAttributes() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
182+
// GIVEN
183+
final int expectedNumberOfExceptions = 2;
184+
final Waiter waiter = new Waiter();
185+
final String innerExceptionMessage = "Cause exception message";
186+
final Exception cause = new IllegalArgumentException(innerExceptionMessage);
187+
final String outerExceptionMessage = "Outer exception";
188+
final Exception exception = new IllegalArgumentException(outerExceptionMessage, cause);
189+
final BacktraceClient client = new BacktraceClient(context, credentials);
190+
client.sendInnerExceptions(true);
191+
client.sendSuppressedExceptions(true);
192+
193+
final List<BacktraceData> unhandledExceptionData = new ArrayList<>();
194+
client.setOnRequestHandler(data -> {
195+
unhandledExceptionData.add(data);
196+
if (unhandledExceptionData.size() == expectedNumberOfExceptions) {
197+
waiter.resume();
198+
}
199+
return new BacktraceResult(data.getReport(), data.getReport().message,
200+
BacktraceResultStatus.Ok);
201+
});
202+
203+
final BacktraceExceptionHandler handler = createBacktraceExceptionHandler(client);
204+
205+
// WHEN
206+
handler.uncaughtException(Thread.currentThread(), exception);
207+
208+
// WAIT FOR THE RESULT FROM ANOTHER THREAD
209+
try {
210+
waiter.await(5, TimeUnit.SECONDS);
211+
} catch (Exception ex) {
212+
fail(ex.getMessage());
213+
}
214+
215+
// THEN
216+
final BacktraceData outerException = unhandledExceptionData.get(0);
217+
final BacktraceData innerException = unhandledExceptionData.get(unhandledExceptionData.size() - 1);
218+
assertEquals(outerException.attributes.get("error.trace"), innerException.attributes.get("error.trace"));
219+
assertEquals(outerException.uuid, innerException.attributes.get("error.parent"));
220+
assertNull(outerException.attributes.get("error.parent"));
221+
assertEquals(BacktraceAttributeConsts.UnhandledExceptionAttributeType, outerException.attributes.get("error.type"));
222+
assertEquals(BacktraceAttributeConsts.UnhandledExceptionAttributeType, innerException.attributes.get("error.type"));
223+
}
134224
}

backtrace-library/src/main/java/backtraceio/library/BacktraceClient.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,23 +197,35 @@ public void send(String message, OnServerResponseEventListener serverResponseEve
197197
}
198198

199199
/**
200-
* Sending an exception to Backtrace API
200+
* Sending an throwable to Backtrace API
201201
*
202-
* @param exception current exception
202+
* @param throwable current throwable
203203
*/
204-
public void send(Exception exception) {
205-
this.send(exception, null);
204+
public void send(Throwable throwable) {
205+
this.send(throwable, null);
206206
}
207207

208208
/**
209-
* Sending an exception to Backtrace API
209+
* Sending an throwable to Backtrace API
210210
*
211-
* @param exception current exception
211+
* @param throwable current throwable
212212
* @param serverResponseEventListener event callback that will be executed after receiving a response from the server
213213
*/
214-
public void send(Exception exception, OnServerResponseEventListener
214+
public void send(Throwable throwable, OnServerResponseEventListener
215215
serverResponseEventListener) {
216-
super.send(new BacktraceReport(exception), serverResponseEventListener);
216+
this.send(throwable, null, serverResponseEventListener);
217+
}
218+
219+
/**
220+
* Sending an throwable to Backtrace API
221+
*
222+
* @param throwable current throwable
223+
* @param attributes throwable attributes
224+
* @param serverResponseEventListener event callback that will be executed after receiving a response from the server
225+
*/
226+
public void send(Throwable throwable, Map<String, Object> attributes, OnServerResponseEventListener
227+
serverResponseEventListener) {
228+
super.send(new BacktraceReport(throwable, attributes), serverResponseEventListener);
217229
}
218230

219231
/**
@@ -292,4 +304,4 @@ public void disableAnr() {
292304
this.anrWatchdog.stopMonitoringAnr();
293305
}
294306
}
295-
}
307+
}

0 commit comments

Comments
 (0)