Skip to content

Commit

Permalink
Start using reports to send inner exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
Konrad Dysput committed Nov 22, 2024
1 parent a487226 commit 1f55f5f
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.TimeUnit;

import backtraceio.library.common.BacktraceSerializeHelper;
Expand Down Expand Up @@ -93,7 +94,12 @@ public void sendExceptionWithManyCause() {

final BacktraceClient backtraceClient = new BacktraceClient(context, credentials);
final Waiter waiter = new Waiter();
final String mainExceptionExpectedMessage = "java.io.IOException: java.lang.IllegalArgumentException: New Exception";

final Stack<String> expectedExceptionMessages = new Stack<String>() {{
add("java.lang.IllegalArgumentException: New Exception");
add("java.io.IOException: java.lang.IllegalArgumentException: New Exception");
}};

RequestHandler rh = data -> {
String jsonString = BacktraceSerializeHelper.toJson(data);

Expand All @@ -102,7 +108,7 @@ public void sendExceptionWithManyCause() {
final JSONObject jsonObject = new JSONObject(jsonString);
final JSONObject exceptionProperties = jsonObject.getJSONObject("annotations").getJSONObject("Exception properties");
final String mainExceptionMessage = jsonObject.getJSONObject("annotations").getJSONObject("Exception").getString("message");

final String mainExceptionExpectedMessage = expectedExceptionMessages.pop();
assertEquals(mainExceptionExpectedMessage, mainExceptionMessage);
assertTrue(exceptionProperties.getJSONArray("stack-trace").length() > 0);
assertEquals(mainExceptionExpectedMessage, exceptionProperties.get("detail-message"));
Expand All @@ -124,7 +130,7 @@ public void sendExceptionWithManyCause() {

// WAIT FOR THE RESULT FROM ANOTHER THREAD
try {
waiter.await(5, TimeUnit.SECONDS);
waiter.await(500, TimeUnit.SECONDS);
} catch (Exception ex) {
fail(ex.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import backtraceio.library.interfaces.Database;
import backtraceio.library.models.database.BacktraceDatabaseSettings;
import backtraceio.library.models.json.BacktraceReport;
import backtraceio.library.services.ExceptionTransformer;
import backtraceio.library.watchdog.BacktraceANRWatchdog;
import backtraceio.library.watchdog.OnApplicationNotRespondingEvent;

Expand All @@ -20,7 +19,6 @@
*/
public class BacktraceClient extends BacktraceBase {

private final ExceptionTransformer exceptionTransformer = new ExceptionTransformer();
/**
* Backtrace ANR watchdog instance
*/
Expand Down Expand Up @@ -227,10 +225,7 @@ public void send(Throwable throwable, OnServerResponseEventListener
*/
public void send(Throwable throwable, Map<String, Object> attributes, OnServerResponseEventListener
serverResponseEventListener) {
for (BacktraceReport report :
this.exceptionTransformer.transformExceptionIntoReports(throwable, attributes)) {
super.send(report, serverResponseEventListener);
}
super.send(new BacktraceReport(throwable, attributes), serverResponseEventListener);
}

/**
Expand Down Expand Up @@ -310,21 +305,4 @@ public void disableAnr() {
}
}

/**
* Determine if Reports should be generated for inner exceptions. By default the value is set to true.
*
* @param sendInnerExceptions boolean flag that enabled/disable sending inner exceptions
*/
public void sendInnerExceptions(boolean sendInnerExceptions) {
this.exceptionTransformer.sendInnerExceptions(sendInnerExceptions);
}

/**
* Determine if Reports should be generated for suppressed exceptions. By default the value is set to true.
*
* @param sendSuppressedExceptions boolean flag that enabled/disable sending suppressed exceptions
*/
public void sendSuppressedExceptions(boolean sendSuppressedExceptions) {
this.exceptionTransformer.sendSuppressedExceptions(sendSuppressedExceptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@
import backtraceio.library.models.types.BacktraceResultStatus;
import backtraceio.library.services.BacktraceApi;
import backtraceio.library.services.BacktraceMetrics;
import backtraceio.library.services.ReportExceptionTransformer;

/**
* Base Backtrace Android client
*/
public class BacktraceBase implements Client {

private static final transient String LOG_TAG = BacktraceBase.class.getSimpleName();
private static final String LOG_TAG = BacktraceBase.class.getSimpleName();
/**
* Backtrace client version
*/
public static String version = backtraceio.library.BuildConfig.VERSION_NAME;

static {
System.loadLibrary("backtrace-native");
Expand All @@ -45,12 +50,6 @@ public class BacktraceBase implements Client {
* Backtrace database instance
*/
public final Database database;

/**
* Backtrace client version
*/
public static String version = backtraceio.library.BuildConfig.VERSION_NAME;

/**
* Get custom client attributes. Every argument stored in dictionary will be send to Backtrace API
*/
Expand All @@ -61,27 +60,23 @@ public class BacktraceBase implements Client {
*/
public final List<String> attachments;
private final BacktraceCredentials credentials;

private final ReportExceptionTransformer reportExceptionTransformer = new ReportExceptionTransformer();
/**
* Backtrace metrics instance
*/
public Metrics metrics = null;

/**
* Application context
*/
protected Context context;

/**
* Instance of BacktraceApi that allows to send data to Backtrace API
*/
private Api backtraceApi;

/**
* Event which will be executed before sending data to Backtrace API
*/
private OnBeforeSendEventListener beforeSendEventListener = null;

/**
* Is Proguard symbolication enabled? We have to inform the Backtrace API
*/
Expand Down Expand Up @@ -574,25 +569,28 @@ public void send(BacktraceReport report) {
/**
* Sending an exception to Backtrace API
*
* @param report current BacktraceReport
* @param sourceReport current BacktraceReport
*/
public void send(BacktraceReport report, final OnServerResponseEventListener callback) {
public void send(BacktraceReport sourceReport, final OnServerResponseEventListener callback) {
Breadcrumbs breadcrumbs = this.database.getBreadcrumbs();
if (breadcrumbs != null) {
breadcrumbs.processReportBreadcrumbs(report);
}
addReportAttachments(report);
for (BacktraceReport report :
this.reportExceptionTransformer.transformReportWithInnerExceptions(sourceReport)) {
if (breadcrumbs != null) {
breadcrumbs.processReportBreadcrumbs(report);
}
addReportAttachments(report);

BacktraceData backtraceData = new BacktraceData(this.context, report, this.attributes);
backtraceData.symbolication = this.isProguardEnabled ? "proguard" : null;
BacktraceData backtraceData = new BacktraceData(this.context, report, this.attributes);
backtraceData.symbolication = this.isProguardEnabled ? "proguard" : null;

final BacktraceDatabaseRecord record = this.database.add(report, this.attributes, this.isProguardEnabled);
final BacktraceDatabaseRecord record = this.database.add(report, this.attributes, this.isProguardEnabled);

if (this.beforeSendEventListener != null) {
backtraceData = this.beforeSendEventListener.onEvent(backtraceData);
}
if (this.beforeSendEventListener != null) {
backtraceData = this.beforeSendEventListener.onEvent(backtraceData);
}

this.backtraceApi.send(backtraceData, this.getDatabaseCallback(record, callback));
this.backtraceApi.send(backtraceData, this.getDatabaseCallback(record, callback));
}
}

private OnServerResponseEventListener getDatabaseCallback(final BacktraceDatabaseRecord record, final OnServerResponseEventListener customCallback) {
Expand Down Expand Up @@ -624,4 +622,22 @@ private boolean isBreadcrumbsAvailable() {
return database != null && database.getBreadcrumbs() != null;
}


/**
* Determine if Reports should be generated for inner exceptions. By default the value is set to true.
*
* @param sendInnerExceptions boolean flag that enabled/disable sending inner exceptions
*/
public void sendInnerExceptions(boolean sendInnerExceptions) {
this.reportExceptionTransformer.sendInnerExceptions(sendInnerExceptions);
}

/**
* Determine if Reports should be generated for suppressed exceptions. By default the value is set to true.
*
* @param sendSuppressedExceptions boolean flag that enabled/disable sending suppressed exceptions
*/
public void sendSuppressedExceptions(boolean sendSuppressedExceptions) {
this.reportExceptionTransformer.sendSuppressedExceptions(sendSuppressedExceptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,43 @@
*/
public class BacktraceReport {

/**
* Reference to the original exception. This field is internally used by the library.
*/
private final transient Throwable originalException;
/**
* 16 bytes of randomness in human readable UUID format
* server will reject request if uuid is already found
*/
public UUID uuid = UUID.randomUUID();

/**
* UTC timestamp in seconds
*/
public long timestamp = BacktraceTimeHelper.getTimestampSeconds();

/**
* Get information about report type. If value is true the BacktraceReport has an error
*/
public Boolean exceptionTypeReport = false;

/**
* Get a report classification
*/
public String classifier = "";

/**
* Get an report attributes
*/
public Map<String, Object> attributes;

public Map<String, Object> attributes = new HashMap<>();
/**
* Get a custom client message
*/
public String message;

/**
* Get a report exception
*/
public Exception exception;

/**
* Get all paths to attachments
*/
public List<String> attachmentPaths;

public List<String> attachmentPaths = new ArrayList<>();
/**
* Current report exception stack
*/
Expand Down Expand Up @@ -173,6 +169,7 @@ public BacktraceReport(
public BacktraceReport(Throwable throwable, Map<String, Object> attributes, List<String> attachmentPaths) {
this.attributes = CollectionUtils.copyMap(attributes);
this.attachmentPaths = CollectionUtils.copyList(attachmentPaths);
this.originalException = throwable;
this.exception = this.prepareException(throwable);
if (throwable != null) {
this.classifier = getExceptionClassifier(throwable);
Expand Down Expand Up @@ -237,6 +234,16 @@ private void setDefaultErrorTypeAttribute() {
: BacktraceAttributeConsts.MessageAttributeType);
}

/**
* Returns report exception - original exception if possible or serializable exception if
* the report was loaded from JSON.
*
* @return throwable object
*/
public Throwable getException() {
return this.originalException != null ? this.originalException : this.exception;
}

public BacktraceData toBacktraceData(Context context, Map<String, Object> clientAttributes) {
return toBacktraceData(context, clientAttributes, false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import backtraceio.library.models.json.BacktraceReport;

public class ExceptionTransformer {
public class ReportExceptionTransformer {

/**
* Error trace attribute key
Expand Down Expand Up @@ -49,29 +49,42 @@ public void sendSuppressedExceptions(boolean sendSuppressedExceptions) {
* Transforms throwable into an array of Backtrace reports. During the transformation, inner exception and suppressed exceptions
* are being converted into Backtrace reports with a proper exception reference.
*
* @param throwable throwable
* @param attributes report attributes - attributes will be included in each report
* @param sourceReport BacktraceReport
* @return list of Backtrace reports
*/
public List<BacktraceReport> transformExceptionIntoReports(Throwable throwable, Map<String, Object> attributes) {
public List<BacktraceReport> transformReportWithInnerExceptions(BacktraceReport sourceReport) {
final String exceptionTrace = UUID.randomUUID().toString();
final List<BacktraceReport> reports = new ArrayList<>();
String parentId = null;
final List<BacktraceReport> reports = new ArrayList<BacktraceReport>() {{
add(sourceReport);
}};

if (!sourceReport.exceptionTypeReport) {
return reports;
}
Throwable throwable = sourceReport.getException();
if (throwable == null) {
return reports;
}
/**
* To keep the original report, we're not re-creating it but rather, we copy all known possible values
* that should be also available in inner exceptions. We should keep the original report, in case of potential
* changes that could
*/
String parentId = sourceReport.uuid.toString();
Map<String, Object> attributes = sourceReport.attributes;
List<String> attachments = sourceReport.attachmentPaths;
reports.addAll(this.getSuppressedReports(throwable, attachments, attributes, exceptionTrace, parentId));

throwable = throwable.getCause();

while (throwable != null) {
BacktraceReport report = new BacktraceReport(throwable, attributes);
report.attachmentPaths.addAll(attachments);
this.extendReportWithNestedExceptionAttributes(report, exceptionTrace, parentId);
reports.add(report);

parentId = report.uuid.toString();
if (sendSuppressedExceptions) {
for (Throwable suppressedException :
throwable.getSuppressed()) {
BacktraceReport suppressedExceptionReport = new BacktraceReport(suppressedException, attributes);
this.extendReportWithNestedExceptionAttributes(suppressedExceptionReport, exceptionTrace, parentId);
reports.add(suppressedExceptionReport);
}
}
reports.addAll(this.getSuppressedReports(throwable, attachments, attributes, exceptionTrace, parentId));

if (!sendInnerExceptions) {
break;
Expand All @@ -82,6 +95,29 @@ public List<BacktraceReport> transformExceptionIntoReports(Throwable throwable,
return reports;
}

private List<BacktraceReport> getSuppressedReports(
Throwable throwable,
List<String> attachments,
Map<String, Object> attributes,
String exceptionTrace,
String parentId) {
List<BacktraceReport> reports = new ArrayList<>();
if (!this.sendSuppressedExceptions) {
return reports;
}

for (Throwable suppressedException :
throwable.getSuppressed()) {
BacktraceReport suppressedExceptionReport = new BacktraceReport(suppressedException, attributes);
this.extendReportWithNestedExceptionAttributes(suppressedExceptionReport, exceptionTrace, parentId);
suppressedExceptionReport.attachmentPaths.addAll(attachments);
reports.add(suppressedExceptionReport);
}


return reports;
}

/**
* Add exception trace attributes to the nested exception
*
Expand Down
Loading

0 comments on commit 1f55f5f

Please sign in to comment.