Configuring Backtrace for Android
Configure Backtrace for your Android project. This page defines the configuration settings, classes, and methods available with the Backtrace Android SDK.
Usage
- Java
- Kotlin
// replace with your submission url
BacktraceCredentials credentials = new BacktraceCredentials("<submissionUrl>");
BacktraceClient backtraceClient = new BacktraceClient(getApplicationContext(), credentials);
// send test report
backtraceClient.send("test");
// Capture uncaught exceptions
BacktraceExceptionHandler.enable(backtraceClient);
// Enable ANR detection
backtraceClient.enableAnr();
// Enable Crash Free metrics
backtraceClient.metrics.enable(new BacktraceMetricsSettings(credentials));
// replace with your submission url
val credentials = BacktraceCredentials("<submissionUrl>")
val backtraceClient = BacktraceClient(applicationContext, credentials)
// send test report
backtraceClient.send("test")
// Capture uncaught exceptions
BacktraceExceptionHandler.enable(backtraceClient)
// Enable ANR detection
backtraceClient.enableAnr()
// Enable Crash Free metrics
backtraceClient.metrics.enable(BacktraceMetricsSettings(credentials))
Global Custom Attributes
Custom attributes can be included with both managed and native reports. To set global custom attributes, pass in a map of attributes to the BacktraceClient
constructor method.
- Java
- Kotlin
Map<String, Object> attributes = new HashMap<String, Object>(){{
put("custom-attribute-key", "custom-attribute-value");
}};
BacktraceClient backtraceClient = new BacktraceClient(context, credentials, attributes);
val attributes: HashMap<String, Any> = hashMapOf("custom-attribute-key" to "custom-attribute-value")
val backtraceClient = BacktraceClient(context, credentials, attributes)
Dynamic Custom Attributes
Use BacktraceClient.addAttribute
to add global attributes to both the managed and native layer after initialization.
- Java
- Kotlin
BacktraceClient backtraceClient = new BacktraceClient(context, credentials, database);
// ...
// Add a single attribute
final String attributeKey = "test-attribute";
final String attributeValue = "test-value";
backtraceClient.addAttribute(attributeKey, attributeValue);
// Append an attribute map
final String attributeKey = "test-attribute";
final String attributeValue = "test-value";
Map<String, Object> attributes = new HashMap<>();
attributes.put(attributeKey, attributeValue);
backtraceClient.addAttribute(attributes);
val backtraceClient = BacktraceClient(context, credentials, database)
// ...
// Add a single attribute
val attributeKey = "test-attribute"
val attributeValue = "test-value"
backtraceClient.addAttribute(attributeKey, attributeValue)
// Append an attribute map
val attributeKey = "test-attribute"
val attributeValue = "test-value"
val attributes: HashMap<String, Any> = HashMap<String, Any> ()
attributes[attributeKey] = attributeValue
backtraceClient.addAttribute(attributes)
File Attachments
You can enable default file attachments which will be sent with all Backtrace reports both managed and native.
String fileName = context.getFilesDir() + "/" + "myCustomFile.txt";
List<String> attachments = new ArrayList<String>(){{
add(fileName);
}};
BacktraceClient backtraceClient = new BacktraceClient(context, credentials, database, attributes, attachments);
File attachment paths for crash reports can only be specified on initialization. If you have rotating file logs or another situation where the exact filename won't be known when you initialize your Backtrace client, you can use symlinks:
// The file simlink path to pass to Backtrace
String fileName = context.getFilesDir() + "/" + "myCustomFile.txt";
List<String> attachments = new ArrayList<String>(){{
add(fileName);
}};
BacktraceClient backtraceClient = new BacktraceClient(context, credentials, database, attributes, attachments);
// The actual filename of the desired log, not known to the BacktraceClient on initialization
String fileNameDateString = context.getFilesDir() + "/" + "myCustomFile06_11_2021.txt";
// Create symlink
Os.symlink(fileNameDateString, fileName);
If you create any new files in the same directory as your BacktraceDatabase
directory, they will be deleted when you create a new BacktraceClient
.
Application Not Responding (ANRs)
The BacktraceClient
allows you to detect Application Not Responding (ANR) errors that occur when the main thread is blocked for more than 5 seconds. You can enable ANR reporting as follows:
backtraceClient.enableAnr(timeout, event, debug);
You can also provide the following parameters as an argument:
timeout
: Specifies how long (in milliseconds) the thread should be blocked before the ANR is reported.event
: Specifies an event, which will be executed instead of handling the ANR error by default.debug
: Does not report ANRs if the app is in debug mode.
Offline Database Settings
The BacktraceClient
allows you to customize the initialization of BacktraceDatabase
for local storage of error reports by supplying a BacktraceDatabaseSettings
parameter, as shown below:
BacktraceCredentials credentials = new BacktraceCredentials("https://submit.backtrace.io/{subdomain-name}/{submission-token}/json");
Context context = getApplicationContext();
String dbPath = context.getFilesDir().getAbsolutePath() + "/sample/backtrace/path"; // any path, eg. absolute path to the internal storage
BacktraceDatabaseSettings settings = new BacktraceDatabaseSettings(dbPath);
settings.setMaxRecordCount(100);
settings.setMaxDatabaseSize(100);
settings.setRetryBehavior(RetryBehavior.ByInterval);
settings.setAutoSendMode(true);
settings.setRetryOrder(RetryOrder.Queue);
BacktraceDatabase database = new BacktraceDatabase(context, settings);
BacktraceClient backtraceClient = new BacktraceClient(context, credentials, database);
// start capturing NDK crashes
database.setupNativeIntegration(backtraceClient, credentials);
Sending Reports
The BacktraceClient.send
method sends an error report to the specified Backtrace endpoint. The send
method is overloaded—see the examples below.
Using BacktraceReport
The BacktraceReport
class represents a single error report. You can also submit custom attributes using the attributes parameter, as shown below.
- Java
- Kotlin
try {
// throw exception here
} catch (Exception e) {
BacktraceReport report = new BacktraceReport(e,
new HashMap<String, Object>() {{
put("key", "value");
}}, new ArrayList<String>() {{
add("absolute_file_path_1");
add("absolute_file_path_2");
}});
backtraceClient.send(report);
}
try {
// throw exception here
}
catch (e: Exception) {
val report = BacktraceReport(e, mapOf("key" to "value"), listOf("absolute_file_path_1", "absolute_file_path_2"))
backtraceClient.send(report)
}
Sending Reports Asynchronously
You can use the send
method to specify an event that should be performed after a report is sent to the server, as shown below.
- Java
- Kotlin
client.send(report, new OnServerResponseEventListener() {
@Override
public void onEvent(BacktraceResult backtraceResult) {
// process result here
}
});
client.send(report) { backtraceResult ->
// process result here
}
Sending Reports for an Exception or a Message
You can use the BacktraceClient.send
method to create a report for a specific exception or a custom message, as shown below.
- Java
- Kotlin
try {
// throw exception here
} catch (Exception exception) {
backtraceClient.send(new BacktraceReport(exception));
// pass exception to send method
backtraceClient.send(exception);
// pass your custom message to send method
backtraceClient.send("Message");
}
try {
// throw exception here
} catch (exception: Exception) {
backtraceClient.send(BacktraceReport(exception));
// pass exception to send method
backtraceClient.send(exception);
// pass your custom message to send method
backtraceClient.send("Message");
}
Custom Event Handlers
The BacktraceClient
allows you to attach your custom event handlers. For example, you can trigger actions before the send
method, as shown below.
- Java
- Kotlin
backtraceClient.setOnBeforeSendEventListener(new OnBeforeSendEventListener() {
@Override
public BacktraceData onEvent(BacktraceData data) {
// another code
return data;
}
});
backtraceClient.setOnBeforeSendEventListener { data ->
// another code
data
}
All events are written in "listener" pattern.
The BacktraceClient
currently supports the following events:
BeforeSend
RequestHandler
OnServerError
Unhandled Application Exceptions
The BacktraceClient
supports reporting of unhandled application exceptions not captured by your try-catch blocks. To enable reporting of unhandled exceptions:
BacktraceExceptionHandler.enable(backtraceClient);
You can also add a custom map of attributes to the BacktraceExceptionHandler
, which will be sent with each unhandled exception:
BacktraceExceptionHandler.setCustomAttributes(customAttributes);
Library Logger - Debug Mode
BacktraceLogger
is a class that helps with debugging and analysis code flow execution inside the library. Logger is a wrapper on the Android Log
class. BacktraceLogger
supports the following four logging levels:
DEBUG
WARN
ERROR
OFF
To enable displaying logs from inside the library, set the level from which information should be logged, as shown below.
BacktraceLogger.setLevel(LogLevel.DEBUG);
Monitoring Custom Threads
The backtrace-android library provides structures and methods to monitor the blocking of your own threads.
Make sure to check whether the thread is blocked and increment the counter, as shown below.
BacktraceWatchdog watchdog = BacktraceWatchdog(backtraceClient); // Initialize BacktraceWatchdog
watchdog.registerThread(customThread, timeout, delay); // Register custom thread
watchdog.checkIsAnyThreadIsBlocked(); // check if any thread has exceeded the time, by default an error will be sent to the Backtrace console
// The following code should be executed inside the thread you want to monitor
watchdog.tick(this); // In your custom thread class make incrementation to inform that the thread is not blocked
Custom Client and Report Classes
You can extend BacktraceBase
to create your own Backtrace client and error report implementation. You can refer to BacktraceClient
for an implementation example.
Breadcrumbs
Breadcrumbs help you track events leading up to your crash, error, or other submitted objects.
When breadcrumbs are enabled, any captured breadcrumbs will automatically be attached as a file to your crash, error, or other submitted object (including native crashes) and displayed in the UI on the Breadcrumbs tab.
Enabling Breadcrumbs
You can enable breadcrumbs as follows:
backtraceClient.enableBreadcrumbs(view.getContext().getApplicationContext());
Pass the application context to get automatic breadcrumbs for ActivityLifecycleCallbacks.
Adding Manual Breadcrumbs
You can add breadcrumbs as follows:
backtraceClient.addBreadcrumb("About to send Backtrace report", BacktraceBreadcrumbType.LOG);
We recommend that you do not make calls to addBreadcrumb
from performance-critical code paths.
Automatic Breadcrumbs
By default, if you enable breadcrumbs, Backtrace registers handlers to capture Android Broadcasts and other common system events, such as low memory warnings, battery warnings, screen orientation changes, ActivityLifecycleCallbacks, and more.
You can limit the types of automatic events that are captured by specifying which automatic breadcrumb types you want to enable. For example:
EnumSet<BacktraceBreadcrumbType> breadcrumbTypesToEnable = EnumSet.of(BacktraceBreadcrumbType.USER);
backtraceClient.enableBreadcrumbs(view.getContext().getApplicationContext(), breadcrumbTypesToEnable);
To disable all automatic breadcrumbs:
EnumSet<BacktraceBreadcrumbType> breadcrumbTypesToEnable = EnumSet.of(BacktraceBreadcrumbType.MANUAL);
backtraceClient.enableBreadcrumbs(view.getContext().getApplicationContext(), breadcrumbTypesToEnable);
Breadcrumbs that you add using addBreadcrumb
calls in your own code are always logged, regardless of their BacktraceBreadcrumbType
, as long as breadcrumbs are enabled. The enabled breadcrumb types do not affect your addBreadcrumb
calls.
Adding Native Breadcrumbs
To add breadcrumbs from your NDK/C++ code, you must first register your BacktraceClient
Java class with the NDK by passing it to Backtrace::InitializeNativeBreadcrumbs
, then call the function from the Backtrace header, backtrace-android.h
.
backtrace-android.h
is included with the example-app.
-
Create a JNI function which passes your active
BacktraceClient
to theBacktrace::InitializeNativeBreadcrumbs
function frombacktrace-android.h
.- JNI:
#include <jni.h>
#include "backtrace-android.h"
JNIEXPORT jboolean JNICALL
Java_backtraceio_backtraceio_MainActivity_registerNativeBreadcrumbs(JNIEnv *env, jobject thiz,
jobject backtrace_base) {
return Backtrace::InitializeNativeBreadcrumbs(env, backtrace_base);
}backtrace.-android.h
:
bool Backtrace::InitializeNativeBreadcrumbs(JNIEnv *env, jobject backtrace_base);
-
You can now add breadcrumbs from your NDK/C++ code by directly calling the below function from
backtrace-android.h
.#include <jni.h>
#include "backtrace-android.h"
std::unordered_map<std::string, std::string> attributes;
attributes["My Attribute"] = "Attribute Value";
bool success = Backtrace::AddBreadcrumb(env,
"My Native Breadcrumb",
&attributes,
Backtrace::BreadcrumbType::USER,
Backtrace::BreadcrumbLevel::ERROR);
Error-Free Metrics
Error-free metrics allow you to determine:
- How many of your unique users (i.e., unique device IDs) using your app are experiencing errors/crashes.
- How many application sessions (i.e., individual application sessions from startup till shutdown/exit) of your app are experiencing errors/crashes.
You can track those metrics at-a-glance, as well as in detail to find out what kinds of errors/crashes are most common. For more information, see Stability Metrics Widgets.
Enabling Error-Free Metrics
You can enable error-free metrics as follows:
Make sure to enable error-free metrics before you enable the native integration.
// Enable metrics
BacktraceMetricsSettings metricsSettings = new BacktraceMetricsSettings(backtraceCredentials);
backtraceClient.metrics.enable(metricsSettings);
NDK Applications
If your native app is built with NDK 16b, the Breakpad native crash client will be used instead of our recommended Crashpad crash client. To avoid this, use NDK 17c+ to build your native app.
Breakpad crash reports are submitted on the next app startup, instead of at crash time like Crashpad crash reports.
Enabling Native Integration
In general, this should be the final step in setting up your Backtrace client to ensure all attributes and file attachment paths are captured properly by the native crash handler.
To capture NDK crashes, you can use the enableNativeIntegration
method as follows:
backtraceClient.enableNativeIntegration();
In addition, you may also need to add the extractNativeLibs
option to your AndroidManifest.xml
file:
<application
android:extractNativeLibs="true">
...
</application>
For more information about extractNativeLibs
, see Android's developer documentation.
Disabling Native Integration
You can also disable the native integration as follows:
backtraceClient.disableNativeIntegration();
Breakpad does not currently support disableNativeIntegration
.
Uploading Symbols
For an NDK application, debugging symbols are not available to Backtrace by default. You will need to upload the app symbols for your native code to Backtrace.
You can do this by uploading the native libraries themselves, which are usually found in the APK bundle. For more information on how to upload symbols for an NDK app, see Symbolication.
Client-Side Unwinding
For an NDK application, debugging symbols for system functions (for instance in libc.so
) and other opaque libraries can be difficult to obtain. In these cases, it is better to unwind the callstack on the crashing application (i.e: the client).
This may not provide the same callstack quality as with debugging symbols, but will give you debugging information you would otherwise not have if you don't have debugging symbols available.
When viewing a crash in the Backtrace console, it may still show warning messages that symbols are missing from certain frames after client-side unwinding is performed. This warning is expected if these symbols are not available on the Backtrace server, and should not impact your ability to read the callstack.
To enable client side unwinding, you can call the setupNativeIntegration
method with an additional boolean value.
database.setupNativeIntegration(backtraceClient, credentials, true);
Client-side unwinding is only available for fatal crashes. For instance, non-fatal Crashpad dumps generated with DumpWithoutCrash
will not use client-side unwinding.
Client-side unwinding is only available for the following platforms:
- NDK level 17 or higher (i.e: only with the Crashpad crash reporting backend).
- SDK level 21 or higher (i.e: if the minimum SDK version is lower than 21, client-side unwinding will be disabled for 32-bit arm platforms).
Unwinding Modes
You can optionally specify the unwinding mode as follows:
database.setupNativeIntegration(backtraceClient, credentials, true, UnwindingMode.REMOTE_DUMPWITHOUTCRASH);
The following unwinding modes are available:
- LOCAL: Unwinding is done within the same process that has the crash. This is less robust than remote unwinding, but avoids the complexity of creating a child process and IPC. Local unwinding is executed from a signal handler and needs to be signal-safe.
- REMOTE: Unwinding is done by a child process. This means that the unwinding is correct even in case of severe malfunctions in the crashing parent process, and signal-safety is not a concern.
- LOCAL_DUMPWITHOUTCRASH: This option is the same as
LOCAL
unwinding, but instead of using the regular Crashpad signal hander to call the unwinder and regular Crashpad reporting mechanism, Backtrace's custom signal handler will be used to call the unwinder before we send the report using Crashpad'sDumpWithoutCrash()
method. - REMOTE_DUMPWITHOUTCRASH: This is the default and recommended option. It's the same as
REMOTE
unwinding, but instead of using the regular Crashpad signal hander to call the unwinder and regular Crashpad reporting mechanism, Backtrace's custom signal handler will be used to call the unwinder before we send the report using Crashpad'sDumpWithoutCrash()
method. - LOCAL_CONTEXT: This option is the same as
LOCAL
unwinding, but usesucontext_t *
from the signal handler to reconstruct the callstack instead.