From f7961fb6d026c6a28e85967cb94fd5e53d0e4b2d Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 2 Aug 2018 10:32:13 +0100 Subject: [PATCH 01/15] feat: capture trace of error reporting thread and identify it with boolean flag in serialised json --- .../com/bugsnag/android/ThreadStateTest.kt | 54 +++++++++++++++++++ .../java/com/bugsnag/android/ThreadState.java | 24 ++++----- 2 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt new file mode 100644 index 0000000000..78f75fe65f --- /dev/null +++ b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt @@ -0,0 +1,54 @@ +package com.bugsnag.android + +import android.support.test.filters.SmallTest +import android.support.test.runner.AndroidJUnit4 +import com.bugsnag.android.BugsnagTestUtils.streamableToJsonArray +import org.json.JSONObject +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class ThreadStateTest { + + private val threadState = ThreadState(Configuration("api-key")) + private val json = streamableToJsonArray(threadState) + + /** + * Verifies that the required values for 'thread' are serialised as an array + */ + @Test + fun testSerialisation() { + for (k in 0 until json.length()) { + val thread = json[k] as JSONObject + assertNotNull(thread.getString("id")) + assertNotNull(thread.getString("name")) + assertNotNull(thread.getString("stacktrace")) + assertEquals("android", thread.getString("type")) + } + } + + /** + * Verifies that the current thread is serialised as an object, and that only this value + * contains the errorReportingThread boolean flag + */ + @Test + fun testCurrentThread() { + val currentThreadId = Thread.currentThread().id + var currentThreadCount = 0 + + for (k in 0 until json.length()) { + val thread = json[k] as JSONObject + val threadId = thread.getLong("id") + + if (threadId == currentThreadId) { + assertTrue(thread.getBoolean("errorReportingThread")) + currentThreadCount++ + } else { + assertFalse(thread.has("errorReportingThread")) + } + } + assertEquals(1, currentThreadCount) + } +} diff --git a/sdk/src/main/java/com/bugsnag/android/ThreadState.java b/sdk/src/main/java/com/bugsnag/android/ThreadState.java index 2e13daf717..9d43a35591 100644 --- a/sdk/src/main/java/com/bugsnag/android/ThreadState.java +++ b/sdk/src/main/java/com/bugsnag/android/ThreadState.java @@ -15,34 +15,26 @@ class ThreadState implements JsonStream.Streamable { private static final String THREAD_TYPE = "android"; - final Configuration config; + private final Configuration config; private final Thread[] threads; private final Map stackTraces; + private final long currentThreadId; ThreadState(Configuration config) { this.config = config; stackTraces = Thread.getAllStackTraces(); - threads = sanitiseThreads(Thread.currentThread().getId(), stackTraces); + currentThreadId = Thread.currentThread().getId(); + threads = sanitiseThreads(stackTraces); } /** * Returns an array of threads excluding the current thread, sorted by thread id * - * @param currentThreadId the current thread id - * @param liveThreads all live threads + * @param liveThreads all live threads */ - private Thread[] sanitiseThreads(long currentThreadId, - Map liveThreads) { + private Thread[] sanitiseThreads(Map liveThreads) { Set threadSet = liveThreads.keySet(); - // remove current thread - for (Iterator iterator = threadSet.iterator(); iterator.hasNext(); ) { - Thread thread = iterator.next(); - if (thread.getId() == currentThreadId) { - iterator.remove(); - } - } - Thread[] threads = threadSet.toArray(new Thread[threadSet.size()]); Arrays.sort(threads, new Comparator() { public int compare(@NonNull Thread lhs, @NonNull Thread rhs) { @@ -63,6 +55,10 @@ public void toStream(@NonNull JsonStream writer) throws IOException { StackTraceElement[] stacktrace = stackTraces.get(thread); writer.name("stacktrace").value(new Stacktrace(config, stacktrace)); + + if (currentThreadId == thread.getId()) { + writer.name("errorReportingThread").value(true); + } writer.endObject(); } writer.endArray(); From 60b59347b98cf9cdedf6bec69cdba06431a3e9e0 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 2 Aug 2018 10:46:38 +0100 Subject: [PATCH 02/15] add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ece927071..c0e797e559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 4.X.X (TBD) + +* Capture trace of error reporting thread and identify with boolean flag [#355](https://github.com/bugsnag/bugsnag-android/pull/355) + ## 4.6.0 (2018-08-02) * Android P compatibility fixes - ensure available information on StrictMode violations is collected [#350](https://github.com/bugsnag/bugsnag-android/pull/350) From 9461b25f5717a023ba361f51c60ad4923e01b7e9 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 7 Aug 2018 10:26:18 +0100 Subject: [PATCH 03/15] feat: pass current thread into threadstate when notifying, rather than relying on Thread.currentThre --- .../com/bugsnag/android/TestHarnessHooks.kt | 3 +- .../com/bugsnag/android/BeforeNotifyTest.kt | 3 +- .../com/bugsnag/android/ErrorStoreTest.java | 3 +- .../java/com/bugsnag/android/ErrorTest.java | 43 ++++++++++------ .../com/bugsnag/android/ExceptionsTest.java | 2 +- .../com/bugsnag/android/NullMetadataTest.java | 12 +++-- .../java/com/bugsnag/android/ReportTest.java | 2 +- .../com/bugsnag/android/ThreadStateTest.kt | 2 +- .../main/java/com/bugsnag/android/Client.java | 50 ++++++++++++------- .../main/java/com/bugsnag/android/Error.java | 12 +++-- .../com/bugsnag/android/ExceptionHandler.java | 4 +- .../java/com/bugsnag/android/ThreadState.java | 4 +- 12 files changed, 87 insertions(+), 53 deletions(-) diff --git a/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt b/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt index 6cba832f00..9a3d2ce91c 100644 --- a/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt +++ b/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt @@ -66,6 +66,7 @@ internal fun createCustomHeaderDelivery(context: Context): Delivery { internal fun writeErrorToStore(client: Client) { - val error = Error.Builder(Configuration("api-key"), RuntimeException(), null).build() + val error = Error.Builder(Configuration("api-key"), RuntimeException(), null, + Thread.currentThread()).build() client.errorStore.write(error) } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt index 2a2a82c646..1132f9ce5b 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt @@ -23,7 +23,8 @@ class BeforeNotifyTest { false } - val error = Error.Builder(config, RuntimeException("Test"), null).build() + val error = Error.Builder(config, RuntimeException("Test"), null, + Thread.currentThread()).build() beforeNotify.run(error) assertEquals(context, error.context) } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java index d6f5656732..eddf017cfb 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java @@ -71,7 +71,8 @@ public void testWrite() throws Exception { @NonNull private Error writeErrorToStore() { - Error error = new Error.Builder(config, new RuntimeException(), null).build(); + Error error = new Error.Builder(config, new RuntimeException(), + null, Thread.currentThread()).build(); errorStore.write(error); return error; } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java index b58613b5cb..082eb846a2 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java @@ -41,7 +41,7 @@ public class ErrorTest { public void setUp() throws Exception { config = new Configuration("api-key"); RuntimeException exception = new RuntimeException("Example message"); - error = new Error.Builder(config, exception, null).build(); + error = new Error.Builder(config, exception, null, Thread.currentThread()).build(); } @After @@ -55,12 +55,14 @@ public void testShouldIgnoreClass() { // Shouldn't ignore classes not in ignoreClasses RuntimeException runtimeException = new RuntimeException("Test"); - Error error = new Error.Builder(config, runtimeException, null).build(); + Error error = new Error.Builder(config, + runtimeException, null, Thread.currentThread()).build(); assertFalse(error.shouldIgnoreClass()); // Should ignore errors in ignoreClasses IOException ioException = new IOException("Test"); - error = new Error.Builder(config, ioException, null).build(); + error = new Error.Builder(config, + ioException, null, Thread.currentThread()).build(); assertTrue(error.shouldIgnoreClass()); } @@ -91,7 +93,8 @@ public void testBasicSerialization() throws JSONException, IOException { @Test public void testHandledSerialisation() throws Exception { - Error err = new Error.Builder(config, new RuntimeException(), null) + Error err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); @@ -108,7 +111,8 @@ public void testHandledSerialisation() throws Exception { @Test public void testUnhandledSerialisation() throws Exception { - Error err = new Error.Builder(config, new RuntimeException(), null) + Error err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()) .severityReasonType(HandledState.REASON_UNHANDLED_EXCEPTION) .severity(Severity.ERROR) .build(); @@ -126,7 +130,8 @@ public void testUnhandledSerialisation() throws Exception { @Test public void testPromiseRejectionSerialisation() throws Exception { - Error err = new Error.Builder(config, new RuntimeException(), null) + Error err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()) .severityReasonType(HandledState.REASON_PROMISE_REJECTION) .severity(Severity.ERROR) .build(); @@ -144,7 +149,8 @@ public void testPromiseRejectionSerialisation() throws Exception { @Test public void testLogSerialisation() throws Exception { - Error err = new Error.Builder(config, new RuntimeException(), null) + Error err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()) .severityReasonType(HandledState.REASON_LOG) .severity(Severity.WARNING) .attributeValue("warning") @@ -179,7 +185,8 @@ public void testUserSpecifiedSerialisation() throws Exception { @Test public void testStrictModeSerialisation() throws Exception { - Error err = new Error.Builder(config, new RuntimeException(), null) + Error err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()) .severityReasonType(HandledState.REASON_STRICT_MODE) .attributeValue("Test") .build(); @@ -244,7 +251,8 @@ public void testSetSeverity() throws JSONException, IOException { @Test public void testSessionIncluded() throws Exception { Session session = generateSession(); - Error err = new Error.Builder(config, new RuntimeException(), session).build(); + Error err = new Error.Builder(config, + new RuntimeException(), session, Thread.currentThread()).build(); JSONObject errorJson = streamableToJson(err); assertNotNull(errorJson); @@ -264,7 +272,8 @@ public void testSessionIncluded() throws Exception { @Test(expected = JSONException.class) public void testSessionExcluded() throws Exception { - Error err = new Error.Builder(config, new RuntimeException(), null).build(); + Error err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()).build(); JSONObject errorJson = streamableToJson(err); assertNotNull(errorJson); @@ -274,10 +283,12 @@ public void testSessionExcluded() throws Exception { @Test public void checkExceptionMessageNullity() throws Exception { String msg = "Foo"; - Error err = new Error.Builder(config, new RuntimeException(msg), null).build(); + Error err = new Error.Builder(config, + new RuntimeException(msg), null, Thread.currentThread()).build(); assertEquals(msg, err.getExceptionMessage()); - err = new Error.Builder(config, new RuntimeException(), null).build(); + err = new Error.Builder(config, + new RuntimeException(), null, Thread.currentThread()).build(); assertEquals("", err.getExceptionMessage()); } @@ -298,7 +309,8 @@ public void testSendThreadsDisabled() throws Exception { public void testBugsnagExceptionName() throws Exception { BugsnagException exception = new BugsnagException("Busgang", "exceptional", new StackTraceElement[]{}); - Error err = new Error.Builder(config, exception, null).build(); + Error err = new Error.Builder(config, + exception, null, Thread.currentThread()).build(); assertEquals("Busgang", err.getExceptionName()); } @@ -357,7 +369,8 @@ public void testSetUser() throws Exception { @Test public void testBuilderMetaData() { Configuration config = new Configuration("api-key"); - Error.Builder builder = new Error.Builder(config, new RuntimeException("foo"), null); + Error.Builder builder = new Error.Builder(config, + new RuntimeException("foo"), null, Thread.currentThread()); assertNotNull(builder.metaData(new MetaData()).build()); @@ -401,7 +414,7 @@ public void testBuilderNullSession() throws Throwable { Session session = generateSession(); session.setAutoCaptured(true); - error = new Error.Builder(config, exception, session).build(); + error = new Error.Builder(config, exception, session, Thread.currentThread()).build(); JSONObject errorJson = streamableToJson(error); assertFalse(errorJson.has("session")); diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ExceptionsTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ExceptionsTest.java index 1bac4006e2..d712e79587 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ExceptionsTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ExceptionsTest.java @@ -71,7 +71,7 @@ public void testNamedException() throws JSONException, IOException { StackTraceElement element = new StackTraceElement("Class", "method", "Class.java", 123); StackTraceElement[] frames = new StackTraceElement[]{element}; Error error = new Error.Builder(config, "RuntimeException", - "Example message", frames, null).build(); + "Example message", frames, null, Thread.currentThread()).build(); Exceptions exceptions = new Exceptions(config, error.getException()); JSONObject exceptionJson = streamableToJsonArray(exceptions).getJSONObject(0); diff --git a/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java b/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java index dbe7c969c5..0e03cdba01 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java @@ -42,20 +42,21 @@ public void tearDown() throws Exception { @Test public void testErrorDefaultMetaData() throws Exception { - Error error = new Error.Builder(config, throwable, null).build(); + Error error = new Error.Builder(config, throwable, null, Thread.currentThread()).build(); validateDefaultMetadata(error.getMetaData()); } @Test public void testSecondErrorDefaultMetaData() throws Exception { Error error = new Error.Builder(config, "RuntimeException", - "Something broke", new StackTraceElement[]{}, null).build(); + "Something broke", new StackTraceElement[]{}, + null, Thread.currentThread()).build(); validateDefaultMetadata(error.getMetaData()); } @Test public void testErrorSetMetadataRef() throws Exception { - Error error = new Error.Builder(config, throwable, null).build(); + Error error = new Error.Builder(config, throwable, null, Thread.currentThread()).build(); MetaData metaData = new MetaData(); metaData.addToTab(TAB_KEY, "test", "data"); error.setMetaData(metaData); @@ -64,7 +65,7 @@ public void testErrorSetMetadataRef() throws Exception { @Test public void testErrorSetNullMetadata() throws Exception { - Error error = new Error.Builder(config, throwable, null).build(); + Error error = new Error.Builder(config, throwable, null, Thread.currentThread()).build(); error.setMetaData(null); validateDefaultMetadata(error.getMetaData()); } @@ -97,7 +98,8 @@ public boolean run(Error error) { return false; } }); - Error error = new Error.Builder(config, new Throwable(), null).build(); + Error error = new Error.Builder(config, new Throwable(), + null, Thread.currentThread()).build(); Client client = Bugsnag.getClient(); client.notify(error, DeliveryStyle.SAME_THREAD, null); } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java index 1ec84e37bd..5a32c3b063 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java @@ -31,7 +31,7 @@ public class ReportTest { public void setUp() throws Exception { Configuration config = new Configuration("example-api-key"); RuntimeException exception = new RuntimeException("Something broke"); - Error error = new Error.Builder(config, exception, null).build(); + Error error = new Error.Builder(config, exception, null, Thread.currentThread()).build(); report = new Report("api-key", error); } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt index 78f75fe65f..3755006612 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt @@ -12,7 +12,7 @@ import org.junit.runner.RunWith @SmallTest class ThreadStateTest { - private val threadState = ThreadState(Configuration("api-key")) + private val threadState = ThreadState(Configuration("api-key"), Thread.currentThread()) private val json = streamableToJsonArray(threadState) /** diff --git a/sdk/src/main/java/com/bugsnag/android/Client.java b/sdk/src/main/java/com/bugsnag/android/Client.java index ff5727e10c..64e1d8896e 100644 --- a/sdk/src/main/java/com/bugsnag/android/Client.java +++ b/sdk/src/main/java/com/bugsnag/android/Client.java @@ -761,7 +761,8 @@ public void beforeRecordBreadcrumb(BeforeRecordBreadcrumb beforeRecordBreadcrumb * @param exception the exception to send to Bugsnag */ public void notify(@NonNull Throwable exception) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, !BLOCKING); @@ -775,7 +776,8 @@ public void notify(@NonNull Throwable exception) { * additional modification */ public void notify(@NonNull Throwable exception, Callback callback) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, DeliveryStyle.ASYNC, callback); @@ -795,7 +797,7 @@ public void notify(@NonNull String name, @NonNull StackTraceElement[] stacktrace, Callback callback) { Error error = new Error.Builder(config, name, message, stacktrace, - sessionTracker.getCurrentSession()) + sessionTracker.getCurrentSession(), Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, DeliveryStyle.ASYNC, callback); @@ -809,7 +811,8 @@ public void notify(@NonNull String name, * Severity.WARNING or Severity.INFO */ public void notify(@NonNull Throwable exception, Severity severity) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .severity(severity) .build(); notify(error, !BLOCKING); @@ -825,7 +828,8 @@ public void notify(@NonNull Throwable exception, Severity severity) { @Deprecated public void notify(@NonNull Throwable exception, @NonNull MetaData metaData) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .metaData(metaData) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); @@ -844,7 +848,8 @@ public void notify(@NonNull Throwable exception, @Deprecated public void notify(@NonNull Throwable exception, Severity severity, @NonNull MetaData metaData) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .metaData(metaData) .severity(severity) .build(); @@ -868,7 +873,7 @@ public void notify(@NonNull String name, @NonNull String message, @NonNull StackTraceElement[] stacktrace, Severity severity, @NonNull MetaData metaData) { Error error = new Error.Builder(config, name, message, - stacktrace, sessionTracker.getCurrentSession()) + stacktrace, sessionTracker.getCurrentSession(), Thread.currentThread()) .severity(severity) .metaData(metaData) .build(); @@ -896,7 +901,7 @@ public void notify(@NonNull String name, Severity severity, @NonNull MetaData metaData) { Error error = new Error.Builder(config, name, message, - stacktrace, sessionTracker.getCurrentSession()) + stacktrace, sessionTracker.getCurrentSession(), Thread.currentThread()) .severity(severity) .metaData(metaData) .build(); @@ -1005,7 +1010,8 @@ public void run() { * @param exception the exception to send to Bugsnag */ public void notifyBlocking(@NonNull Throwable exception) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, BLOCKING); @@ -1019,7 +1025,8 @@ public void notifyBlocking(@NonNull Throwable exception) { * additional modification */ public void notifyBlocking(@NonNull Throwable exception, Callback callback) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, DeliveryStyle.SAME_THREAD, callback); @@ -1039,7 +1046,7 @@ public void notifyBlocking(@NonNull String name, @NonNull StackTraceElement[] stacktrace, Callback callback) { Error error = new Error.Builder(config, name, message, - stacktrace, sessionTracker.getCurrentSession()) + stacktrace, sessionTracker.getCurrentSession(), Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, DeliveryStyle.SAME_THREAD, callback); @@ -1055,7 +1062,8 @@ public void notifyBlocking(@NonNull String name, @Deprecated public void notifyBlocking(@NonNull Throwable exception, @NonNull MetaData metaData) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .metaData(metaData) .build(); @@ -1074,7 +1082,8 @@ public void notifyBlocking(@NonNull Throwable exception, @Deprecated public void notifyBlocking(@NonNull Throwable exception, Severity severity, @NonNull MetaData metaData) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), + Thread.currentThread()) .metaData(metaData) .severity(severity) .build(); @@ -1100,7 +1109,7 @@ public void notifyBlocking(@NonNull String name, Severity severity, @NonNull MetaData metaData) { Error error = new Error.Builder(config, name, message, - stacktrace, sessionTracker.getCurrentSession()) + stacktrace, sessionTracker.getCurrentSession(), Thread.currentThread()) .severity(severity) .metaData(metaData) .build(); @@ -1128,7 +1137,7 @@ public void notifyBlocking(@NonNull String name, Severity severity, @NonNull MetaData metaData) { Error error = new Error.Builder(config, name, message, - stacktrace, sessionTracker.getCurrentSession()) + stacktrace, sessionTracker.getCurrentSession(), Thread.currentThread()) .severity(severity) .metaData(metaData) .build(); @@ -1144,7 +1153,8 @@ public void notifyBlocking(@NonNull String name, * Severity.WARNING or Severity.INFO */ public void notifyBlocking(@NonNull Throwable exception, Severity severity) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, + sessionTracker.getCurrentSession(), Thread.currentThread()) .severity(severity) .build(); notify(error, BLOCKING); @@ -1172,7 +1182,8 @@ public void internalClientNotify(@NonNull Throwable exception, Logger.info(msg); @SuppressWarnings("WrongConstant") - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + Error error = new Error.Builder(config, exception, + sessionTracker.getCurrentSession(), Thread.currentThread()) .severity(Severity.fromString(severity)) .severityReasonType(severityReason) .attributeValue(logLevel) @@ -1329,8 +1340,9 @@ void deliver(@NonNull Report report, @NonNull Error error) { */ void cacheAndNotify(@NonNull Throwable exception, Severity severity, MetaData metaData, @HandledState.SeverityReason String severityReason, - @Nullable String attributeValue) { - Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession()) + @Nullable String attributeValue, Thread thread) { + Error error = new Error.Builder(config, exception, + sessionTracker.getCurrentSession(), thread) .severity(severity) .metaData(metaData) .severityReasonType(severityReason) diff --git a/sdk/src/main/java/com/bugsnag/android/Error.java b/sdk/src/main/java/com/bugsnag/android/Error.java index 7fd37d761f..f047a42101 100644 --- a/sdk/src/main/java/com/bugsnag/android/Error.java +++ b/sdk/src/main/java/com/bugsnag/android/Error.java @@ -399,8 +399,11 @@ static class Builder { @HandledState.SeverityReason private String severityReasonType; - Builder(@NonNull Configuration config, @NonNull Throwable exception, Session session) { - this.threadState = new ThreadState(config); + Builder(@NonNull Configuration config, + @NonNull Throwable exception, + Session session, + Thread thread) { + this.threadState = new ThreadState(config, thread); this.config = config; this.exception = exception; this.severityReasonType = HandledState.REASON_USER_SPECIFIED; // default @@ -414,8 +417,9 @@ static class Builder { } Builder(@NonNull Configuration config, @NonNull String name, - @NonNull String message, @NonNull StackTraceElement[] frames, Session session) { - this(config, new BugsnagException(name, message, frames), session); + @NonNull String message, @NonNull StackTraceElement[] frames, + Session session, Thread thread) { + this(config, new BugsnagException(name, message, frames), session, thread); } Builder severityReasonType(@HandledState.SeverityReason String severityReasonType) { diff --git a/sdk/src/main/java/com/bugsnag/android/ExceptionHandler.java b/sdk/src/main/java/com/bugsnag/android/ExceptionHandler.java index caad55593e..79e2f79b34 100644 --- a/sdk/src/main/java/com/bugsnag/android/ExceptionHandler.java +++ b/sdk/src/main/java/com/bugsnag/android/ExceptionHandler.java @@ -77,12 +77,12 @@ public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwab StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); client.cacheAndNotify(throwable, Severity.ERROR, - metaData, severityReason, violationDesc); + metaData, severityReason, violationDesc, thread); StrictMode.setThreadPolicy(originalThreadPolicy); } else { client.cacheAndNotify(throwable, Severity.ERROR, - metaData, severityReason, violationDesc); + metaData, severityReason, violationDesc, thread); } } diff --git a/sdk/src/main/java/com/bugsnag/android/ThreadState.java b/sdk/src/main/java/com/bugsnag/android/ThreadState.java index 9d43a35591..b15e3a19d9 100644 --- a/sdk/src/main/java/com/bugsnag/android/ThreadState.java +++ b/sdk/src/main/java/com/bugsnag/android/ThreadState.java @@ -20,10 +20,10 @@ class ThreadState implements JsonStream.Streamable { private final Map stackTraces; private final long currentThreadId; - ThreadState(Configuration config) { + ThreadState(Configuration config, Thread thread) { this.config = config; stackTraces = Thread.getAllStackTraces(); - currentThreadId = Thread.currentThread().getId(); + currentThreadId = thread.getId(); threads = sanitiseThreads(stackTraces); } From 37f4de29af7c05618ca7424c9640cfb307975630 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 7 Aug 2018 10:29:46 +0100 Subject: [PATCH 04/15] docs: update javadoc for threadstate --- sdk/src/main/java/com/bugsnag/android/ThreadState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/com/bugsnag/android/ThreadState.java b/sdk/src/main/java/com/bugsnag/android/ThreadState.java index b15e3a19d9..bf5183f8f2 100644 --- a/sdk/src/main/java/com/bugsnag/android/ThreadState.java +++ b/sdk/src/main/java/com/bugsnag/android/ThreadState.java @@ -28,7 +28,7 @@ class ThreadState implements JsonStream.Streamable { } /** - * Returns an array of threads excluding the current thread, sorted by thread id + * Returns an array of threads including the current thread, sorted by thread id * * @param liveThreads all live threads */ From d75f08c2f0e5e0aafb347927726d3174daac9c21 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 7 Aug 2018 10:31:16 +0100 Subject: [PATCH 05/15] rename thread -> currentThread --- sdk/src/main/java/com/bugsnag/android/ThreadState.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/java/com/bugsnag/android/ThreadState.java b/sdk/src/main/java/com/bugsnag/android/ThreadState.java index bf5183f8f2..62be2a52b0 100644 --- a/sdk/src/main/java/com/bugsnag/android/ThreadState.java +++ b/sdk/src/main/java/com/bugsnag/android/ThreadState.java @@ -20,10 +20,10 @@ class ThreadState implements JsonStream.Streamable { private final Map stackTraces; private final long currentThreadId; - ThreadState(Configuration config, Thread thread) { + ThreadState(Configuration config, Thread currentThread) { this.config = config; stackTraces = Thread.getAllStackTraces(); - currentThreadId = thread.getId(); + currentThreadId = currentThread.getId(); threads = sanitiseThreads(stackTraces); } From 932397f19fb4d958396a88accaaacbc95463868c Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 7 Aug 2018 11:09:04 +0100 Subject: [PATCH 06/15] test: verify that the current thread is not always used in thread state --- .../com/bugsnag/android/ThreadStateTest.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt index 3755006612..a62b914245 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt @@ -51,4 +51,32 @@ class ThreadStateTest { } assertEquals(1, currentThreadCount) } + + /** + * Verifies that a thread different from the current thread is serialised as an object, + * and that only this value contains the errorReportingThread boolean flag + */ + @Test + fun testDifferentThread() { + val otherThread = Thread.getAllStackTraces() + .filter { it.key != Thread.currentThread() } + .map { it.key } + .first() + + val json = streamableToJsonArray(ThreadState(Configuration("api-key"), otherThread)) + var currentThreadCount = 0 + + for (k in 0 until json.length()) { + val thread = json[k] as JSONObject + val threadId = thread.getLong("id") + + if (threadId == otherThread.id) { + assertTrue(thread.getBoolean("errorReportingThread")) + currentThreadCount++ + } else { + assertFalse(thread.has("errorReportingThread")) + } + } + assertEquals(1, currentThreadCount) + } } From 213b33b1890b27acad5968ed939d155009ea758e Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 9 Aug 2018 13:43:51 +0100 Subject: [PATCH 07/15] fix: add the current thread to the thread stacktrace map if it is not already a key An issue exists in api 24/25 where not all threads are reported correctly from Thread.getAllStacktraces(). Adding it in manually and calling Thread.getStacktrace() appears to negate this behaviour. https://issuetracker.google.com/issues/64122757 --- .../com/bugsnag/android/ThreadStateTest.kt | 35 +++++++++++++++++-- .../main/java/com/bugsnag/android/Error.java | 2 +- .../java/com/bugsnag/android/ThreadState.java | 12 +++++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt index a62b914245..9c105f5533 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt @@ -8,11 +8,13 @@ import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith + @RunWith(AndroidJUnit4::class) @SmallTest class ThreadStateTest { - private val threadState = ThreadState(Configuration("api-key"), Thread.currentThread()) + private val configuration = Configuration("api-key") + private val threadState = ThreadState(configuration, Thread.currentThread(), Thread.getAllStackTraces()) private val json = streamableToJsonArray(threadState) /** @@ -63,7 +65,8 @@ class ThreadStateTest { .map { it.key } .first() - val json = streamableToJsonArray(ThreadState(Configuration("api-key"), otherThread)) + val state = ThreadState(configuration, otherThread, Thread.getAllStackTraces()) + val json = streamableToJsonArray(state) var currentThreadCount = 0 for (k in 0 until json.length()) { @@ -79,4 +82,32 @@ class ThreadStateTest { } assertEquals(1, currentThreadCount) } + + /** + * Verifies that if the current thread is missing from the available traces as reported by + * [Thread.getAllStackTraces], its stacktrace will still be serialised + */ + @Test + fun testMissingCurrentThread() { + val currentThread = Thread.currentThread() + val missingTraces = Thread.getAllStackTraces() + missingTraces.remove(currentThread) + + val state = ThreadState(configuration, currentThread, missingTraces) + val json = streamableToJsonArray(state) + + var currentThreadCount = 0 + + for (k in 0 until json.length()) { + val thread = json[k] as JSONObject + val threadId = thread.getLong("id") + + if (threadId == currentThread.id) { + currentThreadCount++ + val jsonArray = thread.getJSONArray("stacktrace") + assertTrue(jsonArray.length() > 0) + } + } + assertEquals(1, currentThreadCount) + } } diff --git a/sdk/src/main/java/com/bugsnag/android/Error.java b/sdk/src/main/java/com/bugsnag/android/Error.java index f047a42101..7439ebf930 100644 --- a/sdk/src/main/java/com/bugsnag/android/Error.java +++ b/sdk/src/main/java/com/bugsnag/android/Error.java @@ -403,7 +403,7 @@ static class Builder { @NonNull Throwable exception, Session session, Thread thread) { - this.threadState = new ThreadState(config, thread); + this.threadState = new ThreadState(config, thread, Thread.getAllStackTraces()); this.config = config; this.exception = exception; this.severityReasonType = HandledState.REASON_USER_SPECIFIED; // default diff --git a/sdk/src/main/java/com/bugsnag/android/ThreadState.java b/sdk/src/main/java/com/bugsnag/android/ThreadState.java index 62be2a52b0..fdc6bc399c 100644 --- a/sdk/src/main/java/com/bugsnag/android/ThreadState.java +++ b/sdk/src/main/java/com/bugsnag/android/ThreadState.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.util.Arrays; import java.util.Comparator; -import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -20,9 +19,16 @@ class ThreadState implements JsonStream.Streamable { private final Map stackTraces; private final long currentThreadId; - ThreadState(Configuration config, Thread currentThread) { + ThreadState(Configuration config, Thread currentThread, Map allStackTraces) { this.config = config; - stackTraces = Thread.getAllStackTraces(); + stackTraces = allStackTraces; + + // API 24/25 don't record the currentThread, add it in manually + // https://issuetracker.google.com/issues/64122757 + if (!stackTraces.containsKey(currentThread)) { + stackTraces.put(currentThread, currentThread.getStackTrace()); + } + currentThreadId = currentThread.getId(); threads = sanitiseThreads(stackTraces); } From dc34285f3b29cbb8514fb6cfbc3ce1213176a2e2 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 9 Aug 2018 14:00:06 +0100 Subject: [PATCH 08/15] refactor: factor current thread test logic into separate function --- .../com/bugsnag/android/ThreadStateTest.kt | 51 +++++++------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt index 9c105f5533..26c38201c5 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt @@ -3,6 +3,7 @@ package com.bugsnag.android import android.support.test.filters.SmallTest import android.support.test.runner.AndroidJUnit4 import com.bugsnag.android.BugsnagTestUtils.streamableToJsonArray +import org.json.JSONArray import org.json.JSONObject import org.junit.Assert.* import org.junit.Test @@ -37,21 +38,7 @@ class ThreadStateTest { */ @Test fun testCurrentThread() { - val currentThreadId = Thread.currentThread().id - var currentThreadCount = 0 - - for (k in 0 until json.length()) { - val thread = json[k] as JSONObject - val threadId = thread.getLong("id") - - if (threadId == currentThreadId) { - assertTrue(thread.getBoolean("errorReportingThread")) - currentThreadCount++ - } else { - assertFalse(thread.has("errorReportingThread")) - } - } - assertEquals(1, currentThreadCount) + verifyCurrentThreadStructure(json, Thread.currentThread().id) } /** @@ -67,20 +54,7 @@ class ThreadStateTest { val state = ThreadState(configuration, otherThread, Thread.getAllStackTraces()) val json = streamableToJsonArray(state) - var currentThreadCount = 0 - - for (k in 0 until json.length()) { - val thread = json[k] as JSONObject - val threadId = thread.getLong("id") - - if (threadId == otherThread.id) { - assertTrue(thread.getBoolean("errorReportingThread")) - currentThreadCount++ - } else { - assertFalse(thread.has("errorReportingThread")) - } - } - assertEquals(1, currentThreadCount) + verifyCurrentThreadStructure(json, otherThread.id) } /** @@ -96,18 +70,29 @@ class ThreadStateTest { val state = ThreadState(configuration, currentThread, missingTraces) val json = streamableToJsonArray(state) + verifyCurrentThreadStructure(json, currentThread.id) { + assertTrue(it.getJSONArray("stacktrace").length() > 0) + } + } + + private fun verifyCurrentThreadStructure(json: JSONArray, + currentThreadId: Long, + action: ((thread: JSONObject) -> Unit)? = null) { var currentThreadCount = 0 for (k in 0 until json.length()) { val thread = json[k] as JSONObject val threadId = thread.getLong("id") - if (threadId == currentThread.id) { + if (threadId == currentThreadId) { + assertTrue(thread.getBoolean("errorReportingThread")) currentThreadCount++ - val jsonArray = thread.getJSONArray("stacktrace") - assertTrue(jsonArray.length() > 0) + action?.invoke(thread) + } else { + assertFalse(thread.has("errorReportingThread")) } } - assertEquals(1, currentThreadCount) + assertEquals("Expected one error reporting thread",1, currentThreadCount) } + } From 3949a2362c987b913af570014cbe81b72fd5ce84 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 9 Aug 2018 14:39:40 +0100 Subject: [PATCH 09/15] feat: use exception stacktrace when passed into threadstate constructor, rather than thread trace, f --- .../com/bugsnag/android/ThreadStateTest.kt | 70 +++++++++++++++++-- .../main/java/com/bugsnag/android/Error.java | 2 +- .../java/com/bugsnag/android/ThreadState.java | 9 ++- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt index 26c38201c5..65ebf9d193 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/ThreadStateTest.kt @@ -15,7 +15,7 @@ import org.junit.runner.RunWith class ThreadStateTest { private val configuration = Configuration("api-key") - private val threadState = ThreadState(configuration, Thread.currentThread(), Thread.getAllStackTraces()) + private val threadState = ThreadState(configuration, Thread.currentThread(), Thread.getAllStackTraces(), null) private val json = streamableToJsonArray(threadState) /** @@ -52,7 +52,7 @@ class ThreadStateTest { .map { it.key } .first() - val state = ThreadState(configuration, otherThread, Thread.getAllStackTraces()) + val state = ThreadState(configuration, otherThread, Thread.getAllStackTraces(), null) val json = streamableToJsonArray(state) verifyCurrentThreadStructure(json, otherThread.id) } @@ -67,7 +67,7 @@ class ThreadStateTest { val missingTraces = Thread.getAllStackTraces() missingTraces.remove(currentThread) - val state = ThreadState(configuration, currentThread, missingTraces) + val state = ThreadState(configuration, currentThread, missingTraces, null) val json = streamableToJsonArray(state) verifyCurrentThreadStructure(json, currentThread.id) { @@ -75,6 +75,68 @@ class ThreadStateTest { } } + /** + * Verifies that a handled error uses [Thread] for the reporting thread stacktrace + */ + @Test + fun testHandledStacktrace() { + val currentThread = Thread.currentThread() + val allStackTraces = Thread.getAllStackTraces() + val state = ThreadState(configuration, currentThread, allStackTraces, null) + val json = streamableToJsonArray(state) + + // find the stack trace for the current thread that was passed as a parameter + val expectedTrace = allStackTraces.filter { + it.key.id == currentThread.id + }.map { it.value }.first() + + verifyCurrentThreadStructure(json, currentThread.id) { + + // the thread id + name should always be used + assertEquals(currentThread.name, it.getString("name")) + assertEquals(currentThread.id, it.getLong("id")) + + // stacktrace should come from the thread (check same length and line numbers) + val serialisedTrace = it.getJSONArray("stacktrace") + assertEquals(expectedTrace.size, serialisedTrace.length()) + + expectedTrace.forEachIndexed { index, element -> + val jsonObject = serialisedTrace.getJSONObject(index) + assertEquals(element.lineNumber, jsonObject.getInt("lineNumber")) + } + } + } + + /** + * * Verifies that an unhandled error uses [Exception] for the reporting thread stacktrace + */ + @Test + fun testUnhandledStacktrace() { + val currentThread = Thread.currentThread() + val allStackTraces = Thread.getAllStackTraces() + val exc: Throwable = RuntimeException("Whoops") + val expectedTrace = exc.stackTrace + + val state = ThreadState(configuration, currentThread, allStackTraces, exc) + val json = streamableToJsonArray(state) + + verifyCurrentThreadStructure(json, currentThread.id) { + + // the thread id + name should always be used + assertEquals(currentThread.name, it.getString("name")) + assertEquals(currentThread.id, it.getLong("id")) + + // stacktrace should come from the exception (check different length) + val serialisedTrace = it.getJSONArray("stacktrace") + assertEquals(expectedTrace.size, serialisedTrace.length()) + + expectedTrace.forEachIndexed { index, element -> + val jsonObject = serialisedTrace.getJSONObject(index) + assertEquals(element.lineNumber, jsonObject.getInt("lineNumber")) + } + } + } + private fun verifyCurrentThreadStructure(json: JSONArray, currentThreadId: Long, action: ((thread: JSONObject) -> Unit)? = null) { @@ -92,7 +154,7 @@ class ThreadStateTest { assertFalse(thread.has("errorReportingThread")) } } - assertEquals("Expected one error reporting thread",1, currentThreadCount) + assertEquals("Expected one error reporting thread", 1, currentThreadCount) } } diff --git a/sdk/src/main/java/com/bugsnag/android/Error.java b/sdk/src/main/java/com/bugsnag/android/Error.java index 7439ebf930..15c90ae0b1 100644 --- a/sdk/src/main/java/com/bugsnag/android/Error.java +++ b/sdk/src/main/java/com/bugsnag/android/Error.java @@ -403,7 +403,7 @@ static class Builder { @NonNull Throwable exception, Session session, Thread thread) { - this.threadState = new ThreadState(config, thread, Thread.getAllStackTraces()); + this.threadState = new ThreadState(config, thread, Thread.getAllStackTraces(), null); this.config = config; this.exception = exception; this.severityReasonType = HandledState.REASON_USER_SPECIFIED; // default diff --git a/sdk/src/main/java/com/bugsnag/android/ThreadState.java b/sdk/src/main/java/com/bugsnag/android/ThreadState.java index fdc6bc399c..4ae7ba317d 100644 --- a/sdk/src/main/java/com/bugsnag/android/ThreadState.java +++ b/sdk/src/main/java/com/bugsnag/android/ThreadState.java @@ -1,6 +1,7 @@ package com.bugsnag.android; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.io.IOException; import java.util.Arrays; @@ -19,7 +20,10 @@ class ThreadState implements JsonStream.Streamable { private final Map stackTraces; private final long currentThreadId; - ThreadState(Configuration config, Thread currentThread, Map allStackTraces) { + public ThreadState(@NonNull Configuration config, + @NonNull Thread currentThread, + @NonNull Map allStackTraces, + @Nullable Throwable exc) { this.config = config; stackTraces = allStackTraces; @@ -28,6 +32,9 @@ class ThreadState implements JsonStream.Streamable { if (!stackTraces.containsKey(currentThread)) { stackTraces.put(currentThread, currentThread.getStackTrace()); } + if (exc != null) { // unhandled errors use the exception trace + stackTraces.put(currentThread, exc.getStackTrace()); + } currentThreadId = currentThread.getId(); threads = sanitiseThreads(stackTraces); From 2f83a7c95684dedadbe978ee5c7b9a33311f6cb1 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 9 Aug 2018 14:46:01 +0100 Subject: [PATCH 10/15] feat: update exceptionHandler threadstate to record exception stacktrace for unhandled errors --- .../com/bugsnag/android/TestHarnessHooks.kt | 2 +- .../com/bugsnag/android/BeforeNotifyTest.kt | 2 +- .../com/bugsnag/android/ErrorStoreTest.java | 2 +- .../java/com/bugsnag/android/ErrorTest.java | 31 ++++++++++--------- .../com/bugsnag/android/NullMetadataTest.java | 11 ++++--- .../java/com/bugsnag/android/ReportTest.java | 3 +- .../main/java/com/bugsnag/android/Client.java | 24 +++++++------- .../main/java/com/bugsnag/android/Error.java | 10 +++--- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt b/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt index 9a3d2ce91c..98bea56795 100644 --- a/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt +++ b/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt @@ -67,6 +67,6 @@ internal fun createCustomHeaderDelivery(context: Context): Delivery { internal fun writeErrorToStore(client: Client) { val error = Error.Builder(Configuration("api-key"), RuntimeException(), null, - Thread.currentThread()).build() + Thread.currentThread(), false).build() client.errorStore.write(error) } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt index 1132f9ce5b..279cc061ff 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt +++ b/sdk/src/androidTest/java/com/bugsnag/android/BeforeNotifyTest.kt @@ -24,7 +24,7 @@ class BeforeNotifyTest { } val error = Error.Builder(config, RuntimeException("Test"), null, - Thread.currentThread()).build() + Thread.currentThread(), false).build() beforeNotify.run(error) assertEquals(context, error.context) } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java index eddf017cfb..6b84c75f01 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ErrorStoreTest.java @@ -72,7 +72,7 @@ public void testWrite() throws Exception { @NonNull private Error writeErrorToStore() { Error error = new Error.Builder(config, new RuntimeException(), - null, Thread.currentThread()).build(); + null, Thread.currentThread(), false).build(); errorStore.write(error); return error; } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java index 082eb846a2..f35bfa1bef 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ErrorTest.java @@ -41,7 +41,7 @@ public class ErrorTest { public void setUp() throws Exception { config = new Configuration("api-key"); RuntimeException exception = new RuntimeException("Example message"); - error = new Error.Builder(config, exception, null, Thread.currentThread()).build(); + error = new Error.Builder(config, exception, null, Thread.currentThread(), false).build(); } @After @@ -56,13 +56,13 @@ public void testShouldIgnoreClass() { // Shouldn't ignore classes not in ignoreClasses RuntimeException runtimeException = new RuntimeException("Test"); Error error = new Error.Builder(config, - runtimeException, null, Thread.currentThread()).build(); + runtimeException, null, Thread.currentThread(), false).build(); assertFalse(error.shouldIgnoreClass()); // Should ignore errors in ignoreClasses IOException ioException = new IOException("Test"); error = new Error.Builder(config, - ioException, null, Thread.currentThread()).build(); + ioException, null, Thread.currentThread(), false).build(); assertTrue(error.shouldIgnoreClass()); } @@ -94,7 +94,7 @@ public void testBasicSerialization() throws JSONException, IOException { @Test public void testHandledSerialisation() throws Exception { Error err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()) + new RuntimeException(), null, Thread.currentThread(), false) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); @@ -112,7 +112,7 @@ public void testHandledSerialisation() throws Exception { @Test public void testUnhandledSerialisation() throws Exception { Error err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()) + new RuntimeException(), null, Thread.currentThread(), false) .severityReasonType(HandledState.REASON_UNHANDLED_EXCEPTION) .severity(Severity.ERROR) .build(); @@ -131,7 +131,7 @@ public void testUnhandledSerialisation() throws Exception { @Test public void testPromiseRejectionSerialisation() throws Exception { Error err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()) + new RuntimeException(), null, Thread.currentThread(), false) .severityReasonType(HandledState.REASON_PROMISE_REJECTION) .severity(Severity.ERROR) .build(); @@ -150,7 +150,7 @@ public void testPromiseRejectionSerialisation() throws Exception { @Test public void testLogSerialisation() throws Exception { Error err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()) + new RuntimeException(), null, Thread.currentThread(), false) .severityReasonType(HandledState.REASON_LOG) .severity(Severity.WARNING) .attributeValue("warning") @@ -186,7 +186,7 @@ public void testUserSpecifiedSerialisation() throws Exception { @Test public void testStrictModeSerialisation() throws Exception { Error err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()) + new RuntimeException(), null, Thread.currentThread(), false) .severityReasonType(HandledState.REASON_STRICT_MODE) .attributeValue("Test") .build(); @@ -252,7 +252,7 @@ public void testSetSeverity() throws JSONException, IOException { public void testSessionIncluded() throws Exception { Session session = generateSession(); Error err = new Error.Builder(config, - new RuntimeException(), session, Thread.currentThread()).build(); + new RuntimeException(), session, Thread.currentThread(), false).build(); JSONObject errorJson = streamableToJson(err); assertNotNull(errorJson); @@ -273,7 +273,7 @@ public void testSessionIncluded() throws Exception { @Test(expected = JSONException.class) public void testSessionExcluded() throws Exception { Error err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()).build(); + new RuntimeException(), null, Thread.currentThread(), false).build(); JSONObject errorJson = streamableToJson(err); assertNotNull(errorJson); @@ -284,11 +284,11 @@ public void testSessionExcluded() throws Exception { public void checkExceptionMessageNullity() throws Exception { String msg = "Foo"; Error err = new Error.Builder(config, - new RuntimeException(msg), null, Thread.currentThread()).build(); + new RuntimeException(msg), null, Thread.currentThread(), false).build(); assertEquals(msg, err.getExceptionMessage()); err = new Error.Builder(config, - new RuntimeException(), null, Thread.currentThread()).build(); + new RuntimeException(), null, Thread.currentThread(), false).build(); assertEquals("", err.getExceptionMessage()); } @@ -310,7 +310,7 @@ public void testBugsnagExceptionName() throws Exception { BugsnagException exception = new BugsnagException("Busgang", "exceptional", new StackTraceElement[]{}); Error err = new Error.Builder(config, - exception, null, Thread.currentThread()).build(); + exception, null, Thread.currentThread(), false).build(); assertEquals("Busgang", err.getExceptionName()); } @@ -370,7 +370,7 @@ public void testSetUser() throws Exception { public void testBuilderMetaData() { Configuration config = new Configuration("api-key"); Error.Builder builder = new Error.Builder(config, - new RuntimeException("foo"), null, Thread.currentThread()); + new RuntimeException("foo"), null, Thread.currentThread(), false); assertNotNull(builder.metaData(new MetaData()).build()); @@ -414,7 +414,8 @@ public void testBuilderNullSession() throws Throwable { Session session = generateSession(); session.setAutoCaptured(true); - error = new Error.Builder(config, exception, session, Thread.currentThread()).build(); + error = new Error.Builder(config, exception, session, + Thread.currentThread(), false).build(); JSONObject errorJson = streamableToJson(error); assertFalse(errorJson.has("session")); diff --git a/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java b/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java index 0e03cdba01..7ec7736467 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/NullMetadataTest.java @@ -42,7 +42,8 @@ public void tearDown() throws Exception { @Test public void testErrorDefaultMetaData() throws Exception { - Error error = new Error.Builder(config, throwable, null, Thread.currentThread()).build(); + Error error = new Error.Builder(config, throwable, null, + Thread.currentThread(), false).build(); validateDefaultMetadata(error.getMetaData()); } @@ -56,7 +57,8 @@ public void testSecondErrorDefaultMetaData() throws Exception { @Test public void testErrorSetMetadataRef() throws Exception { - Error error = new Error.Builder(config, throwable, null, Thread.currentThread()).build(); + Error error = new Error.Builder(config, throwable, null, + Thread.currentThread(), false).build(); MetaData metaData = new MetaData(); metaData.addToTab(TAB_KEY, "test", "data"); error.setMetaData(metaData); @@ -65,7 +67,8 @@ public void testErrorSetMetadataRef() throws Exception { @Test public void testErrorSetNullMetadata() throws Exception { - Error error = new Error.Builder(config, throwable, null, Thread.currentThread()).build(); + Error error = new Error.Builder(config, throwable, null, + Thread.currentThread(), false).build(); error.setMetaData(null); validateDefaultMetadata(error.getMetaData()); } @@ -99,7 +102,7 @@ public boolean run(Error error) { } }); Error error = new Error.Builder(config, new Throwable(), - null, Thread.currentThread()).build(); + null, Thread.currentThread(), false).build(); Client client = Bugsnag.getClient(); client.notify(error, DeliveryStyle.SAME_THREAD, null); } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java index 5a32c3b063..10e4a76e52 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java @@ -31,7 +31,8 @@ public class ReportTest { public void setUp() throws Exception { Configuration config = new Configuration("example-api-key"); RuntimeException exception = new RuntimeException("Something broke"); - Error error = new Error.Builder(config, exception, null, Thread.currentThread()).build(); + Error error = new Error.Builder(config, exception, null, + Thread.currentThread(), false).build(); report = new Report("api-key", error); } diff --git a/sdk/src/main/java/com/bugsnag/android/Client.java b/sdk/src/main/java/com/bugsnag/android/Client.java index 64e1d8896e..7a8dd329c9 100644 --- a/sdk/src/main/java/com/bugsnag/android/Client.java +++ b/sdk/src/main/java/com/bugsnag/android/Client.java @@ -762,7 +762,7 @@ public void beforeRecordBreadcrumb(BeforeRecordBreadcrumb beforeRecordBreadcrumb */ public void notify(@NonNull Throwable exception) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, !BLOCKING); @@ -777,7 +777,7 @@ public void notify(@NonNull Throwable exception) { */ public void notify(@NonNull Throwable exception, Callback callback) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, DeliveryStyle.ASYNC, callback); @@ -812,7 +812,7 @@ public void notify(@NonNull String name, */ public void notify(@NonNull Throwable exception, Severity severity) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .severity(severity) .build(); notify(error, !BLOCKING); @@ -829,7 +829,7 @@ public void notify(@NonNull Throwable exception, Severity severity) { public void notify(@NonNull Throwable exception, @NonNull MetaData metaData) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .metaData(metaData) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); @@ -849,7 +849,7 @@ public void notify(@NonNull Throwable exception, public void notify(@NonNull Throwable exception, Severity severity, @NonNull MetaData metaData) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .metaData(metaData) .severity(severity) .build(); @@ -1011,7 +1011,7 @@ public void run() { */ public void notifyBlocking(@NonNull Throwable exception) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, BLOCKING); @@ -1026,7 +1026,7 @@ public void notifyBlocking(@NonNull Throwable exception) { */ public void notifyBlocking(@NonNull Throwable exception, Callback callback) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .build(); notify(error, DeliveryStyle.SAME_THREAD, callback); @@ -1063,7 +1063,7 @@ public void notifyBlocking(@NonNull String name, public void notifyBlocking(@NonNull Throwable exception, @NonNull MetaData metaData) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .severityReasonType(HandledState.REASON_HANDLED_EXCEPTION) .metaData(metaData) .build(); @@ -1083,7 +1083,7 @@ public void notifyBlocking(@NonNull Throwable exception, public void notifyBlocking(@NonNull Throwable exception, Severity severity, @NonNull MetaData metaData) { Error error = new Error.Builder(config, exception, sessionTracker.getCurrentSession(), - Thread.currentThread()) + Thread.currentThread(), false) .metaData(metaData) .severity(severity) .build(); @@ -1154,7 +1154,7 @@ public void notifyBlocking(@NonNull String name, */ public void notifyBlocking(@NonNull Throwable exception, Severity severity) { Error error = new Error.Builder(config, exception, - sessionTracker.getCurrentSession(), Thread.currentThread()) + sessionTracker.getCurrentSession(), Thread.currentThread(), false) .severity(severity) .build(); notify(error, BLOCKING); @@ -1183,7 +1183,7 @@ public void internalClientNotify(@NonNull Throwable exception, @SuppressWarnings("WrongConstant") Error error = new Error.Builder(config, exception, - sessionTracker.getCurrentSession(), Thread.currentThread()) + sessionTracker.getCurrentSession(), Thread.currentThread(), false) .severity(Severity.fromString(severity)) .severityReasonType(severityReason) .attributeValue(logLevel) @@ -1342,7 +1342,7 @@ void cacheAndNotify(@NonNull Throwable exception, Severity severity, MetaData me @HandledState.SeverityReason String severityReason, @Nullable String attributeValue, Thread thread) { Error error = new Error.Builder(config, exception, - sessionTracker.getCurrentSession(), thread) + sessionTracker.getCurrentSession(), thread, true) .severity(severity) .metaData(metaData) .severityReasonType(severityReason) diff --git a/sdk/src/main/java/com/bugsnag/android/Error.java b/sdk/src/main/java/com/bugsnag/android/Error.java index 15c90ae0b1..e968559495 100644 --- a/sdk/src/main/java/com/bugsnag/android/Error.java +++ b/sdk/src/main/java/com/bugsnag/android/Error.java @@ -401,9 +401,11 @@ static class Builder { Builder(@NonNull Configuration config, @NonNull Throwable exception, - Session session, - Thread thread) { - this.threadState = new ThreadState(config, thread, Thread.getAllStackTraces(), null); + @Nullable Session session, + @NonNull Thread thread, + boolean unhandled) { + Throwable exc = unhandled ? exception : null; + this.threadState = new ThreadState(config, thread, Thread.getAllStackTraces(), exc); this.config = config; this.exception = exception; this.severityReasonType = HandledState.REASON_USER_SPECIFIED; // default @@ -419,7 +421,7 @@ static class Builder { Builder(@NonNull Configuration config, @NonNull String name, @NonNull String message, @NonNull StackTraceElement[] frames, Session session, Thread thread) { - this(config, new BugsnagException(name, message, frames), session, thread); + this(config, new BugsnagException(name, message, frames), session, thread, false); } Builder severityReasonType(@HandledState.SeverityReason String severityReasonType) { From a54442999a12149a7937428134c85e50a4411258 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 20 Aug 2018 14:36:19 +0100 Subject: [PATCH 11/15] test: add mazerunner scenario for thread id --- features/error_reporting_thread.feature | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 features/error_reporting_thread.feature diff --git a/features/error_reporting_thread.feature b/features/error_reporting_thread.feature new file mode 100644 index 0000000000..b16a28a612 --- /dev/null +++ b/features/error_reporting_thread.feature @@ -0,0 +1,7 @@ +Feature: Error Reporting Thread + +Scenario: Only 1 thread is flagged as the error reporting thread + When I run "HandledExceptionScenario" with the defaults + Then I should receive a request + And the request is a valid for the error reporting API + And the thread "main" contains the error reporting flag From 3d3a5c4e174b155f17b00ccdf093f27f6f5f7d78 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 20 Aug 2018 14:54:39 +0100 Subject: [PATCH 12/15] build: update gemfile lock --- Gemfile.lock | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 100922b710..6a1e54c831 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,13 @@ GIT remote: git@github.com:bugsnag/maze-runner - revision: f7123450d5a75b719911c6dd3baa0507e6062c2d + revision: 19a9f6ef393861bc05b1f70b197a342f0b11260e specs: bugsnag-maze-runner (1.0.0) cucumber (~> 3.1.0) cucumber-expressions (= 5.0.15) minitest (~> 5.0) rack (~> 2.0.0) + rake (~> 12.3.0) test-unit (~> 3.2.0) GEM @@ -32,17 +33,18 @@ GEM cucumber-tag_expressions (1.1.1) cucumber-wire (0.0.1) diff-lcs (1.3) - gherkin (5.0.0) + gherkin (5.1.0) method_source (0.9.0) minitest (5.11.3) multi_json (1.13.1) multi_test (0.1.2) - power_assert (1.1.1) + power_assert (1.1.3) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) rack (2.0.5) - test-unit (3.2.7) + rake (12.3.1) + test-unit (3.2.8) power_assert PLATFORMS From 8f5d35224dbfe5187b3dc90f4304ce142cd1520c Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 20 Aug 2018 15:49:34 +0100 Subject: [PATCH 13/15] test: update mazerunner scenario to use latest error reporting step syntax --- Gemfile.lock | 2 +- features/error_reporting_thread.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6a1e54c831..c65ec1f107 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git@github.com:bugsnag/maze-runner - revision: 19a9f6ef393861bc05b1f70b197a342f0b11260e + revision: 177f27da9966aed2cb87687fa8384d6141d2fa05 specs: bugsnag-maze-runner (1.0.0) cucumber (~> 3.1.0) diff --git a/features/error_reporting_thread.feature b/features/error_reporting_thread.feature index b16a28a612..2982a602a6 100644 --- a/features/error_reporting_thread.feature +++ b/features/error_reporting_thread.feature @@ -4,4 +4,4 @@ Scenario: Only 1 thread is flagged as the error reporting thread When I run "HandledExceptionScenario" with the defaults Then I should receive a request And the request is a valid for the error reporting API - And the thread "main" contains the error reporting flag + And the thread with name "main" contains the error reporting flag From 4eb87e4ab788faea50a1a670e683b069442d8e35 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 25 Sep 2018 13:26:54 +0100 Subject: [PATCH 14/15] test: add mazerunner scenario for unhandled exception adds a scenario for an unhandled exception, that ensures the error reporting thread is serialised. For handled exceptions, the thread trace should be reported. For unhandled exceptions, the exception stacktrace should be used instead. --- features/error_reporting_thread.feature | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/features/error_reporting_thread.feature b/features/error_reporting_thread.feature index 2982a602a6..72ab8ff537 100644 --- a/features/error_reporting_thread.feature +++ b/features/error_reporting_thread.feature @@ -1,7 +1,20 @@ +# For handled exceptions, the thread trace should be reported in the threads array. +# For unhandled exceptions, the exception trace should be reported instead. + Feature: Error Reporting Thread -Scenario: Only 1 thread is flagged as the error reporting thread +Scenario: Only 1 thread is flagged as the error reporting thread for handled exceptions When I run "HandledExceptionScenario" with the defaults Then I should receive a request And the request is a valid for the error reporting API And the thread with name "main" contains the error reporting flag + And the "method" of stack frame 0 equals "com.bugsnag.android.mazerunner.scenarios.Scenario.generateException" + And the payload field "events.0.threads.0.stacktrace.0.method" ends with "getThreadStackTrace" + +Scenario: Only 1 thread is flagged as the error reporting thread for unhandled exceptions + When I run "UnhandledExceptionScenario" with the defaults + Then I should receive 1 request + And the request is a valid for the error reporting API + And the thread with name "main" contains the error reporting flag + And the "method" of stack frame 0 equals "com.bugsnag.android.mazerunner.scenarios.Scenario.generateException" + And the payload field "events.0.threads.0.stacktrace.0.method" equals "com.bugsnag.android.mazerunner.scenarios.Scenario.generateException" From c829ec73746cd6564a6151d1aafc1c2786dc7735 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 25 Sep 2018 15:22:27 +0100 Subject: [PATCH 15/15] v4.7.0 --- CHANGELOG.md | 2 +- README.md | 2 +- gradle.properties | 2 +- sdk/src/main/java/com/bugsnag/android/Notifier.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59cf391af7..e02c4f499a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 4.X.X (TBD) +## 4.7.0 (2018-09-26) * Capture trace of error reporting thread and identify with boolean flag [#355](https://github.com/bugsnag/bugsnag-android/pull/355) diff --git a/README.md b/README.md index 6ba64d4b71..dc5f901c52 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build status](https://travis-ci.org/bugsnag/bugsnag-android.svg?branch=master)](https://travis-ci.org/bugsnag/bugsnag-android) [![Coverage Status](https://coveralls.io/repos/github/bugsnag/bugsnag-android/badge.svg?branch=master)](https://coveralls.io/github/bugsnag/bugsnag-android?branch=master) -![Method count and size](https://img.shields.io/badge/Methods%20and%20size-79%20classes%20|%20638%20methods%20|%20314%20fields%20|%20116%20KB-e91e63.svg) +![Method count and size](https://img.shields.io/badge/Methods%20and%20size-79%20classes%20|%20638%20methods%20|%20315%20fields%20|%20116%20KB-e91e63.svg) Get comprehensive [Android crash reports](https://www.bugsnag.com/platforms/android/) to quickly debug errors. diff --git a/gradle.properties b/gradle.properties index 1896be615e..cea28a040e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=4.6.1 +VERSION_NAME=4.7.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git diff --git a/sdk/src/main/java/com/bugsnag/android/Notifier.java b/sdk/src/main/java/com/bugsnag/android/Notifier.java index fd72e47925..5de8be8ba2 100644 --- a/sdk/src/main/java/com/bugsnag/android/Notifier.java +++ b/sdk/src/main/java/com/bugsnag/android/Notifier.java @@ -10,7 +10,7 @@ public class Notifier implements JsonStream.Streamable { private static final String NOTIFIER_NAME = "Android Bugsnag Notifier"; - private static final String NOTIFIER_VERSION = "4.6.1"; + private static final String NOTIFIER_VERSION = "4.7.0"; private static final String NOTIFIER_URL = "https://bugsnag.com"; @NonNull