
In-app debugging overlay for inspecting logs, HTTP traffic, and analytics with syntax-highlighted JSON, secure header redaction, body truncation, modular plugin panels, and zero release overhead.
Extensible on-device dev tools for Kotlin Multiplatform
An in-app debugging overlay for KMP — inspect logs, network traffic, and analytics events with a beautiful Compose UI. No external tools needed.
Features • Installation • Quick Start • Plugins • Custom Plugins • Documentation
| Feature | Description |
|---|---|
| 🔍 Log Inspector | Search, filter, and copy logs with syntax-highlighted JSON |
| 🌐 Network Viewer | HTTP request/response inspection with method badges |
| 📊 Analytics Tracker | Monitor analytics events in real-time |
| 🎨 Beautiful UI | Material3 design with light/dark mode support |
| 🧩 Plugin System | Extend with custom debug panels through modular dependencies |
| 📱 Adaptive Layout | Bottom sheet on phones, dialog on tablets |
| 🔌 Zero Release Overhead | Disable with a single flag — no runtime cost |
| 🍎 Multiplatform | Android, iOS, Desktop (JVM), Web (WASM) |
AELog is fully modularized. You only add the artifact for the feature you need.
Every plugin module carries its dependencies transitively, so you never need to add
ae-log-core or intermediate modules manually.
Pick your scenario below — copy only that block.
// build.gradle.kts
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-logs:1.0.5")
// ↳ transitively includes ae-log-core
}// build.gradle.kts
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-network-ktor:1.0.5")
// ↳ transitively includes ae-log-network and ae-log-core
}// build.gradle.kts
androidMain.dependencies {
implementation("io.github.abdo-essam:ae-log-network-okhttp:1.0.5")
// ↳ transitively includes ae-log-network and ae-log-core
}// build.gradle.kts
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-analytics:1.0.5")
// ↳ transitively includes ae-log-core
}For a KMP project with Ktor on all platforms and OkHttp on Android:
// build.gradle.kts (shared module)
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-logs:1.0.5")
implementation("io.github.abdo-essam:ae-log-network-ktor:1.0.5")
implementation("io.github.abdo-essam:ae-log-analytics:1.0.5")
}
androidMain.dependencies {
// Add this only if your Android target also uses OkHttp
implementation("io.github.abdo-essam:ae-log-network-okhttp:1.0.5")
}
}
}| Artifact | Transitively includes | Platform |
|---|---|---|
ae-log-logs |
ae-log-core |
KMP |
ae-log-network |
ae-log-core |
KMP |
ae-log-network-ktor |
ae-log-network, ae-log-core
|
KMP |
ae-log-network-okhttp |
ae-log-network, ae-log-core
|
Android |
ae-log-analytics |
ae-log-core |
KMP |
[versions]
aelog = "1.0.5"
[libraries]
aelog-logs = { module = "io.github.abdo-essam:ae-log-logs", version.ref = "aelog" }
aelog-network-ktor = { module = "io.github.abdo-essam:ae-log-network-ktor", version.ref = "aelog" }
aelog-network-okhttp = { module = "io.github.abdo-essam:ae-log-network-okhttp", version.ref = "aelog" }
aelog-analytics = { module = "io.github.abdo-essam:ae-log-analytics", version.ref = "aelog" }Best called early in your platform-specific entry points (e.g. Application.onCreate for Android, or main ViewController for iOS):
AELog.init(
LogPlugin(), // Built-in log viewer
NetworkPlugin(), // Network inspector
AnalyticsPlugin() // Analytics tracker
)Wrap your main content:
@Composable
fun App(debugMode: Boolean) {
LogProvider(
enabled = debugMode, // ← disables UI overhead in release builds
uiConfig = UiConfig(
showFloatingButton = true, // Enables the 'bug' overlay button
enableLongPress = true, // Show panel on 3-finger long press
)
) {
MaterialTheme {
YourAppContent()
}
}
}No need to add Compose to your app! Just launch the built-in activity anywhere (like a developer menu button):
import com.ae.log.launchViewer
import com.ae.log.AELog
// Call from any Activity or Fragment
AELog.launchViewer(requireContext()) AELog is a discoverable object modelled after Android's built-in Log class.
Just type AELog. and the IDE lists every method — no extension hunting required:
AELog.log.v("Auth", "Token checked")
AELog.log.d("Auth", "Token refreshed")
AELog.log.i("HomeScreen", "App launched!")
AELog.log.w("Auth", "Session expiring soon")
AELog.log.e("Database", "Failed to clear cache", exception) // stack trace auto-appended
AELog.log.wtf("Auth", "Unexpected state")All calls are silent no-ops if
AELog.init()has not been called yet — safe in shared modules that run before app startup.
Omit the tag and AELog derives it from the caller's class name automatically. No repetition, no overhead:
AELog.log.d("Token refreshed") // tag → "AuthViewModel"
AELog.log.i("App launched!") // tag → "HomeScreen"
AELog.log.e("Failed to clear cache", t) // tag → "Database"// Network & Analytics APIs
AELog.network.logRequest(method = "GET", url = "https://api.example.com/users")
AELog.network.logResponse(url = "https://api.example.com/users", statusCode = 200)
AELog.analytics.logEvent("item_added_to_cart", properties = mapOf("id" to "123"))AELog provides first-class interceptors for OkHttp and Ktor.
Both interceptors are secure by default. They automatically redact sensitive headers like Authorization and Cookie to prevent token leakage in logs.
// OkHttp
val interceptor = OkHttpInterceptor(
redactHeaders = setOf("X-Sensitive-Header") // Extends default redactions
)
// Ktor
val client = HttpClient {
install(KtorInterceptor) {
redactHeaders = setOf("X-Api-Key")
}
}To prevent memory issues when inspecting large payloads (e.g., file uploads), bodies are automatically truncated (default 250 KB).
OkHttpInterceptor(
maxRequestBodyBytes = 500_000, // 500 KB limit
maxResponseBodyBytes = 1_000_000 // 1 MB limit
)By default, Ktor response streams can only be read once. To enable non-destructive inspection of response bodies:
DoubleReceive plugin in your HttpClient.bodyAsText(). If DoubleReceive is not installed, this may consume the stream—ensure your app logic is compatible or use the recommended plugin.val client = HttpClient {
install(DoubleReceive) // Recommended for Network Plugin
install(KtorInterceptor)
}Three ways to open the inspector:
LocalLogController.current.show()
| Module / Plugin | Class | Description |
|---|---|---|
:ae-log-core |
- | Infrastructure, EventBus, and UI Shell |
:ae-log-logs |
LogPlugin |
Log viewer with severity filters (ALL / VERBOSE / DEBUG / INFO / WARN / ERROR) |
:ae-log-network |
NetworkPlugin |
HTTP inspector with method badges, status filtering (2xx / 4xx / 5xx) and full body view |
:ae-log-analytics |
AnalyticsPlugin |
Analytics tracker separating Screens / Events with expandable properties |
Create your own debug panel (e.g., a Database Inspector or Feature Flags toggler) in 3 steps:
class FeatureFlagsPlugin : UIPlugin {
override val id = "feature_flags"
override val name = "Flags"
override val icon = Icons.Default.Flag
private val _badgeCount = MutableStateFlow<Int?>(null)
override val badgeCount: StateFlow<Int?> = _badgeCount
override fun onAttach(context: PluginContext) {
// Initialize your plugin (observe context.scope, context.eventBus, etc.)
}
@Composable
override fun Content(modifier: Modifier) {
// Your Compose UI here
LazyColumn(modifier = modifier) {
items(flags) { flag ->
FlagRow(flag)
}
}
}
}
// Install it
AELog.init(LogPlugin(), FeatureFlagsPlugin())📖 See the Custom Plugins Guide for the full API reference.
AELog works with any logging setup — just forward your log calls to the AELog API:
// Forward any log call directly — no bridge library needed
AELog.log(
severity = LogSeverity.INFO,
tag = "MyTag",
message = "Something happened",
throwable = null, // stack trace is appended automatically when present
)📖 See the Logging Integrations Guide for adapter examples (Kermit, Napier, Timber, SLF4J).
The SDK follows an encapsulated Model-Store-API-UI pattern, making plugins 100% reactive, modular, and thread-safe.
graph TD
subgraph UI ["UI Layer"]
LP["LogProvider\n(Compose wrapper)"]
end
subgraph Core ["Core — ae-log-core"]
AE["AELog\n(singleton engine)"]
EB["EventBus"]
end
subgraph Plugins ["Plugins (optional, loaded on demand)"]
direction LR
LP1["LogPlugin\nae-log-logs"]
NP["NetworkPlugin\nae-log-network"]
AP["AnalyticsPlugin\nae-log-analytics"]
end
subgraph Storage ["Data Layer (StateFlow — thread-safe)"]
direction LR
LS[("LogStorage")]
NS[("NetworkStorage")]
AS[("AnalyticsStorage")]
end
subgraph Interceptors ["Auto-interceptors (optional)"]
direction LR
KI["KtorInterceptor\nae-log-network-ktor"]
OI["OkHttpInterceptor\nae-log-network-okhttp"]
end
LP --> AE
AE --> EB
AE --> LP1
AE --> NP
AE --> AP
LP1 --> LS
NP --> NS
AP --> AS
KI --> NP
OI --> NP| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0) |
| iOS | 15.0 |
| Kotlin | 2.2.0+ |
| Compose Multiplatform | 1.7.3+ |
Contributions are welcome! Please read the Contributing Guide first.
git clone https://github.com/abdo-essam/AELog.git
cd AELog
./gradlew build
./gradlew allTestsCopyright 2026 Abdo Essam
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Extensible on-device dev tools for Kotlin Multiplatform
An in-app debugging overlay for KMP — inspect logs, network traffic, and analytics events with a beautiful Compose UI. No external tools needed.
Features • Installation • Quick Start • Plugins • Custom Plugins • Documentation
| Feature | Description |
|---|---|
| 🔍 Log Inspector | Search, filter, and copy logs with syntax-highlighted JSON |
| 🌐 Network Viewer | HTTP request/response inspection with method badges |
| 📊 Analytics Tracker | Monitor analytics events in real-time |
| 🎨 Beautiful UI | Material3 design with light/dark mode support |
| 🧩 Plugin System | Extend with custom debug panels through modular dependencies |
| 📱 Adaptive Layout | Bottom sheet on phones, dialog on tablets |
| 🔌 Zero Release Overhead | Disable with a single flag — no runtime cost |
| 🍎 Multiplatform | Android, iOS, Desktop (JVM), Web (WASM) |
AELog is fully modularized. You only add the artifact for the feature you need.
Every plugin module carries its dependencies transitively, so you never need to add
ae-log-core or intermediate modules manually.
Pick your scenario below — copy only that block.
// build.gradle.kts
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-logs:1.0.5")
// ↳ transitively includes ae-log-core
}// build.gradle.kts
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-network-ktor:1.0.5")
// ↳ transitively includes ae-log-network and ae-log-core
}// build.gradle.kts
androidMain.dependencies {
implementation("io.github.abdo-essam:ae-log-network-okhttp:1.0.5")
// ↳ transitively includes ae-log-network and ae-log-core
}// build.gradle.kts
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-analytics:1.0.5")
// ↳ transitively includes ae-log-core
}For a KMP project with Ktor on all platforms and OkHttp on Android:
// build.gradle.kts (shared module)
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.abdo-essam:ae-log-logs:1.0.5")
implementation("io.github.abdo-essam:ae-log-network-ktor:1.0.5")
implementation("io.github.abdo-essam:ae-log-analytics:1.0.5")
}
androidMain.dependencies {
// Add this only if your Android target also uses OkHttp
implementation("io.github.abdo-essam:ae-log-network-okhttp:1.0.5")
}
}
}| Artifact | Transitively includes | Platform |
|---|---|---|
ae-log-logs |
ae-log-core |
KMP |
ae-log-network |
ae-log-core |
KMP |
ae-log-network-ktor |
ae-log-network, ae-log-core
|
KMP |
ae-log-network-okhttp |
ae-log-network, ae-log-core
|
Android |
ae-log-analytics |
ae-log-core |
KMP |
[versions]
aelog = "1.0.5"
[libraries]
aelog-logs = { module = "io.github.abdo-essam:ae-log-logs", version.ref = "aelog" }
aelog-network-ktor = { module = "io.github.abdo-essam:ae-log-network-ktor", version.ref = "aelog" }
aelog-network-okhttp = { module = "io.github.abdo-essam:ae-log-network-okhttp", version.ref = "aelog" }
aelog-analytics = { module = "io.github.abdo-essam:ae-log-analytics", version.ref = "aelog" }Best called early in your platform-specific entry points (e.g. Application.onCreate for Android, or main ViewController for iOS):
AELog.init(
LogPlugin(), // Built-in log viewer
NetworkPlugin(), // Network inspector
AnalyticsPlugin() // Analytics tracker
)Wrap your main content:
@Composable
fun App(debugMode: Boolean) {
LogProvider(
enabled = debugMode, // ← disables UI overhead in release builds
uiConfig = UiConfig(
showFloatingButton = true, // Enables the 'bug' overlay button
enableLongPress = true, // Show panel on 3-finger long press
)
) {
MaterialTheme {
YourAppContent()
}
}
}No need to add Compose to your app! Just launch the built-in activity anywhere (like a developer menu button):
import com.ae.log.launchViewer
import com.ae.log.AELog
// Call from any Activity or Fragment
AELog.launchViewer(requireContext()) AELog is a discoverable object modelled after Android's built-in Log class.
Just type AELog. and the IDE lists every method — no extension hunting required:
AELog.log.v("Auth", "Token checked")
AELog.log.d("Auth", "Token refreshed")
AELog.log.i("HomeScreen", "App launched!")
AELog.log.w("Auth", "Session expiring soon")
AELog.log.e("Database", "Failed to clear cache", exception) // stack trace auto-appended
AELog.log.wtf("Auth", "Unexpected state")All calls are silent no-ops if
AELog.init()has not been called yet — safe in shared modules that run before app startup.
Omit the tag and AELog derives it from the caller's class name automatically. No repetition, no overhead:
AELog.log.d("Token refreshed") // tag → "AuthViewModel"
AELog.log.i("App launched!") // tag → "HomeScreen"
AELog.log.e("Failed to clear cache", t) // tag → "Database"// Network & Analytics APIs
AELog.network.logRequest(method = "GET", url = "https://api.example.com/users")
AELog.network.logResponse(url = "https://api.example.com/users", statusCode = 200)
AELog.analytics.logEvent("item_added_to_cart", properties = mapOf("id" to "123"))AELog provides first-class interceptors for OkHttp and Ktor.
Both interceptors are secure by default. They automatically redact sensitive headers like Authorization and Cookie to prevent token leakage in logs.
// OkHttp
val interceptor = OkHttpInterceptor(
redactHeaders = setOf("X-Sensitive-Header") // Extends default redactions
)
// Ktor
val client = HttpClient {
install(KtorInterceptor) {
redactHeaders = setOf("X-Api-Key")
}
}To prevent memory issues when inspecting large payloads (e.g., file uploads), bodies are automatically truncated (default 250 KB).
OkHttpInterceptor(
maxRequestBodyBytes = 500_000, // 500 KB limit
maxResponseBodyBytes = 1_000_000 // 1 MB limit
)By default, Ktor response streams can only be read once. To enable non-destructive inspection of response bodies:
DoubleReceive plugin in your HttpClient.bodyAsText(). If DoubleReceive is not installed, this may consume the stream—ensure your app logic is compatible or use the recommended plugin.val client = HttpClient {
install(DoubleReceive) // Recommended for Network Plugin
install(KtorInterceptor)
}Three ways to open the inspector:
LocalLogController.current.show()
| Module / Plugin | Class | Description |
|---|---|---|
:ae-log-core |
- | Infrastructure, EventBus, and UI Shell |
:ae-log-logs |
LogPlugin |
Log viewer with severity filters (ALL / VERBOSE / DEBUG / INFO / WARN / ERROR) |
:ae-log-network |
NetworkPlugin |
HTTP inspector with method badges, status filtering (2xx / 4xx / 5xx) and full body view |
:ae-log-analytics |
AnalyticsPlugin |
Analytics tracker separating Screens / Events with expandable properties |
Create your own debug panel (e.g., a Database Inspector or Feature Flags toggler) in 3 steps:
class FeatureFlagsPlugin : UIPlugin {
override val id = "feature_flags"
override val name = "Flags"
override val icon = Icons.Default.Flag
private val _badgeCount = MutableStateFlow<Int?>(null)
override val badgeCount: StateFlow<Int?> = _badgeCount
override fun onAttach(context: PluginContext) {
// Initialize your plugin (observe context.scope, context.eventBus, etc.)
}
@Composable
override fun Content(modifier: Modifier) {
// Your Compose UI here
LazyColumn(modifier = modifier) {
items(flags) { flag ->
FlagRow(flag)
}
}
}
}
// Install it
AELog.init(LogPlugin(), FeatureFlagsPlugin())📖 See the Custom Plugins Guide for the full API reference.
AELog works with any logging setup — just forward your log calls to the AELog API:
// Forward any log call directly — no bridge library needed
AELog.log(
severity = LogSeverity.INFO,
tag = "MyTag",
message = "Something happened",
throwable = null, // stack trace is appended automatically when present
)📖 See the Logging Integrations Guide for adapter examples (Kermit, Napier, Timber, SLF4J).
The SDK follows an encapsulated Model-Store-API-UI pattern, making plugins 100% reactive, modular, and thread-safe.
graph TD
subgraph UI ["UI Layer"]
LP["LogProvider\n(Compose wrapper)"]
end
subgraph Core ["Core — ae-log-core"]
AE["AELog\n(singleton engine)"]
EB["EventBus"]
end
subgraph Plugins ["Plugins (optional, loaded on demand)"]
direction LR
LP1["LogPlugin\nae-log-logs"]
NP["NetworkPlugin\nae-log-network"]
AP["AnalyticsPlugin\nae-log-analytics"]
end
subgraph Storage ["Data Layer (StateFlow — thread-safe)"]
direction LR
LS[("LogStorage")]
NS[("NetworkStorage")]
AS[("AnalyticsStorage")]
end
subgraph Interceptors ["Auto-interceptors (optional)"]
direction LR
KI["KtorInterceptor\nae-log-network-ktor"]
OI["OkHttpInterceptor\nae-log-network-okhttp"]
end
LP --> AE
AE --> EB
AE --> LP1
AE --> NP
AE --> AP
LP1 --> LS
NP --> NS
AP --> AS
KI --> NP
OI --> NP| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0) |
| iOS | 15.0 |
| Kotlin | 2.2.0+ |
| Compose Multiplatform | 1.7.3+ |
Contributions are welcome! Please read the Contributing Guide first.
git clone https://github.com/abdo-essam/AELog.git
cd AELog
./gradlew build
./gradlew allTestsCopyright 2026 Abdo Essam
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0