Backtrace's integration with Android applications written in Java or Kotlin which allows customers to capture and report handled and unhandled java exceptions to their Backtrace instance, instantly offering the ability to prioritize and debug software errors. Backtrace also captures and reports native (JNI/NDK) handled and unhandled exceptions if native integration is enabled.
Java
// replace with your endpoint url and token
BacktraceCredentials credentials = new BacktraceCredentials("<endpoint-url>", "<token>");
BacktraceClient backtraceClient = new BacktraceClient(getApplicationContext(), credentials);
try {
// throw exception here
} catch (Exception exception) {
backtraceClient.send(new BacktraceReport(e));
}
Kotlin
// replace with your endpoint url and token
val backtraceCredentials = BacktraceCredentials("<endpoint-url>", "<token>")
val backtraceClient = BacktraceClient(applicationContext, backtraceCredentials)
try {
// throw exception here
}
catch (e: Exception) {
backtraceClient.send(BacktraceReport(e))
}
- Features Summary
- Supported SDKs
- Differences and limitations of the SDKs version
- Installation
- Running sample application
- Using Backtrace library
- File attachments
- Breadcrumbs
- Working with NDK applications
- Working with Proguard
- Documentation
- Light-weight Java client library that quickly submits exceptions and crashes to your Backtrace dashboard. Can include callstack, system metadata, custom metadata and file attachments if needed.
- Supports a wide range of Android SDKs.
- Supports offline database for error report storage and re-submission in case of network outage.
- Fully customizable and extendable event handlers and base classes for custom implementations.
- Supports detection of blocking the application's main thread (Application Not Responding).
- Supports monitoring the blocking of manually created threads by providing watchdog.
- Supports native (JNI/NDK) exceptions and crashes.
- Supports Proguard obfuscated crashes.
- Supports Breadcrumbs.
- Minimum SDK version 21 (Android 5.0)
- Target SDK version 28 (Android 9.0)
- Minimum NDK version 17c
- Maximum NDK version 21
- arm32/arm64
- x86_64 emulator
- Getting the status that the device is in power saving mode is available from API 21.
- Gradle
dependencies {
implementation 'com.github.backtrace-labs.backtrace-android:backtrace-library:3.2.0'
}
- Maven
<dependency>
<groupId>com.github.backtrace-labs.backtrace-android</groupId>
<artifactId>backtrace-library</artifactId>
<version>3.1.0</version>
<type>aar</type>
</dependency>
- To send errors to the server instance you need to add permissions for Internet connection into
AndroidManifest.xml
file in your application.
<uses-permission android:name="android.permission.INTERNET" />
- To send file attachments from external storage to the server instance you need to add permissions for read external storage into
AndroidManifest.xml
file in your application.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- Open
MainActivity.java
class in app\src\main\java\backtraceio\backtraceio and replaceBacktraceCredential
constructor parameters with yourBacktrace endpoint URL
(e.g. https://xxx.sp.backtrace.io:6098) andsubmission token
:
Java
BacktraceCredentials credentials = new BacktraceCredentials("https://<yourInstance>.sp.backtrace.io:6098/", "<submissionToken>");
Kotlin
val backtraceCredentials = BacktraceCredentials("https://<yourInstance>.sp.backtrace.io:6098/", "<submissionToken>")
First start:
- Press
Run
andRun..
or type keys combinationAlt+Shift+F10
. - As module select
app
other options leave default. - Select
Run
and then select your emulator or connected device. - You should see new errors in your Backtrace instance. Refresh the Project page or Query Builder to see new details in real-time.
First create a BacktraceCredential
instance with your Backtrace endpoint URL
(e.g. https://xxx.sp.backtrace.io:6098) and submission token
, and supply it as a parameter in the BacktraceClient
constructor:
Java
BacktraceCredentials credentials = new BacktraceCredentials("https://<yourInstance>.sp.backtrace.io:6098/", "<submissionToken>");
BacktraceClient backtraceClient = new BacktraceClient(getApplicationContext(), credentials);
Kotlin
val backtraceCredentials = BacktraceCredentials("https://<yourInstance>.sp.backtrace.io:6098/", "<submissionToken>")
val backtraceClient = BacktraceClient(applicationContext, backtraceCredentials)
Another option for creating a BacktraceCredentials object is using the URL to which the report is to be sent, pass URL string as parameter to BacktraceCredentials
constructor:
Java
BacktraceCredentials credentials = new BacktraceCredentials("https://submit.backtrace.io/{universe}/{token}/json");
Kotlin
val backtraceCredentials = BacktraceCredentials("https://submit.backtrace.io/{universe}/{token}/json")
It is possible to add your global custom attributes to BacktraceClient and send them with each of report. To do it you should pass map with custom attributes to BacktraceClient constructor method.
Java
Map<String, Object> attributes = new HashMap<String, Object>(){{
put("custom-attribute-key", "custom-attribute-value");
}};
BacktraceClient backtraceClient = new BacktraceClient(context, credentials, attributes);
Kotlin
val attributes: HashMap<String, Any> = hashMapOf("custom-attribute-key" to "custom-attribute-value")
val backtraceClient = BacktraceClient(context, credentials, attributes)
Backtrace client allows you to detect that main thread is blocked, you can pass timeout
as argument and event
which should be executed instead of sending the error information to the Backtrace console by default. You can also provide information that the application is working in the debug mode by providing debug
parameter, then if the debugger is connected errors will not be reported. Default value of timeout
is 5 seconds.
backtraceClient.enableAnr(timeout, event, debug);
BacktraceClient allows you to customize the initialization of BacktraceDatabase for local storage of error reports by supplying a BacktraceDatabaseSettings parameter, as follows:
Java
BacktraceCredentials credentials = new BacktraceCredentials("https://myserver.sp.backtrace.io:6097/", "4dca18e8769d0f5d10db0d1b665e64b3d716f76bf182fbcdad5d1d8070c12db0");
Context context = getApplicationContext();
String dbPath = context.getFilesDir().getAbsolutePath(); // 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);
Method BacktraceClient.send
will send an error report to the Backtrace endpoint specified. There send
method is overloaded, see examples below:
The BacktraceReport
class represents a single error report. (Optional) You can also submit custom attributes using the attributes
parameter.
Java
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);
}
Kotlin
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)
}
Method send
behind the mask use dedicated thread which sending report to server. You can specify the method that should be performed after completion.
Java
client.send(report, new OnServerResponseEventListener() {
@Override
public void onEvent(BacktraceResult backtraceResult) {
// process result here
}
});
Kotlin
client.send(report) { backtraceResult ->
// process result here
}
BacktraceClient
can also automatically create BacktraceReport
given an exception or a custom message using the following overloads of the BacktraceClient.send
method:
Java
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");
}
Kotlin
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");
}
All events are written in listener pattern. BacktraceClient
allows you to attach your custom event handlers. For example, you can trigger actions before the send
method:
Java
backtraceClient.setOnBeforeSendEventListener(new OnBeforeSendEventListener() {
@Override
public BacktraceData onEvent(BacktraceData data) {
// another code
return data;
}
});
Kotlin
backtraceClient.setOnBeforeSendEventListener { data ->
// another code
data
}
BacktraceClient
currently supports the following events:
BeforeSend
RequestHandler
OnServerError
BacktraceClient
also supports reporting of unhandled application exceptions not captured by your try-catch blocks. To enable reporting of unhandled exceptions:
BacktraceExceptionHandler.enable(backtraceClient);
You can add custom map of attributes to BacktraceExceptionHandler
which will be sent with each unhandled exception:
BacktraceExceptionHandler.setCustomAttributes(customAttributes);
BacktraceLogger
is a class which helps with debugging and analysis code flow execution inside the library. Logger is a wrapper on Android Log
class. BacktraceLogger
supports 4 logging levels:
DEBUG
WARN
ERROR
OFF
In order to enable displaying logs from inside the library, one should set the level from which information should be logged:
BacktraceLogger.setLevel(LogLevel.DEBUG);
You can extend BacktraceBase
to create your own Backtrace client and error report implementation. You can refer to BacktraceClient
for implementation inspirations.
Library provides structures and methods to monitor the blocking of your own threads. It is the responsibility of the library user to check whether the thread is blocked and the user's thread should increment the counter.
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
You can enable default file attachments which will be sent with all Backtrace reports both managed and native.
final String fileName = context.getFilesDir() + "/" + "myCustomFile.txt";
List<String> attachments = new ArrayList<String>(){{
add(fileName);
}};
backtraceClient = new BacktraceClient(context, credentials, database, attributes, attachments);
Backtrace crash file attachment paths 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
final String fileName = context.getFilesDir() + "/" + "myCustomFile.txt";
List<String> attachments = new ArrayList<String>(){{
add(fileName);
}};
backtraceClient = new BacktraceClient(context, credentials, database, attributes, attachments);
// The actual filename of the desired log, not known to the BacktraceClient on initialization
final String fileNameDateString = context.getFilesDir() + "/" + "myCustomFile06_11_2021.txt";
// Create symlink
Os.symlink(fileNameDateString, fileName);
Note: If you create any new files in the same directory as your BacktraceDatabase
directory, they will be deleted when you create a new BacktraceClient
.
Breadcrumbs help you track events leading up to your crash, error, or other submitted object.
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 in the Breadcrumbs
tab.
backtraceClient.enableBreadcrumbs(view.getContext().getApplicationContext());
Pass the Application Context to get automatic breadcrumbs for ActivityLifecycleCallbacks
backtraceClient.addBreadcrumb("About to send Backtrace report", BacktraceBreadcrumbType.LOG);
By default if you enable breadcrumbs we will register handlers to capture Android Broadcasts and other common system events, such as low memory warnings, battery warnings, screen orientation changes, ActivityLifecycleCallbacks, etc.
You can limit the types of automatic events we capture for you by specifying which automatic breadcrumb types you want to enable, such as:
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);
NOTE: 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 own addBreadcrumb
calls.
To add breadcrumbs from NDK, first you must register your BacktraceClient
Java class with the NDK.
You can do this by creating a JNI function which passes your active BacktraceClient
to the Backtrace::InitializeNativeBreadcrumbs
function from the Backtrace header, backtrace-android.h
. backtrace-android.h
is included in the example-app
in this repo.
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);
Once you have registered your BacktraceClient
by passing it to Backtrace::InitializeNativeBreadcrumbs
, you can 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);
- Don't make calls to
addBreadcrumb
from performance-critical code paths.
If you would like to capture NDK Crashes you can use the BacktraceDatabase
setupNativeIntegration
method.
database.setupNativeIntegration(backtraceClient, credentials);
In addition, you may need to add the extractNativeLibs option to your AndroidManifest.xml:
<application
android:extractNativeLibs="true">
...
</application>
More details about extractNativeLibs are available from the Android documentation
For an NDK application, debugging symbols are not available to Backtrace by default. You will need to upload the application 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. Click here to learn more about symbolification
These are needed since Proguard breaks some Backtrace libraries
-keep class com.google.gson.**.* { *; }
-keep class backtraceio.library.**.* { *; }
2. Enable Proguard mode in the BacktraceClient
backtraceClient.enableProguard();
3. Create a UUID of your choice and set it as the value for the attribute symbolication_id
, you will upload your Proguard mapping file with this same UUID later
final UUID proguardMappingUUID = UUID.fromString("f6c3e8d4-8626-4051-94ec-53e6daccce25");
final Map<String, Object> attributes = new HashMap<String, Object>() {{
put("symbolication_id", proguardMappingUUID.toString());
}};
Currently we don't have a way to upload the Proguard mapping file from the UI. You will need to use a tool such as curl
or Postman to upload the Proguard mapping file to Backtrace.
To do so, please construct an HTTP POST request with the following parameters, and submit the mapping file as the request body:
https://<Universe Name>.sp.backtrace.io:6098/post?format=proguard&token=<Symbol Upload Token>&universe=<Universe Name>&project=<Project Name>&symbolication_id=<symbolication_id from above>
If the symbolication_id from the submitted crash matches a symbolication_id of a submitted Proguard mapping file, it will attempt to use that mapping file to deobfuscate the symbols from the submitted crash.
Please ensure your Proguard mapping file has Unix line endings before submitting to Backtrace!
BacktraceReport
is a class that describe a single error report.
BacktraceClient
is a class that allows you to instantiate a client instance that interacts with BacktraceApi
. This class sets up connection to the Backtrace endpoint and manages error reporting behavior. BacktraceClient
extends BacktraceBase
class.
BacktraceData
is a serializable class that holds the data to create a diagnostic JSON to be sent to the Backtrace endpoint via BacktraceApi
. You can add additional pre-processors for BacktraceData
by attaching an event handler to the BacktraceClient.setOnBeforeSendEventListener(event)
event. BacktraceData
require BacktraceReport
and BacktraceClient
client attributes.
BacktraceApi
is a class that sends diagnostic JSON to the Backtrace endpoint. BacktraceApi
is instantiated when the BacktraceClient
constructor is called. You use the following event handlers in BacktraceApi
to customize how you want to handle JSON data:
RequestHandler
- attach an event handler to this event to override the defaultBacktraceApi.send
method.OnServerError
- attach an event handler to be invoked when the server returns with a400 bad request
,401 unauthorized
or other HTTP error codes.
BacktraceResult
is a class that holds response and result from a send
method call. The class contains a status
property that indicates whether the call was completed (OK
), the call returned with an error (ServerError
), . Additionally, the class has a message
property that contains details about the status.
BacktraceDatabase
is a class that stores error report data in your local hard drive. If DatabaseSettings
dones't contain a valid DatabasePath
then BacktraceDatabase
won't store error report data.
BacktraceDatabase
stores error reports that were not sent successfully due to network outage or server unavailability. BacktraceDatabase
periodically tries to resend reports
cached in the database. In BacktraceDatabaseSettings
you can set the maximum number of entries (MaxRecordCount
) to be stored in the database. The database will retry sending
stored reports every RetryInterval
seconds up to RetryLimit
times, both customizable in the BacktraceDatabaseSettings
.
BacktraceDatabaseSettings
has the following properties:
DatabasePath
- the local directory path whereBacktraceDatabase
stores error report data when reports fail to sendMaxRecordCount
- Maximum number of stored reports in Database. If value is equal to0
, then there is no limit.MaxDatabaseSize
- Maximum database size in MB. If value is equal to0
, there is no limit.AutoSendMode
- if the value istrue
,BacktraceDatabase
will automatically try to resend stored reports. Default isfalse
.RetryBehavior
-RetryBehavior.ByInterval
- Default.BacktraceDatabase
will try to resend the reports every time interval specified byRetryInterval
.RetryBehavior.NoRetry
- Will not attempt to resend reports
RetryInterval
- the time interval between retries, in seconds.RetryLimit
- the maximum number of timesBacktraceDatabase
will attempt to resend error report before removing it from the database.
If you want to clear your database or remove all reports after send method you can use clear
or flush
methods.