A sample Android app for evaluating and comparing mobile observability SDKs. This project integrates two major observability platforms through a unified facade, allowing side-by-side comparison of their features, performance, and developer experience.
| SDK | Version | Focus Area |
|---|---|---|
| Sentry | 8.26.0 | Crash reporting, distributed tracing, error monitoring |
| Embrace | 8.1.0 | Mobile-focused observability, OpenTelemetry support |
The app uses a facade pattern that dispatches all observability calls to all registered SDKs simultaneously:
┌─────────────────────────────────────────────────────────────┐
│ App Code │
│ Logger.logScreenView() / ObservabilityManager.log() │
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ObservabilityManager │
│ (Dispatches to all registered providers) │
└──────────┬──────────┬──────────┬────────────────────────────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Console │ │Embrace │ │Sentry │
│Provider│ │Adapter │ │Adapter │
└────────┘ └────────┘ └────────┘
git clone https://github.com/sonrohan/ObservabilityToolshopEvaluator.git
cd ObservabilityToolshopEvaluatorCopy the example files and add your SDK keys:
# Main environment file (for Sentry)
cp .env.example .env
# Embrace requires a separate config file (in app/src/main/)
cp app/src/main/embrace-config.example.json app/src/main/embrace-config.jsonEdit .env with your Sentry DSN:
SENTRY_DSN=https://your-key@o123456.ingest.sentry.io/1234567Edit app/src/main/embrace-config.json:
{
"app_id": "your-app-id",
"api_token": "your-api-token",
"ndk_enabled": false
}Note: You don't need all keys. SDKs with empty/missing keys are simply skipped during initialization.
./gradlew installDebugOr open in Android Studio and run.
| SDK | Where to Get Keys |
|---|---|
| Sentry | sentry.io → Settings → Projects → Client Keys (DSN) |
| Embrace | dash.embrace.io → Settings → App Settings |
import com.sonrohan.observabilitytoolshipevaluator.observability.ObservabilityManager
import com.sonrohan.observabilitytoolshipevaluator.observability.LogLevel
// Log at different levels
ObservabilityManager.debug("User opened settings")
ObservabilityManager.info("Data loaded", mapOf("count" to 42))
ObservabilityManager.warning("Cache miss", mapOf("key" to "user_prefs"))
ObservabilityManager.error("Failed to save", mapOf("reason" to "disk_full"))
// Custom events
ObservabilityManager.logEvent("purchase_completed", mapOf(
"product_id" to "sku_123",
"amount" to 29.99
))ObservabilityManager.addBreadcrumb(
message = "User tapped checkout",
category = "ui",
type = BreadcrumbType.USER_ACTION,
attributes = mapOf("screen" to "cart")
)// Set user
ObservabilityManager.setUser(
id = "user_123",
email = "user@example.com",
name = "John Doe",
attributes = mapOf("plan" to "premium")
)
// Clear on logout
ObservabilityManager.clearUser()// Manual span management
val span = ObservabilityManager.startSpan("loadUserData")
try {
span.setAttribute("user_id", userId)
// ... do work ...
span.addEvent("data_fetched", mapOf("rows" to 100))
span.end()
} catch (e: Exception) {
span.endWithError(e)
throw e
}
// Or use the convenience wrapper
val result = ObservabilityManager.withSpan("fetchProducts") { span ->
span.addEvent("started")
api.getProducts() // automatically ends span on success/error
}// Record non-fatal exceptions
try {
riskyOperation()
} catch (e: Exception) {
ObservabilityManager.recordException(
throwable = e,
message = "Failed during checkout",
attributes = mapOf("cart_id" to cartId)
)
}
// Record errors without exception
ObservabilityManager.recordError(
message = "Invalid state detected",
attributes = mapOf("expected" to "A", "actual" to "B")
)The Logger object provides domain-specific convenience methods:
import com.sonrohan.observabilitytoolshipevaluator.util.Logger
// Navigation
Logger.logScreenView("ProductDetail", "ProductDetailActivity")
Logger.logTabChanged("Favorites", previousTab = "Home")
// User Actions
Logger.logButtonTap("checkout", screenName = "Cart")
Logger.logSearchExecuted("shoes", filters = mapOf("size" to "10"), resultCount = 42)
// API Tracking
Logger.logApiRequest("/api/users")
Logger.logApiResponse("/api/users", statusCode = 200, durationMs = 150, resultCount = 25)
Logger.logApiError("/api/users", error = "timeout", statusCode = 504)
// Performance
Logger.logSlowFrame(durationMs = 32, screenName = "Home")app/src/main/java/com/sonrohan/observabilitytoolshipevaluator/
├── ObservabilityApp.kt # Application class (SDK initialization)
├── MainActivity.kt # Main UI
├── observability/
│ ├── ObservabilityProvider.kt # Interface for all adapters
│ ├── ObservabilityManager.kt # Singleton dispatcher
│ ├── adapters/
│ │ ├── EmbraceAdapter.kt # Embrace SDK integration
│ │ └── SentryAdapter.kt # Sentry SDK integration
│ └── providers/
│ └── ConsoleProvider.kt # Debug/Logcat provider
└── util/
├── Logger.kt # Domain-specific logging utilities
└── CrashTestService.kt # Test crash/ANR triggers
The app includes CrashTestService for testing SDK crash reporting:
import com.sonrohan.observabilitytoolshipevaluator.util.CrashTestService
// Crash types
CrashTestService.triggerNullPointerCrash()
CrashTestService.triggerArrayIndexOutOfBounds()
CrashTestService.triggerDivideByZero()
CrashTestService.triggerStackOverflow()
CrashTestService.triggerMemoryPressure()
// Performance issues
CrashTestService.blockMainThread(5) // ANR trigger
CrashTestService.heavyComputationOnMainThread()
CrashTestService.triggerMemoryLeak()| Feature | Sentry | Embrace |
|---|---|---|
| Crash Reporting | ✅ | ✅ |
| Breadcrumbs | ✅ | ✅ |
| User Identity | ✅ | ✅ |
| Custom Events | ✅ | ✅ |
| Performance Tracing | ✅ Transactions | ✅ OpenTelemetry |
| Session Attributes | ✅ Tags/Context | ✅ Session Properties |
| Network Monitoring | ✅ Auto (OkHttp) | ✅ Auto (OkHttp) |
| ANR Detection | ✅ | ✅ |
| Session Replay | ✅ |
- Android Studio Ladybug or newer
- Kotlin 2.2+
- Java 17+ (for build environment)
- Min SDK 26, Target SDK 36
- Gradle 8.x / AGP 8.x
This is a demo/evaluation project. Use freely for testing and comparison purposes.