From 5be6e8b3286cf3cde7d9c0df5fac8ebf0f138ce3 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 22 Aug 2019 13:06:26 +0100 Subject: [PATCH 01/10] feat: report internal SDK errors to bugsnag Rather than sending minimal error reports, the SDK now reports internal SDK errors to bugsnag instead in a way that does not route them to customer dashboards. A minimal report is generated which is not altered in callbacks and is not cached to disk. --- .../com/bugsnag/android/ErrorFilenameTest.kt | 44 -------------- .../main/java/com/bugsnag/android/Client.java | 54 ++++++++++++++++- .../com/bugsnag/android/Configuration.java | 2 +- .../java/com/bugsnag/android/ErrorStore.java | 59 ++----------------- .../scenarios/CorruptedOldReportScenario.kt | 31 ---------- .../scenarios/CorruptedReportScenario.kt | 22 ------- .../scenarios/EmptyReportScenario.kt | 19 ------ .../MinimalHandledExceptionScenario.kt | 24 -------- .../MinimalUnhandledExceptionScenario.kt | 22 ------- features/minimal_report.feature | 36 ----------- .../scenarios/CorruptedOldReportScenario.kt | 31 ---------- .../scenarios/CorruptedReportScenario.kt | 22 ------- .../scenarios/EmptyReportScenario.kt | 19 ------ .../MinimalHandledExceptionScenario.kt | 24 -------- .../MinimalUnhandledExceptionScenario.kt | 22 ------- tests/features/minimal_report.feature | 33 ----------- 16 files changed, 58 insertions(+), 406 deletions(-) delete mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt delete mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt delete mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt delete mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt delete mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt delete mode 100644 features/minimal_report.feature delete mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt delete mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt delete mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt delete mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt delete mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt delete mode 100644 tests/features/minimal_report.feature diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt index 7b8d0b1a24..2f30a996c9 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt @@ -5,8 +5,6 @@ import androidx.test.core.app.ApplicationProvider import com.bugsnag.android.ErrorStore.ERROR_REPORT_COMPARATOR import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -53,48 +51,6 @@ class ErrorFilenameTest { assertEquals("i-h-com.bugsnag.android.SuperCaliFragilistic", filename) } - @Test - fun testErrorFromInvalidFilename() { - val invalids = arrayOf( - null, "", "test.txt", "i-h.foo", - "1504255147933_683c6b92-b325-4987-80ad-77086509ca1e.json" - ) - invalids.forEach { assertNull(errorStore.generateErrorFromFilename(it)) } - } - - @Test - fun testUnhandledErrorFromFilename() { - val filename = "1504255147933_e-u-java.lang.RuntimeException_" + - "683c6b92-b325-4987-80ad-77086509ca1e.json" - val err = errorStore.generateErrorFromFilename(filename) - assertNotNull(err) - assertTrue(err.handledState.isUnhandled) - assertEquals(Severity.ERROR, err.severity) - assertEquals("java.lang.RuntimeException", err.exceptionName) - } - - @Test - fun testHandledErrorFromFilename() { - val filename = "1504500000000_i-h-java.lang.IllegalStateException_" + - "683c6b92-b325-4987-80ad-77086509ca1e_startupcrash.json" - val err = errorStore.generateErrorFromFilename(filename) - assertNotNull(err) - assertFalse(err.handledState.isUnhandled) - assertEquals(Severity.INFO, err.severity) - assertEquals("java.lang.IllegalStateException", err.exceptionName) - } - - @Test - fun testErrorWithoutClassFromFilename() { - val filename = "1504500000000_i-h-_" + - "683c6b92-b325-4987-80ad-77086509ca1e_startupcrash.json" - val err = errorStore.generateErrorFromFilename(filename) - assertNotNull(err) - assertFalse(err.handledState.isUnhandled) - assertEquals(Severity.INFO, err.severity) - assertEquals("", err.exceptionName) - } - @Test fun testIsLaunchCrashReport() { val valid = diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java index 6d17d47736..f6b7e6cd85 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java @@ -20,6 +20,7 @@ import kotlin.Unit; import kotlin.jvm.functions.Function1; +import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -48,6 +49,9 @@ public class Client extends Observable implements Observer { private static final String USER_NAME_KEY = "user.name"; private static final String USER_EMAIL_KEY = "user.email"; + static final String INTERNAL_DIAGNOSTICS_TAB = "BugsnagDiagnostics"; + static final String INTERNAL_NOTIFIER_TAB = "Notifier"; + @NonNull protected final Configuration config; final Context appContext; @@ -195,9 +199,15 @@ public Unit invoke(Boolean connected) { // Create the error store that is used in the exception handler errorStore = new ErrorStore(config, appContext, new ErrorStore.Delegate() { @Override - public void onErrorReadFailure(Error error) { + public void onErrorReadFailure(Exception exc, File errorFile) { // send a minimal error to bugsnag with no cache - Client.this.notify(error, DeliveryStyle.NO_CACHE, null); + Thread thread = Thread.currentThread(); + Error err = new Error.Builder(config, exc, null, thread, true).build(); + + MetaData metaData = err.getMetaData(); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "filename", errorFile.getName()); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "fileLength", errorFile.length()); + Client.this.reportInternalBugsnagError(err); } }); @@ -972,6 +982,46 @@ void notify(@NonNull Error error, } } + /** + * Reports an error that occurred within the notifier to bugsnag. A lean error report will be + * generated and sent asynchronously with no callbacks, retry attempts, or writing to disk. + * This is intended for internal use only, and reports will not be visible to end-users. + */ + void reportInternalBugsnagError(@NonNull Error error) { + error.setAppData(appData.getAppDataSummary()); + error.setDeviceData(deviceData.getDeviceDataSummary()); + + MetaData metaData = error.getMetaData(); + metaData.addToTab(INTERNAL_NOTIFIER_TAB, "name", Notifier.getInstance().getName()); + metaData.addToTab(INTERNAL_NOTIFIER_TAB, "version", Notifier.getInstance().getVersion()); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "apiKey", config.getApiKey()); + + Object packageName = appData.getAppData().get("packageName"); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "packageName", packageName); + + final Report report = new Report(null, error); + Async.run(new Runnable() { + @Override + public void run() { + try { + Delivery delivery = config.getDelivery(); + + // can only modify headers if DefaultDelivery is in use + if (delivery instanceof DefaultDelivery) { + Map headers = config.getErrorApiHeaders(); + headers.put("Bugsnag-Internal-Error", "true"); + headers.remove(Configuration.HEADER_API_KEY); + DefaultDelivery defaultDelivery = (DefaultDelivery) delivery; + defaultDelivery.deliver(config.getEndpoint(), report, headers); + } + + } catch (Exception exception) { + Logger.warn("Failed to report minimal error to Bugsnag", exception); + } + } + }); + } + private void deliverReportAsync(@NonNull Error error, Report report) { final Report finalReport = report; final Error finalError = error; diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Configuration.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Configuration.java index c09fd5a0c7..8341f3f5ef 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Configuration.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Configuration.java @@ -23,7 +23,7 @@ public class Configuration extends Observable implements Observer { private static final String HEADER_API_PAYLOAD_VERSION = "Bugsnag-Payload-Version"; - private static final String HEADER_API_KEY = "Bugsnag-Api-Key"; + static final String HEADER_API_KEY = "Bugsnag-Api-Key"; private static final String HEADER_BUGSNAG_SENT_AT = "Bugsnag-Sent-At"; private static final int DEFAULT_MAX_SIZE = 32; static final String DEFAULT_EXCEPTION_TYPE = "android"; diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java index 4b8939467e..0ef984a738 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java @@ -27,12 +27,12 @@ class ErrorStore extends FileStore { interface Delegate { /** - * Invoked when a cached error report cannot be read, and a minimal error is - * read from the information encoded in the filename instead. + * Invoked when a cached error report cannot be read. * - * @param minimalError the minimal error, if encoded in the filename + * @param exception the error encountered reading/delivering the file + * @param errorFile file which could not be read */ - void onErrorReadFailure(Error minimalError); + void onErrorReadFailure(Exception exception, File errorFile); } private static final String STARTUP_CRASH = "_startupcrash"; @@ -171,11 +171,7 @@ private void flushErrorReport(File errorFile) { + " to Bugsnag, will try again later", exception); } catch (Exception exception) { if (delegate != null) { - Error minimalError = generateErrorFromFilename(errorFile.getName()); - - if (minimalError != null) { - delegate.onErrorReadFailure(minimalError); - } + delegate.onErrorReadFailure(exception, errorFile); } deleteStoredFiles(Collections.singleton(errorFile)); } @@ -207,51 +203,6 @@ String calculateFilenameForError(Error error) { return String.format("%s-%s-%s", severity, handled, errClass); } - /** - * Generates minimal error information from a filename, if the report was incomplete/corrupted. - * This allows bugsnag to send the severity, handled state, and error class as a minimal - * report. - * - * Error information is encoded in the filename for recent notifier versions - * as "$severity-$handled-$errorClass", and is not present in legacy versions - * - * @param filename the filename - * @return the minimal error, or null if the filename does not match the expected pattern. - */ - Error generateErrorFromFilename(String filename) { - if (filename == null) { - return null; - } - - try { - int errorInfoStart = filename.indexOf('_') + 1; - int errorInfoEnd = filename.indexOf('_', errorInfoStart); - String encodedErr = filename.substring(errorInfoStart, errorInfoEnd); - - char sevChar = encodedErr.charAt(0); - Severity severity = Severity.fromChar(sevChar); - severity = severity == null ? Severity.ERROR : severity; - - boolean unhandled = encodedErr.charAt(2) == 'u'; - HandledState handledState = HandledState.newInstance(unhandled - ? HandledState.REASON_UNHANDLED_EXCEPTION : HandledState.REASON_HANDLED_EXCEPTION); - - // default if error has no name - String errClass = ""; - - if (encodedErr.length() >= 4) { - errClass = encodedErr.substring(4); - } - BugsnagException exc = new BugsnagException(errClass, "", new StackTraceElement[]{}); - Error error = new Error(config, exc, handledState, severity, null, null); - error.setIncomplete(true); - return error; - } catch (IndexOutOfBoundsException exc) { - // simplifies above implementation by avoiding need for several length checks. - return null; - } - } - @NonNull @Override String getFilename(Object object) { diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt deleted file mode 100644 index 6c0a4464ea..0000000000 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Verifies that if a report is corrupted with an old filename, - * Bugsnag does not crash. - */ -internal class CorruptedOldReportScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - val files = File(context.cacheDir, "bugsnag-errors").listFiles() - - // create an empty (invalid) file with an old name - files.forEach { - val dir = File(it.parent) - it.writeText("{\"exceptions\":[{\"stacktrace\":[") - it.renameTo(File(dir, "1504255147933_683c6b92-b325-4987-80ad-77086509ca1e.json")) - } - } - - override fun run() { - super.run() - Bugsnag.notify(generateException()) - } -} diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt deleted file mode 100644 index dc8a24df7b..0000000000 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Verifies that if a report is corrupted, minimal information is still sent to bugsnag. - */ -internal class CorruptedReportScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - val files = File(context.cacheDir, "bugsnag-errors").listFiles() - files.forEach { it.writeText("{\"exceptions\":[{\"stacktrace\":[") } - - val nativeFiles = File(context.cacheDir, "bugsnag-native").listFiles() - nativeFiles.forEach { it.writeText("{\"exceptions\":[{\"stacktrace\":[") } - } -} diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt deleted file mode 100644 index a20a0a3daa..0000000000 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Verifies that if a report is empty, minimal information is still sent to bugsnag. - */ -internal class EmptyReportScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - val files = File(context.cacheDir, "bugsnag-errors").listFiles() - files.forEach { it.writeText("") } - } -} diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt deleted file mode 100644 index 10f0bb5cc5..0000000000 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Sends a handled exception to Bugsnag, which does not include session data. - */ -internal class MinimalHandledExceptionScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - disableAllDelivery(config) - } - - override fun run() { - super.run() - Bugsnag.notify(java.lang.RuntimeException("Whoops")) - } - -} diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt deleted file mode 100644 index c388f7cce7..0000000000 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Sends an unhandled exception to Bugsnag. - */ -internal class MinimalUnhandledExceptionScenario(config: Configuration, - context: Context) : Scenario(config, context) { - init { - config.setAutoCaptureSessions(false) - disableAllDelivery(config) - } - - override fun run() { - super.run() - throw java.lang.IllegalStateException("Whoops") - } - -} diff --git a/features/minimal_report.feature b/features/minimal_report.feature deleted file mode 100644 index 629e542d2a..0000000000 --- a/features/minimal_report.feature +++ /dev/null @@ -1,36 +0,0 @@ -Feature: Minimal error information is reported for corrupted/empty files - -Scenario: Minimal error report for a Handled Exception with an empty file - When I run "MinimalHandledExceptionScenario" - And I set environment variable "EVENT_TYPE" to "EmptyReportScenario" - And I relaunch the app - Then I should receive 1 request - And the request is valid for the error reporting API - And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 element - And the exception "errorClass" equals "java.lang.RuntimeException" - And the event "severity" equals "warning" - And the event "unhandled" is false - And the event "incomplete" is true - And the event "severityReason.type" equals "handledException" - -Scenario: Minimal error report for an Unhandled Exception with a corrupted file - When I run "MinimalUnhandledExceptionScenario" - And I set environment variable "EVENT_TYPE" to "CorruptedReportScenario" - And I relaunch the app - Then I should receive 1 request - And the request is valid for the error reporting API - And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 element - And the exception "errorClass" equals "java.lang.IllegalStateException" - And the event "severity" equals "error" - And the event "unhandled" is true - And the event "incomplete" is true - And the event "severityReason.type" equals "unhandledException" - -Scenario: Minimal error report with old filename - When I run "MinimalUnhandledExceptionScenario" - And I set environment variable "EVENT_TYPE" to "CorruptedOldReportScenario" - And I relaunch the app - Then I should receive 1 request - And the request is valid for the error reporting API - And the event "unhandled" is false - And the event "incomplete" is false diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt deleted file mode 100644 index 6c0a4464ea..0000000000 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedOldReportScenario.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Verifies that if a report is corrupted with an old filename, - * Bugsnag does not crash. - */ -internal class CorruptedOldReportScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - val files = File(context.cacheDir, "bugsnag-errors").listFiles() - - // create an empty (invalid) file with an old name - files.forEach { - val dir = File(it.parent) - it.writeText("{\"exceptions\":[{\"stacktrace\":[") - it.renameTo(File(dir, "1504255147933_683c6b92-b325-4987-80ad-77086509ca1e.json")) - } - } - - override fun run() { - super.run() - Bugsnag.notify(generateException()) - } -} diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt deleted file mode 100644 index dc8a24df7b..0000000000 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Verifies that if a report is corrupted, minimal information is still sent to bugsnag. - */ -internal class CorruptedReportScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - val files = File(context.cacheDir, "bugsnag-errors").listFiles() - files.forEach { it.writeText("{\"exceptions\":[{\"stacktrace\":[") } - - val nativeFiles = File(context.cacheDir, "bugsnag-native").listFiles() - nativeFiles.forEach { it.writeText("{\"exceptions\":[{\"stacktrace\":[") } - } -} diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt deleted file mode 100644 index a20a0a3daa..0000000000 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Verifies that if a report is empty, minimal information is still sent to bugsnag. - */ -internal class EmptyReportScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - val files = File(context.cacheDir, "bugsnag-errors").listFiles() - files.forEach { it.writeText("") } - } -} diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt deleted file mode 100644 index 10f0bb5cc5..0000000000 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Bugsnag -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Sends a handled exception to Bugsnag, which does not include session data. - */ -internal class MinimalHandledExceptionScenario(config: Configuration, - context: Context) : Scenario(config, context) { - - init { - config.setAutoCaptureSessions(false) - disableAllDelivery(config) - } - - override fun run() { - super.run() - Bugsnag.notify(java.lang.RuntimeException("Whoops")) - } - -} diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt deleted file mode 100644 index c388f7cce7..0000000000 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios - -import android.content.Context -import com.bugsnag.android.Configuration -import java.io.File - -/** - * Sends an unhandled exception to Bugsnag. - */ -internal class MinimalUnhandledExceptionScenario(config: Configuration, - context: Context) : Scenario(config, context) { - init { - config.setAutoCaptureSessions(false) - disableAllDelivery(config) - } - - override fun run() { - super.run() - throw java.lang.IllegalStateException("Whoops") - } - -} diff --git a/tests/features/minimal_report.feature b/tests/features/minimal_report.feature deleted file mode 100644 index 9aef17de6d..0000000000 --- a/tests/features/minimal_report.feature +++ /dev/null @@ -1,33 +0,0 @@ -Feature: Minimal error information is reported for corrupted/empty files - -Scenario: Minimal error report for a Handled Exception with an empty file - When I run "MinimalHandledExceptionScenario" and relaunch the app - And I configure Bugsnag for "EmptyReportScenario" - And I wait to receive a request - And the request is valid for the error reporting API version "4.0" for the "Android Bugsnag Notifier" notifier - And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 elements - And the exception "errorClass" equals "java.lang.RuntimeException" - And the event "severity" equals "warning" - And the event "unhandled" is false - And the event "incomplete" is true - And the event "severityReason.type" equals "handledException" - -Scenario: Minimal error report for an Unhandled Exception with a corrupted file - When I run "MinimalUnhandledExceptionScenario" and relaunch the app - And I configure Bugsnag for "CorruptedReportScenario" - And I wait to receive a request - And the request is valid for the error reporting API version "4.0" for the "Android Bugsnag Notifier" notifier - And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 elements - And the exception "errorClass" equals "java.lang.IllegalStateException" - And the event "severity" equals "error" - And the event "unhandled" is true - And the event "incomplete" is true - And the event "severityReason.type" equals "unhandledException" - -Scenario: Minimal error report with old filename - When I run "MinimalUnhandledExceptionScenario" and relaunch the app - And I run "CorruptedOldReportScenario" - And I wait to receive a request - And the request is valid for the error reporting API version "4.0" for the "Android Bugsnag Notifier" notifier - And the event "unhandled" is false - And the event "incomplete" is false \ No newline at end of file From 54d81a4536288e6bf416da7b208ebb2e456e5c25 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 22 Aug 2019 13:57:42 +0100 Subject: [PATCH 02/10] docs: add changelog entry --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5071f2ea0..06943e1273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## TBD -* Buffer IO when reading cached error reports - [#565](https://github.com/bugsnag/bugsnag-android/pull/565) +* Report internal SDK errors to bugsnag + [#570](https://github.com/bugsnag/bugsnag-android/pull/570) ## 4.18.0 (2019-08-15) From 8b95bd495487fff6faf7dce862468635e1f1efeb Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 22 Aug 2019 14:25:05 +0100 Subject: [PATCH 03/10] test: add mazerunner scenarios for internal error reporting --- .../com/bugsnag/android/TestHarnessHooks.kt | 7 +++++++ .../scenarios/InternalReportScenario.kt | 17 +++++++++++++++++ features/internal_report.feature | 12 ++++++++++++ .../com/bugsnag/android/TestHarnessHooks.kt | 7 +++++++ .../scenarios/InternalReportScenario.kt | 17 +++++++++++++++++ tests/features/internal_report.feature | 12 ++++++++++++ 6 files changed, 72 insertions(+) create mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt create mode 100644 features/internal_report.feature create mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt create mode 100644 tests/features/internal_report.feature diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt index 3ace7c532b..2d01fa5ce7 100644 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt +++ b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt @@ -67,3 +67,10 @@ internal fun writeErrorToStore(client: Client) { Thread.currentThread(), false).build() client.errorStore.write(error) } + +internal fun sendInternalReport(exc: Throwable, config: Configuration, client: Client) { + val thread = Thread.currentThread() + val err = Error.Builder(config, exc, null, thread, true).build() + err.getMetaData().addToTab("BugsnagDiagnostics", "custom-data", "FooBar") + client.reportInternalBugsnagError(err) +} diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt new file mode 100644 index 0000000000..80d20d6a41 --- /dev/null +++ b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt @@ -0,0 +1,17 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.* + +internal class InternalReportScenario(config: Configuration, + context: Context) : Scenario(config, context) { + init { + config.setAutoCaptureSessions(false) + } + + override fun run() { + super.run() + sendInternalReport(RuntimeException("Whoops"), config, Bugsnag.getClient()) + } + +} diff --git a/features/internal_report.feature b/features/internal_report.feature new file mode 100644 index 0000000000..8427624b40 --- /dev/null +++ b/features/internal_report.feature @@ -0,0 +1,12 @@ +Feature: Sending internal error reports + +Scenario: Send a report about an error triggered within the notifier + When I run "InternalReportScenario" + Then I should receive 1 request + And the "Bugsnag-Internal-Error" header equals "true" + And the payload field "apiKey" is null + And the event "context" is null + And the event "session" is null + And the event "breadcrumbs" is null + And the event "app.type" equals "android" + And the event "device.osName" equals "android" diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt index 3ace7c532b..2d01fa5ce7 100644 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt +++ b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/TestHarnessHooks.kt @@ -67,3 +67,10 @@ internal fun writeErrorToStore(client: Client) { Thread.currentThread(), false).build() client.errorStore.write(error) } + +internal fun sendInternalReport(exc: Throwable, config: Configuration, client: Client) { + val thread = Thread.currentThread() + val err = Error.Builder(config, exc, null, thread, true).build() + err.getMetaData().addToTab("BugsnagDiagnostics", "custom-data", "FooBar") + client.reportInternalBugsnagError(err) +} diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt new file mode 100644 index 0000000000..80d20d6a41 --- /dev/null +++ b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt @@ -0,0 +1,17 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.* + +internal class InternalReportScenario(config: Configuration, + context: Context) : Scenario(config, context) { + init { + config.setAutoCaptureSessions(false) + } + + override fun run() { + super.run() + sendInternalReport(RuntimeException("Whoops"), config, Bugsnag.getClient()) + } + +} diff --git a/tests/features/internal_report.feature b/tests/features/internal_report.feature new file mode 100644 index 0000000000..8427624b40 --- /dev/null +++ b/tests/features/internal_report.feature @@ -0,0 +1,12 @@ +Feature: Sending internal error reports + +Scenario: Send a report about an error triggered within the notifier + When I run "InternalReportScenario" + Then I should receive 1 request + And the "Bugsnag-Internal-Error" header equals "true" + And the payload field "apiKey" is null + And the event "context" is null + And the event "session" is null + And the event "breadcrumbs" is null + And the event "app.type" equals "android" + And the event "device.osName" equals "android" From decb9f3831dea8466583b429fc803dd3c18d8c07 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 22 Aug 2019 14:35:25 +0100 Subject: [PATCH 04/10] refactor: add all metadata to diagnostics tab --- .../src/main/java/com/bugsnag/android/Client.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java index f6b7e6cd85..aa2f62f204 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java @@ -50,7 +50,6 @@ public class Client extends Observable implements Observer { private static final String USER_EMAIL_KEY = "user.email"; static final String INTERNAL_DIAGNOSTICS_TAB = "BugsnagDiagnostics"; - static final String INTERNAL_NOTIFIER_TAB = "Notifier"; @NonNull protected final Configuration config; @@ -992,8 +991,9 @@ void reportInternalBugsnagError(@NonNull Error error) { error.setDeviceData(deviceData.getDeviceDataSummary()); MetaData metaData = error.getMetaData(); - metaData.addToTab(INTERNAL_NOTIFIER_TAB, "name", Notifier.getInstance().getName()); - metaData.addToTab(INTERNAL_NOTIFIER_TAB, "version", Notifier.getInstance().getVersion()); + Notifier notifier = Notifier.getInstance(); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "name", notifier.getName()); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "version", notifier.getVersion()); metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "apiKey", config.getApiKey()); Object packageName = appData.getAppData().get("packageName"); From cb25aaa72f0989f843aa1d7a514628eef4aa8344 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 22 Aug 2019 17:11:07 +0100 Subject: [PATCH 05/10] refactor: set error context and catch rejectedExecutionException --- .../main/java/com/bugsnag/android/Client.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java index aa2f62f204..2c2658427f 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java @@ -202,6 +202,7 @@ public void onErrorReadFailure(Exception exc, File errorFile) { // send a minimal error to bugsnag with no cache Thread thread = Thread.currentThread(); Error err = new Error.Builder(config, exc, null, thread, true).build(); + err.setContext("Crash Report Deserialization"); MetaData metaData = err.getMetaData(); metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "filename", errorFile.getName()); @@ -1000,26 +1001,30 @@ void reportInternalBugsnagError(@NonNull Error error) { metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "packageName", packageName); final Report report = new Report(null, error); - Async.run(new Runnable() { - @Override - public void run() { - try { - Delivery delivery = config.getDelivery(); - - // can only modify headers if DefaultDelivery is in use - if (delivery instanceof DefaultDelivery) { - Map headers = config.getErrorApiHeaders(); - headers.put("Bugsnag-Internal-Error", "true"); - headers.remove(Configuration.HEADER_API_KEY); - DefaultDelivery defaultDelivery = (DefaultDelivery) delivery; - defaultDelivery.deliver(config.getEndpoint(), report, headers); + try { + Async.run(new Runnable() { + @Override + public void run() { + try { + Delivery delivery = config.getDelivery(); + + // can only modify headers if DefaultDelivery is in use + if (delivery instanceof DefaultDelivery) { + Map headers = config.getErrorApiHeaders(); + headers.put("Bugsnag-Internal-Error", "true"); + headers.remove(Configuration.HEADER_API_KEY); + DefaultDelivery defaultDelivery = (DefaultDelivery) delivery; + defaultDelivery.deliver(config.getEndpoint(), report, headers); + } + + } catch (Exception exception) { + Logger.warn("Failed to report minimal error to Bugsnag", exception); } - - } catch (Exception exception) { - Logger.warn("Failed to report minimal error to Bugsnag", exception); } - } - }); + }); + } catch (RejectedExecutionException ignored) { + // drop internal report + } } private void deliverReportAsync(@NonNull Error error, Report report) { From 766a92590d5b4cf593a36ffda45abda5d0f37836 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 22 Aug 2019 17:35:52 +0100 Subject: [PATCH 06/10] prefix notifier metadata fields --- .../src/main/java/com/bugsnag/android/Client.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java index 2c2658427f..4349a88426 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java @@ -993,8 +993,8 @@ void reportInternalBugsnagError(@NonNull Error error) { MetaData metaData = error.getMetaData(); Notifier notifier = Notifier.getInstance(); - metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "name", notifier.getName()); - metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "version", notifier.getVersion()); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "notifierName", notifier.getName()); + metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "notifierVersion", notifier.getVersion()); metaData.addToTab(INTERNAL_DIAGNOSTICS_TAB, "apiKey", config.getApiKey()); Object packageName = appData.getAppData().get("packageName"); From 876e5b03f8c6da39f12e7935f39b70e503fbf3ba Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Fri, 23 Aug 2019 08:46:27 +0100 Subject: [PATCH 07/10] refactor: address simon review comments --- .../src/main/java/com/bugsnag/android/Client.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java index 4349a88426..c7903abf16 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java @@ -199,7 +199,7 @@ public Unit invoke(Boolean connected) { errorStore = new ErrorStore(config, appContext, new ErrorStore.Delegate() { @Override public void onErrorReadFailure(Exception exc, File errorFile) { - // send a minimal error to bugsnag with no cache + // send an internal error to bugsnag with no cache Thread thread = Thread.currentThread(); Error err = new Error.Builder(config, exc, null, thread, true).build(); err.setContext("Crash Report Deserialization"); @@ -1018,7 +1018,7 @@ public void run() { } } catch (Exception exception) { - Logger.warn("Failed to report minimal error to Bugsnag", exception); + Logger.warn("Failed to report internal error to Bugsnag", exception); } } }); From 3a86807d55d3d172ec24d5021195ca9e9ad28fa0 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Fri, 23 Aug 2019 13:03:58 +0100 Subject: [PATCH 08/10] test: update internal error report test to trigger error from corrupted json file --- .../scenarios/EmptyReportScenario.kt | 19 +++++++++++++++++++ .../scenarios/InternalReportScenario.kt | 13 ++++++++++--- features/internal_report.feature | 4 +++- .../scenarios/EmptyReportScenario.kt | 19 +++++++++++++++++++ .../scenarios/InternalReportScenario.kt | 13 ++++++++++--- tests/features/internal_report.feature | 9 +++++---- 6 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt create mode 100644 tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt new file mode 100644 index 0000000000..a20a0a3daa --- /dev/null +++ b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt @@ -0,0 +1,19 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import java.io.File + +/** + * Verifies that if a report is empty, minimal information is still sent to bugsnag. + */ +internal class EmptyReportScenario(config: Configuration, + context: Context) : Scenario(config, context) { + + init { + config.setAutoCaptureSessions(false) + val files = File(context.cacheDir, "bugsnag-errors").listFiles() + files.forEach { it.writeText("") } + } +} diff --git a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt index 80d20d6a41..93b249a3ac 100644 --- a/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt +++ b/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt @@ -1,17 +1,24 @@ package com.bugsnag.android.mazerunner.scenarios import android.content.Context -import com.bugsnag.android.* +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import java.io.File +/** + * Sends a handled exception to Bugsnag, which does not include session data. + */ internal class InternalReportScenario(config: Configuration, context: Context) : Scenario(config, context) { + init { config.setAutoCaptureSessions(false) + disableAllDelivery(config) } override fun run() { super.run() - sendInternalReport(RuntimeException("Whoops"), config, Bugsnag.getClient()) + Bugsnag.notify(java.lang.RuntimeException("Whoops")) } -} +} \ No newline at end of file diff --git a/features/internal_report.feature b/features/internal_report.feature index 8427624b40..39c220a0dc 100644 --- a/features/internal_report.feature +++ b/features/internal_report.feature @@ -2,10 +2,12 @@ Feature: Sending internal error reports Scenario: Send a report about an error triggered within the notifier When I run "InternalReportScenario" + And I set environment variable "EVENT_TYPE" to "EmptyReportScenario" + And I relaunch the app Then I should receive 1 request And the "Bugsnag-Internal-Error" header equals "true" And the payload field "apiKey" is null - And the event "context" is null + And the event "context" equals "Crash Report Deserialization" And the event "session" is null And the event "breadcrumbs" is null And the event "app.type" equals "android" diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt new file mode 100644 index 0000000000..a20a0a3daa --- /dev/null +++ b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt @@ -0,0 +1,19 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import java.io.File + +/** + * Verifies that if a report is empty, minimal information is still sent to bugsnag. + */ +internal class EmptyReportScenario(config: Configuration, + context: Context) : Scenario(config, context) { + + init { + config.setAutoCaptureSessions(false) + val files = File(context.cacheDir, "bugsnag-errors").listFiles() + files.forEach { it.writeText("") } + } +} diff --git a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt index 80d20d6a41..93b249a3ac 100644 --- a/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt +++ b/tests/features/fixtures/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/InternalReportScenario.kt @@ -1,17 +1,24 @@ package com.bugsnag.android.mazerunner.scenarios import android.content.Context -import com.bugsnag.android.* +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import java.io.File +/** + * Sends a handled exception to Bugsnag, which does not include session data. + */ internal class InternalReportScenario(config: Configuration, context: Context) : Scenario(config, context) { + init { config.setAutoCaptureSessions(false) + disableAllDelivery(config) } override fun run() { super.run() - sendInternalReport(RuntimeException("Whoops"), config, Bugsnag.getClient()) + Bugsnag.notify(java.lang.RuntimeException("Whoops")) } -} +} \ No newline at end of file diff --git a/tests/features/internal_report.feature b/tests/features/internal_report.feature index 8427624b40..50ac46c10b 100644 --- a/tests/features/internal_report.feature +++ b/tests/features/internal_report.feature @@ -1,11 +1,12 @@ Feature: Sending internal error reports -Scenario: Send a report about an error triggered within the notifier - When I run "InternalReportScenario" - Then I should receive 1 request +Scenario: Sending internal error reports + When I run "InternalReportScenario" and relaunch the app + And I configure Bugsnag for "EmptyReportScenario" + And I wait to receive a request And the "Bugsnag-Internal-Error" header equals "true" And the payload field "apiKey" is null - And the event "context" is null + And the event "context" equals "Crash Report Deserialization" And the event "session" is null And the event "breadcrumbs" is null And the event "app.type" equals "android" From bf12d29a72f405069e681f72c92cf461106dfc70 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Fri, 23 Aug 2019 14:49:14 +0100 Subject: [PATCH 09/10] refactor: remove minimal err info from filename --- .../com/bugsnag/android/ErrorFilenameTest.kt | 47 ++----------------- .../java/com/bugsnag/android/ErrorStore.java | 20 +------- 2 files changed, 6 insertions(+), 61 deletions(-) diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt index 2f30a996c9..8004a61bc9 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt @@ -10,8 +10,6 @@ import org.junit.Before import org.junit.Test import java.io.File -class SuperCaliFragilisticExpiAlidociousBeanFactoryException: RuntimeException() - class ErrorFilenameTest { private lateinit var errorStore: ErrorStore @@ -29,32 +27,10 @@ class ErrorFilenameTest { errorStore = ErrorStore(config, context, null) } - @Test - fun testCalculateFilenameUnhandled() { - val err = generateError(true, Severity.ERROR, RuntimeException()) - val filename = errorStore.calculateFilenameForError(err) - assertEquals("e-u-java.lang.RuntimeException", filename) - } - - @Test - fun testCalculateFilenameHandled() { - val err = generateError(false, Severity.INFO, IllegalStateException("Whoops")) - val filename = errorStore.calculateFilenameForError(err) - assertEquals("i-h-java.lang.IllegalStateException", filename) - } - - @Test - fun testCalculateTruncatedFilename() { - val err = generateError(false, Severity.INFO, - SuperCaliFragilisticExpiAlidociousBeanFactoryException()) - val filename = errorStore.calculateFilenameForError(err) - assertEquals("i-h-com.bugsnag.android.SuperCaliFragilistic", filename) - } - @Test fun testIsLaunchCrashReport() { val valid = - arrayOf("1504255147933_e-u-java.lang.RuntimeException_30b7e350-dcd1-4032-969e-98d30be62bbc_startupcrash.json") + arrayOf("1504255147933_30b7e350-dcd1-4032-969e-98d30be62bbc_startupcrash.json") val invalid = arrayOf( "", ".json", @@ -74,11 +50,9 @@ class ErrorFilenameTest { @Test fun testComparator() { - val first = "1504255147933_e-u-java.lang.RuntimeException_" + - "683c6b92-b325-4987-80ad-77086509ca1e.json" - val second = "1505000000000_i-h-Exception_683c6b92-b325-4987-80ad-77086509ca1e.json" - val startup = "1504500000000_w-h-java.lang.IllegalStateException_683c6b92-b325-" + - "4987-80ad-77086509ca1e_startupcrash.json" + val first = "1504255147933_683c6b92-b325-4987-80ad-77086509ca1e.json" + val second = "1505000000000_683c6b92-b325-4987-80ad-77086509ca1e.json" + val startup = "1504500000000_683c6b92-b325-4987-80ad-77086509ca1e_startupcrash.json" // handle defaults assertEquals(0, ERROR_REPORT_COMPARATOR.compare(null, null).toLong()) @@ -97,17 +71,4 @@ class ErrorFilenameTest { assertTrue(ERROR_REPORT_COMPARATOR.compare(File(first), File(startup)) < 0) assertTrue(ERROR_REPORT_COMPARATOR.compare(File(second), File(startup)) > 0) } - - private fun generateError(unhandled: Boolean, severity: Severity, exc: Throwable): Error { - val currentThread = Thread.currentThread() - val sessionTracker = BugsnagTestUtils.generateSessionTracker() - - val handledState = when { - unhandled -> HandledState.REASON_UNHANDLED_EXCEPTION - else -> HandledState.REASON_HANDLED_EXCEPTION - } - return Error.Builder(config, exc, sessionTracker, currentThread, unhandled) - .severityReasonType(handledState) - .severity(severity).build() - } } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java index 0ef984a738..e5973a9980 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ErrorStore.java @@ -22,8 +22,6 @@ @ThreadSafe class ErrorStore extends FileStore { - private static final int MAX_ERR_CLASS_LEN = 40; - interface Delegate { /** @@ -192,26 +190,13 @@ private List findLaunchCrashReports(Collection storedFiles) { return launchCrashes; } - String calculateFilenameForError(Error error) { - char handled = error.getHandledState().isUnhandled() ? 'u' : 'h'; - char severity = error.getSeverity().getName().charAt(0); - String errClass = error.getExceptionName(); - - if (errClass.length() > MAX_ERR_CLASS_LEN) { - errClass = errClass.substring(0, MAX_ERR_CLASS_LEN); - } - return String.format("%s-%s-%s", severity, handled, errClass); - } - @NonNull @Override String getFilename(Object object) { String suffix = ""; - String encodedInfo; if (object instanceof Error) { Error error = (Error) object; - encodedInfo = calculateFilenameForError(error); Map appData = error.getAppData(); if (appData instanceof Map) { @@ -222,13 +207,12 @@ && isStartupCrash(((Number) appData.get("duration")).longValue())) { } } } else { - encodedInfo = ""; // don't encode for NDK errors, as they are always 'e-u' suffix = "not-jvm"; } String uuid = UUID.randomUUID().toString(); long timestamp = System.currentTimeMillis(); - return String.format(Locale.US, "%s%d_%s_%s%s.json", - storeDirectory, timestamp, encodedInfo, uuid, suffix); + return String.format(Locale.US, "%s%d_%s%s.json", + storeDirectory, timestamp, uuid, suffix); } boolean isStartupCrash(long durationMs) { From 6ff5d5f28518078b2111a433088dda0d06fd2ab3 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 27 Aug 2019 09:59:17 +0100 Subject: [PATCH 10/10] v4.19.0 --- CHANGELOG.md | 2 +- .../src/main/java/com/bugsnag/android/Notifier.java | 2 +- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06943e1273..4f7cc0093d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## TBD +## 4.19.0 (2019-08-27) * Report internal SDK errors to bugsnag [#570](https://github.com/bugsnag/bugsnag-android/pull/570) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.java index ba7d9c2bab..6840e40f9e 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.java +++ b/bugsnag-android-core/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.18.0"; + private static final String NOTIFIER_VERSION = "4.19.0"; private static final String NOTIFIER_URL = "https://bugsnag.com"; @NonNull diff --git a/gradle.properties b/gradle.properties index 3982630a92..9a25553bc9 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.18.0 +VERSION_NAME=4.19.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git