Skip to content

Commit

Permalink
feat: Run callbacks before native crash reports (#379)
Browse files Browse the repository at this point in the history
* Added support for modifying all reports (including native crash
reports) prior to delivery.
* Added new functionality to convert an Error cached on disk back
into an Error object, which is then added to reports. The new 
callbacks are run on the resulting report object just prior to 
delivery.
  • Loading branch information
kattrali authored Oct 29, 2018
2 parents fe8c2ca + 5b19277 commit c08912a
Show file tree
Hide file tree
Showing 27 changed files with 1,800 additions and 33 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## TBD

### Enhancements

* Add a callback to allow modifying reports immediately prior to delivery,
including fatal crashes from native C/C++ code. For more information, see the
[callback reference](https://docs.bugsnag.com/platforms/android/sdk/customizing-error-reports).
[#379](https://github.com/bugsnag/bugsnag-android/pull/379)

### Bug fixes

* [NDK] Improve stack trace quality for signals raised on ARM32 devices
Expand Down
49 changes: 49 additions & 0 deletions features/before_send.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Feature: Run callbacks before reports delivered

Scenario: Change report before native crash report delivered
When I run "NativeBeforeSendScenario"
And I configure the app to run in the "non-crashy" state
And I relaunch the app
Then I should receive a request
And the request payload contains a completed native report
And the exception "errorClass" equals "SIGSEGV"
And the exception "message" equals "Segmentation violation (invalid memory reference)"
And the event "context" equals "!important"
And the exception "type" equals "c"
And the event "severity" equals "error"
And the event "unhandled" is true

Scenario: Change report before JVM crash report delivered
When I run "BeforeSendScenario"
And I configure the app to run in the "non-crashy" state
And I relaunch the app
Then I should receive a request
And the request payload contains a completed native report
And the exception "errorClass" equals "java.lang.RuntimeException"
And the exception "message" equals "Ruh-roh"
And the event "context" equals "UNSET"
And the exception "type" equals "android"
And the event "severity" equals "error"
And the event "unhandled" is true

Scenario: Change report before native notify() report delivered
When I run "NativeNotifyBeforeSendScenario"
Then I should receive a request
And the request is a valid for the error reporting API
And the exception "errorClass" equals "Ad-hoc"
And the exception "message" equals "Auto-generated issue"
And the event "context" equals "hello"
And the exception "type" equals "c"
And the event "severity" equals "info"
And the event "unhandled" is false

Scenario: Change report before notify() report delivered
When I run "NotifyBeforeSendScenario"
Then I should receive a request
And the request is a valid for the error reporting API
And the exception "errorClass" equals "java.lang.Exception"
And the exception "message" equals "Registration failure"
And the event "context" equals "RESET"
And the exception "type" equals "android"
And the event "severity" equals "error"
And the event "unhandled" is false
7 changes: 7 additions & 0 deletions features/fixtures/mazerunner/src/main/cpp/bugsnags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ Java_com_bugsnag_android_mazerunner_scenarios_CXXExtraordinaryLongStringScenario
return 12062 / value;
}

JNIEXPORT void JNICALL
Java_com_bugsnag_android_mazerunner_scenarios_NativeNotifyBeforeSendScenario_activate(JNIEnv *env,
jobject instance) {
bugsnag_notify_env(env, (char *)"Ad-hoc",
(char *)"Auto-generated issue", BSG_SEVERITY_INFO);
}

JNIEXPORT void JNICALL
Java_com_bugsnag_android_mazerunner_scenarios_CXXNotifyScenario_activate(JNIEnv *env,
jobject instance) {
Expand Down
6 changes: 6 additions & 0 deletions features/fixtures/mazerunner/src/main/cpp/entrypoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_cra
printf("This one here: %d\n", crash_write_read_only_mem(42));
}

JNIEXPORT void JNICALL
Java_com_bugsnag_android_mazerunner_scenarios_NativeBeforeSendScenario_crash(JNIEnv *env,
jobject instance) {
printf("This one here: %s\n", crash_improper_cast(39));
}

JNIEXPORT void JNICALL
Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash(JNIEnv *env,
jobject instance) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.bugsnag.android.mazerunner.scenarios;

import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.BeforeSend;
import com.bugsnag.android.Configuration;
import com.bugsnag.android.Report;

import android.content.Context;
import android.support.annotation.NonNull;


public class BeforeSendScenario extends Scenario {

public BeforeSendScenario(@NonNull Configuration config, @NonNull Context context) {
super(config, context);
config.setAutoCaptureSessions(false);
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
report.getError().setContext("UNSET");

return true;
}
});
}

@Override
public void run() {
super.run();
String metadata = getEventMetaData();
if (metadata != null && metadata.equals("non-crashy")) {
return;
}
throw new RuntimeException("Ruh-roh");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.bugsnag.android.mazerunner.scenarios;

import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.BeforeSend;
import com.bugsnag.android.Configuration;
import com.bugsnag.android.Report;

import android.content.Context;
import android.support.annotation.NonNull;


public class NativeBeforeSendScenario extends Scenario {

static {
System.loadLibrary("bugsnag-ndk");
System.loadLibrary("entrypoint");
}

public native void crash();

public NativeBeforeSendScenario(@NonNull Configuration config, @NonNull Context context) {
super(config, context);
config.setAutoCaptureSessions(false);
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
report.getError().setContext("!important");

return true;
}
});
}

@Override
public void run() {
super.run();
String metadata = getEventMetaData();
if (metadata != null && metadata.equals("non-crashy")) {
return;
}
crash();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.bugsnag.android.mazerunner.scenarios;

import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.BeforeSend;
import com.bugsnag.android.Configuration;
import com.bugsnag.android.Report;

import android.content.Context;
import android.support.annotation.NonNull;


public class NativeNotifyBeforeSendScenario extends Scenario {

static {
System.loadLibrary("bugsnag-ndk");
System.loadLibrary("entrypoint");
}

public native void activate();

public NativeNotifyBeforeSendScenario(@NonNull Configuration config, @NonNull Context context) {
super(config, context);
config.setAutoCaptureSessions(false);
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
report.getError().setContext("hello");

return true;
}
});
}

@Override
public void run() {
super.run();
activate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.bugsnag.android.mazerunner.scenarios;

import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.BeforeSend;
import com.bugsnag.android.Configuration;
import com.bugsnag.android.Report;
import com.bugsnag.android.Severity;

import android.content.Context;
import android.support.annotation.NonNull;


public class NotifyBeforeSendScenario extends Scenario {

public NotifyBeforeSendScenario(@NonNull Configuration config, @NonNull Context context) {
super(config, context);
config.setAutoCaptureSessions(false);
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
report.getError().setContext("RESET");
report.getError().setSeverity(Severity.ERROR);

return true;
}
});
}

@Override
public void run() {
super.run();
Bugsnag.notify(new Exception("Registration failure"));
}
}
114 changes: 114 additions & 0 deletions sdk/src/androidTest/java/com/bugsnag/android/BeforeSendTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.bugsnag.android;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class BeforeSendTest {

private Client client;
private Configuration config;
private Report lastReport;
private String result = "";

/**
* Configures a client
*/
@Before
public void setUp() throws Exception {
result = "";
config = new Configuration("key");
config.setDelivery(new Delivery() {
@Override
public void deliver(SessionTrackingPayload payload,
Configuration config)
throws DeliveryFailureException {}

@Override
public void deliver(Report report,
Configuration config)
throws DeliveryFailureException {
lastReport = report;
}
});
client = new Client(InstrumentationRegistry.getContext(), config);
}

@After
public void tearDown() throws Exception {
lastReport = null;
Async.cancelTasks();
}

@Test
public void testCallbackOrderPreserved() {
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
result = result + "a";
return true;
}
});
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
result = result + "b";
return true;
}
});
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
result = result + "c";
return true;
}
});
client.notifyBlocking(new Exception("womp womp"));
assertEquals("abc", result);
assertNotNull(lastReport);
}

@Test
public void testCancelReport() {
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
result = result + "a";
return true;
}
});
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
result = result + "b";
return false;
}
});
client.notifyBlocking(new Exception("womp womp"));
assertEquals("ab", result);
assertNull(lastReport);
}

@Test
public void testAlterReport() {
config.beforeSend(new BeforeSend() {
@Override
public boolean run(Report report) {
report.getError().setGroupingHash("123");
return true;
}
});
client.notifyBlocking(new Exception("womp womp"));
assertEquals("123", lastReport.getError().getGroupingHash());
}
}
Loading

0 comments on commit c08912a

Please sign in to comment.