Android SDK
Native Kotlin SDK for Android with automatic crash detection, ANR detection, OkHttp integration, and performance monitoring.
Built with Kotlin, supports API 19+ (Android 4.4+)
Catches uncaught exceptions, plus native crashes on Android 11+
Detects Application Not Responding issues
Track user journey before errors
Automatic HTTP request tracking
Track API latency with OkHttp interceptors
Automatic device, OS, and app info collection
Installation#
The SDK comes from Abrova Center. Two steps:
1. Add the Abrova repository in settings.gradle.kts (or settings.gradle):
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") }
}
}Groovy: maven { url "https://center.abrova.ir/api/packages/abrova/maven" }
2. Add the dependency to your app's build.gradle.kts:
dependencies {
implementation("ir.abrova.trace:abrova-trace-android-sdk:0.2.3")
}Then sync your project with Gradle files. Requirements: Android 4.4 (API 19) or later; native crash reports need Android 11 (API 30) or later.
Quick Start#
Initialize AbrovaTrace in your Application class:
import android.app.Application
import ir.abrova.trace.sdk.AbrovaTrace
import ir.abrova.trace.sdk.AbrovaTraceConfig
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY")
.environment(if (BuildConfig.DEBUG) "development" else "production")
.release(BuildConfig.VERSION_NAME)
.debug(BuildConfig.DEBUG)
.captureUncaughtExceptions(true)
.captureSignalCrashes(true)
.captureAnr(true)
.anrTimeoutMs(5000L)
.build()
AbrovaTrace.init(this, config)
}
override fun onTerminate() {
AbrovaTrace.close()
super.onTerminate()
}
}onTerminate() is never called on real devices, so also call close() in your main Activity. close() flushes buffered logs, stops the ANR watchdog and shuts down the network client. The API key must start with ab_live_ — the config throws otherwise.
class MainActivity : AppCompatActivity() {
override fun onDestroy() {
if (isFinishing) {
AbrovaTrace.close() // Flush logs, stop the ANR watchdog
}
super.onDestroy()
}
}Capture Errors#
Capture Exceptions
try {
someRiskyOperation()
} catch (e: Exception) {
AbrovaTrace.captureException(e)
}Capture with Extra Context
AbrovaTrace.captureException(
throwable = exception,
message = "Failed to load user profile",
extra = mapOf("user_id" to userId, "screen" to "ProfileActivity"),
tags = mapOf("feature" to "user_profile")
)Capture Messages
import ir.abrova.trace.sdk.models.MessageLevel
// Info message
AbrovaTrace.captureMessage("User completed onboarding", MessageLevel.INFO)
// Warning message
AbrovaTrace.captureMessage("API rate limit approaching", MessageLevel.WARNING)
// Error message with extra data
AbrovaTrace.captureMessage(
message = "Payment failed",
level = MessageLevel.ERROR,
extra = mapOf("amount" to 99.99)
)Messages appear in the Console under Abrova Trace → Messages, with their level, platform, extra data and breadcrumbs. They never create an issue. Levels: MessageLevel.DEBUG, INFO (default), WARNING, ERROR. FATAL is accepted and stored as error. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs.
Breadcrumbs#
Breadcrumbs help you understand what happened before an error occurred. They are automatically added for Activity navigation, app foreground/background, and system events.
import ir.abrova.trace.sdk.models.BreadcrumbType
// User action
AbrovaTrace.addUserBreadcrumb("Added item to cart", mapOf("product_id" to "abc"))
// Navigation
AbrovaTrace.addNavigationBreadcrumb(from = "MainActivity", to = "ProductDetailActivity")
// HTTP request
AbrovaTrace.addHttpBreadcrumb(
method = "POST",
url = "https://api.example.com/orders",
statusCode = 201,
duration = 342L
)
// Custom breadcrumb
AbrovaTrace.addBreadcrumb("Order placed", BreadcrumbType.INFO, mapOf(
"order_id" to "12345",
"total" to 99.99
))OkHttp Integration#
Automatically track HTTP requests as breadcrumbs and capture HTTP errors:
Basic Setup
import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor
import okhttp3.OkHttpClient
val client = OkHttpClient.Builder()
.addInterceptor(AbrovaTraceInterceptor())
.build()With Error Capturing
import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor
import ir.abrova.trace.sdk.network.AbrovaTraceErrorInterceptor
import okhttp3.OkHttpClient
val client = OkHttpClient.Builder()
.addInterceptor(AbrovaTraceInterceptor()) // Adds breadcrumbs
.addInterceptor(AbrovaTraceErrorInterceptor()) // Captures HTTP errors
.build()AbrovaTraceErrorInterceptor sends each 5xx response as a message at error level (Abrova Trace → Messages). 4xx responses are off by default — turn them on with AbrovaTraceErrorInterceptor(captureClientErrors = true) (sent at warning). Network failures (no response) are captured as exceptions.
Crash Detection#
AbrovaTrace detects crashes in-process and, on Android 11+, from the system's own exit records.
- Java/Kotlin Exceptions — Captured in real-time by an uncaught exception handler (
captureUncaughtExceptions). The report is sent before the app closes, on any thread including the main thread; if the server does not accept it within 2 seconds it is kept on the device and sent on the next launch. Requires version 0.2.2 or newer. - ANR Events — A watchdog thread detects the main thread being blocked longer than
anrTimeoutMs(captureAnr) - Native Crashes — SIGSEGV, SIGABRT, etc., reported on the next launch (Android 11 / API 30+ only,
captureSignalCrashes)
How It Works
- On each launch (API 30+), AbrovaTrace reads Android's
ApplicationExitInforecords for your app - Exits since the last launch with reason JVM crash, native crash or ANR are converted into crash reports, including the tombstone for native crashes
- The timestamp of the last sent record is saved, so each crash is reported once; a failed send is retried on the next launch. A Java/Kotlin crash the in-process handler already reported is skipped here
- On Android 10 and below this step does nothing — JVM crashes are still captured by the in-process handler
Performance Monitoring (APM)#
Track API latency and HTTP request performance automatically.
Automatic HTTP Performance Tracking with OkHttp
import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor
import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor
import okhttp3.OkHttpClient
import okhttp3.Request
val client = OkHttpClient.Builder()
.addInterceptor(AbrovaTraceInterceptor()) // Breadcrumbs
.addInterceptor(AbrovaTracePerformanceInterceptor()) // APM tracking
.build()
// All API calls are automatically tracked!
val request = Request.Builder()
.url("https://api.example.com/users")
.build()
client.newCall(request).execute().use { response ->
// Duration, status code, and errors are automatically reported
val users = response.body?.string()
}Using with Retrofit
import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor
import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(AbrovaTraceInterceptor())
.addInterceptor(AbrovaTracePerformanceInterceptor())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(MyApiService::class.java)
// All API calls through this Retrofit instance are automatically trackedTrack Custom Operations
import ir.abrova.trace.sdk.AbrovaTrace
val result = AbrovaTrace.trackOperation("load_user_profile") {
val user = fetchUserFromApi()
cacheUserLocally(user)
user
}Manual Span Reporting
import ir.abrova.trace.sdk.models.PerformanceSpan
// HTTP request span
AbrovaTrace.reportPerformanceSpan(
PerformanceSpan.fromHttpRequest(
method = "GET",
url = "https://api.example.com/products",
statusCode = 200,
durationMs = 245L,
startTime = System.currentTimeMillis() - 245
)
)
// Database query span
AbrovaTrace.reportPerformanceSpan(
PerformanceSpan.forDbQuery(
queryType = "SELECT",
tableName = "products",
durationMs = 15L,
startTime = System.currentTimeMillis() - 15,
rowsAffected = 50
)
)
// Custom operation span
AbrovaTrace.reportPerformanceSpan(
PerformanceSpan.custom(
operation = "image_processing",
durationMs = 1250L,
startTime = System.currentTimeMillis() - 1250,
tags = mapOf("format" to "png")
)
)Logging#
Structured log ingestion with automatic batching and retry. Enable it once, then use the level helpers from anywhere in your app:
// Enable logging after AbrovaTrace.init()
AbrovaTrace.enableLogging(sourceId = "my-android-app")
AbrovaTrace.info("User login successful", mapOf("user_id" to "user_123"))See Android SDK Logging for the full pipeline — batching, log levels, lifecycle handling, manual flush, and configuration.
Configuration Options#
val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY")
.environment("production") // Environment name
.release("1.0.0") // App version
.debug(false) // Enable debug logging
.enabled(true) // Enable/disable SDK
.sampleRate(1.0f) // Error sample rate (0.0 - 1.0)
.maxBreadcrumbs(20) // Max breadcrumbs to keep
.httpTimeout(30) // HTTP request timeout (seconds)
.enableOfflineStorage(true) // Keep unsent errors on the device while offline
.captureUncaughtExceptions(true) // Catch uncaught exceptions
.captureSignalCrashes(true) // Detect native signal crashes
.captureAnr(true) // Detect ANR issues
.anrTimeoutMs(5000L) // ANR detection timeout
.apiUrl("https://trace.abrova.ir") // Server URL (self-hosted override)
.build()| Option | Type | Required | Description |
|---|---|---|---|
| apiKey | String | Yes | Your project API Key (must start with ab_live_) |
| environment | String | No | Environment name (default: "production") |
| release | String | No | App version identifier |
| debug | Boolean | No | Enable debug logging (default: false) |
| enabled | Boolean | No | Enable/disable SDK (default: true) |
| sampleRate | Float | No | Sample rate 0.0-1.0 for captureException (default: 1.0) |
| maxBreadcrumbs | Int | No | Max breadcrumbs to keep (default: 20) |
| httpTimeout | Int | No | HTTP request timeout in seconds (default: 30) |
| enableOfflineStorage | Boolean | No | Keep errors that could not be sent while the device is offline (up to 100) and send them later (default: true) |
| captureUncaughtExceptions | Boolean | No | Catch uncaught exceptions (default: true) |
| captureSignalCrashes | Boolean | No | Report native crashes, and other crashes and ANRs recorded by the system, on next launch, API 30+ (default: true) |
| captureAnr | Boolean | No | Detect ANR events (default: true) |
| anrTimeoutMs | Long | No | ANR detection timeout in ms (default: 5000) |
| apiUrl | String | No | Trace server URL (default: "https://trace.abrova.ir") |
When an error cannot be sent because the device has no connection, the SDK keeps it in the app's private storage and sends it later: on the next launch, and as soon as another error is sent successfully. Up to 100 errors are kept; when the limit is reached, the oldest are dropped. This covers errors only — captureException, uncaught exceptions and ANR reports. Messages, logs and performance data are not stored. Set enableOfflineStorage(false) to keep nothing on the device.
ProGuard / R8#
The SDK includes ProGuard rules automatically. If you need custom rules, add to proguard-rules.pro:
-keep class ir.abrova.trace.sdk.** { *; }
-keepattributes SourceFile,LineNumberTable
-keepattributes *Annotation*Device Compatibility#
- Android 4.4 KitKat — API 19
- Android 5.0 Lollipop — API 21
- Android 8.0 Oreo — API 26
- Android 10 — API 29
- Android 12 — API 31
- Android 14 — API 34
Context & Tags#
Attach persistent metadata to every error and message captured by the SDK (tags are also added to performance spans). Useful for filtering by team, region, feature flag, or experiment cohort in the Abrova Console. ANR reports and crash reports read from the system on the next launch do not carry them.
Global extras
Extras are arbitrary key/value pairs (string keys, any serializable value) automatically merged into every event:
// Set individual extras
AbrovaTrace.setExtra("organizationId", "org-123")
AbrovaTrace.setExtra("feature", "checkout-v2")
// Set multiple at once
AbrovaTrace.setExtras(mapOf(
"organizationId" to "org-123",
"feature" to "checkout-v2",
"experiment" to "new-flow"
))
// Clear when no longer needed (e.g. on logout)
AbrovaTrace.clearExtras()Tags
Tags are string-only key/value pairs that are attached to every error and message:
AbrovaTrace.setTag("team", "payments")
AbrovaTrace.setTag("region", "us-east")
AbrovaTrace.setTags(mapOf("team" to "payments", "version" to "2.1"))
AbrovaTrace.clearTags()Use tags for low-cardinality categorical filters (team, region, build flavor). Use extras for richer per-event context (user IDs, request bodies, computed values).
API Reference#
Full method surface of AbrovaTrace and PerformanceSpan. Method signatures use the Kotlin convention; optional parameters are marked with ?.
AbrovaTrace — Initialization & Lifecycle
| Option | Type | Required | Description |
|---|---|---|---|
| init(context, config) | - | - | Initialize SDK with a full AbrovaTraceConfig |
| init(context, apiKey) | - | - | Initialize SDK with just an API key (uses defaults) |
| isInitialized() | - | - | Returns true if the SDK is initialized |
| close() | - | - | Flush logs, stop the ANR watchdog and shut down the network client |
Error capture
| Option | Type | Required | Description |
|---|---|---|---|
| captureException(throwable, message?, extra?, tags?, callback?) | - | - | Capture an exception with optional metadata |
| captureMessage(message, level?, extra?, tags?, callback?) | - | - | Send a message to Abrova Trace → Messages at a given level (not an issue) |
User & session
| Option | Type | Required | Description |
|---|---|---|---|
| setUserId(id) | - | - | Set the current user identifier |
| getUserId() | - | - | Get the current user identifier |
Global context (extras)
| Option | Type | Required | Description |
|---|---|---|---|
| setExtra(key, value) | - | - | Set a single extra |
| setExtras(extras) | - | - | Set multiple extras at once |
| clearExtras() | - | - | Clear all extras |
Tags
| Option | Type | Required | Description |
|---|---|---|---|
| setTag(key, value) | - | - | Set a single tag |
| setTags(tags) | - | - | Set multiple tags at once |
| clearTags() | - | - | Clear all tags |
Breadcrumbs
| Option | Type | Required | Description |
|---|---|---|---|
| addBreadcrumb(message, type?, data?) | - | - | Add a generic breadcrumb |
| addNavigationBreadcrumb(from, to) | - | - | Add a navigation breadcrumb |
| addUserBreadcrumb(action, data?) | - | - | Add a user-action breadcrumb |
| addHttpBreadcrumb(method, url, statusCode?, duration?) | - | - | Add an HTTP breadcrumb |
| clearBreadcrumbs() | - | - | Clear all breadcrumbs |
Performance monitoring
| Option | Type | Required | Description |
|---|---|---|---|
| reportPerformanceSpan(method, url, statusCode, durationMs, startTime, errorMessage?, tags?) | - | - | Report a single HTTP span |
| reportPerformanceSpan(span, callback?) | - | - | Report a constructed PerformanceSpan |
| reportPerformanceSpanBatch(spans, callback?) | - | - | Report multiple spans at once |
| trackOperation(operation, operationType?, tags?, block) | - | - | Time a block and report it as a span |
| performanceInterceptor(minDurationMs?) | - | - | Build an OkHttp interceptor for HTTP timings |
Logging
| Option | Type | Required | Description |
|---|---|---|---|
| enableLogging(sourceId?, sourceName?, batchSize?, flushIntervalMs?) | - | - | Turn on the log pipeline |
| log(message, level?, metadata?) | - | - | Log at an explicit level |
| trace(message, metadata?) | - | - | Log at trace level |
| logDebug(message, metadata?) | - | - | Log at debug level |
| info(message, metadata?) | - | - | Log at info level |
| warn(message, metadata?) | - | - | Log at warn level |
| logError(message, metadata?) | - | - | Log at error level |
| fatal(message, metadata?) | - | - | Log at fatal level |
| flushLogs(callback?) | - | - | Flush buffered logs immediately |
| getPendingLogCount() | - | - | Number of logs still in the buffer |
PerformanceSpan factories
| Option | Type | Required | Description |
|---|---|---|---|
| PerformanceSpan.fromHttpRequest(...) | - | - | Construct a span from an HTTP request/response |
| PerformanceSpan.forDbQuery(...) | - | - | Construct a span for a database query |
| PerformanceSpan.custom(...) | - | - | Construct a fully custom span |
| PerformanceSpan.generateTraceId() | - | - | Generate a random trace ID |
| PerformanceSpan.generateSpanId() | - | - | Generate a random span ID |
Complete Example#
import android.app.Application
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import ir.abrova.trace.sdk.AbrovaTrace
import ir.abrova.trace.sdk.AbrovaTraceConfig
import ir.abrova.trace.sdk.models.MessageLevel
import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor
import okhttp3.OkHttpClient
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY")
.environment(if (BuildConfig.DEBUG) "development" else "production")
.release(BuildConfig.VERSION_NAME)
.debug(BuildConfig.DEBUG)
.captureUncaughtExceptions(true)
.captureSignalCrashes(true)
.captureAnr(true)
.anrTimeoutMs(5000L)
.maxBreadcrumbs(20)
.enableOfflineStorage(true)
.build()
AbrovaTrace.init(this, config)
}
override fun onTerminate() {
AbrovaTrace.close()
super.onTerminate()
}
}
class MainActivity : AppCompatActivity() {
private val httpClient = OkHttpClient.Builder()
.addInterceptor(AbrovaTraceInterceptor())
.build()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AbrovaTrace.setUserId("user_123")
AbrovaTrace.addUserBreadcrumb("Viewed home screen")
}
private fun handlePurchase() {
try {
processPurchase()
AbrovaTrace.captureMessage(
message = "Purchase successful",
level = MessageLevel.INFO,
extra = mapOf("amount" to 99.99)
)
} catch (e: Exception) {
AbrovaTrace.captureException(
throwable = e,
message = "Purchase failed",
extra = mapOf("user_id" to "user_123", "screen" to "MainActivity")
)
}
}
override fun onDestroy() {
if (isFinishing) {
AbrovaTrace.close()
}
super.onDestroy()
}
private fun processPurchase() { /* ... */ }
}Best Practices#
Initialize AbrovaTrace in your Application class onCreate() to catch all errors from app start
Flushes buffered logs and stops the ANR watchdog
Include user IDs, screen names, and relevant data in the extra parameter
Separate production, staging, and development errors using the environment config
Set debug(true) during development to see SDK logs and verify integration
Add AbrovaTraceInterceptor for HTTP breadcrumbs and AbrovaTraceErrorInterceptor for HTTP error reporting
Call enableLogging() right after init() to capture all application events