
Network inspector for Ktor Client enabling opt-in request and response body capture, SQLDelight persistence, header and JSON key redaction, safe response interception, and optional in-app UI.
NetLens is a Kotlin Multiplatform, opt-in network inspector for Ktor Client.
It lets you see what your app is actually sending and receiving — on Android and iOS — without leaving your app.
Think Chucker, but for Ktor + KMP.
.body<T>())NetLens is published as multiple artifacts so you only include what you need:
| Module | Purpose |
|---|---|
netlens-core |
Models, config, redaction, repository interface |
netlens-ktor |
Ktor client plugin + opt-in capture |
netlens-db |
SQLDelight persistence (Android + iOS) |
netlens-ui |
Optional Compose Multiplatform UI |
commonMain.dependencies {
implementation("io.github.myapplabs.netlens:netlens-core:0.1.0")
implementation("io.github.myapplabs.netlens:netlens-ktor:0.1.0")
implementation("io.github.myapplabs.netlens:netlens-db:0.1.0")
// Optional UI
implementation("io.github.myapplabs.netlens:netlens-ui:0.1.0")
}
Android
val repo = SqlDelightNetLensRepository(
SqlDriverFactory()
)iOS
val repo = SqlDelightNetLensRepository(
SqlDriverFactory()
)val netLens = NetLens(
config = NetLensConfig(
enabled = BuildConfig.DEBUG // strongly recommended
),
repo = repo,
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
)
val client = HttpClient {
install(NetLensPlugin) {
this.netLens = netLens
}
}What gets logged right away
Request and response bodies are captured only when explicitly enabled per request
client.post("https://api.example.com/login") {
enableNetLensBodyCapture()
contentType(ContentType.Application.Json)
setBody("""{"email":"a@b.com","password":"secret"}""")
}Why opt-in?
Bodies are still:
maxBodyBytes)If you include netlens-ui, you can show an in-app inspector
@Composable
fun NetLensRoot(repo: NetLensRepository) {
var selectedId by remember { mutableStateOf<String?>(null) }
if (selectedId == null) {
NetLensListScreen(repo) { selectedId = it }
} else {
NetLensDetailScreen(repo, selectedId!!) {
selectedId = null
}
}
}Recommended usage:
NetLensConfig(
enabled = true,
maxEntries = 500,
maxBodyBytes = 64_000,
captureResponseBodyWhenRequestOptedIn = true,
contentTypeAllowList = setOf(
"application/json",
"text/plain"
)
)| Option | Description |
|---|---|
enabled |
Turns NetLens on/off entirely |
maxEntries |
Oldest entries are pruned |
maxBodyBytes |
Body truncation limit |
contentTypeAllowList |
Only these bodies are logged |
captureResponseBodyWhenRequestOptedIn |
Disable to never capture responses |
Headers
AuthorizationCookieSet-CookieJSON keys
passwordtokenrefreshTokenaccessTokensecretapiKeyDevelopers can supply their own redactor through NetLensConfig.redactor to override header or body sanitization logic.
NetLensConfig(
redactor = object : NetLensRedactor {
override fun redactHeaders(headers: Map<String, List<String>>): Map<String, List<String>> = headers
override fun redactBody(contentType: String?, body: String): String = body
}
)Android
enabled = BuildConfig.DEBUGiOS User you own flag or configuration
| Good for | Not for |
|---|---|
| 🟢 Debugging API responses | 🔴 Production analytics |
| 🟢 Verifying serialization / headers | 🔴 Long-term sensitive logging |
| 🟢 Debugging KMP networking issues |
Apache-2.0
Issues and PRs are welcome.
NetLens is designed to be safe, explicit, and developer-friendly — contributions should follow the same philosophy.
NetLens is a Kotlin Multiplatform, opt-in network inspector for Ktor Client.
It lets you see what your app is actually sending and receiving — on Android and iOS — without leaving your app.
Think Chucker, but for Ktor + KMP.
.body<T>())NetLens is published as multiple artifacts so you only include what you need:
| Module | Purpose |
|---|---|
netlens-core |
Models, config, redaction, repository interface |
netlens-ktor |
Ktor client plugin + opt-in capture |
netlens-db |
SQLDelight persistence (Android + iOS) |
netlens-ui |
Optional Compose Multiplatform UI |
commonMain.dependencies {
implementation("io.github.myapplabs.netlens:netlens-core:0.1.0")
implementation("io.github.myapplabs.netlens:netlens-ktor:0.1.0")
implementation("io.github.myapplabs.netlens:netlens-db:0.1.0")
// Optional UI
implementation("io.github.myapplabs.netlens:netlens-ui:0.1.0")
}
Android
val repo = SqlDelightNetLensRepository(
SqlDriverFactory()
)iOS
val repo = SqlDelightNetLensRepository(
SqlDriverFactory()
)val netLens = NetLens(
config = NetLensConfig(
enabled = BuildConfig.DEBUG // strongly recommended
),
repo = repo,
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
)
val client = HttpClient {
install(NetLensPlugin) {
this.netLens = netLens
}
}What gets logged right away
Request and response bodies are captured only when explicitly enabled per request
client.post("https://api.example.com/login") {
enableNetLensBodyCapture()
contentType(ContentType.Application.Json)
setBody("""{"email":"a@b.com","password":"secret"}""")
}Why opt-in?
Bodies are still:
maxBodyBytes)If you include netlens-ui, you can show an in-app inspector
@Composable
fun NetLensRoot(repo: NetLensRepository) {
var selectedId by remember { mutableStateOf<String?>(null) }
if (selectedId == null) {
NetLensListScreen(repo) { selectedId = it }
} else {
NetLensDetailScreen(repo, selectedId!!) {
selectedId = null
}
}
}Recommended usage:
NetLensConfig(
enabled = true,
maxEntries = 500,
maxBodyBytes = 64_000,
captureResponseBodyWhenRequestOptedIn = true,
contentTypeAllowList = setOf(
"application/json",
"text/plain"
)
)| Option | Description |
|---|---|
enabled |
Turns NetLens on/off entirely |
maxEntries |
Oldest entries are pruned |
maxBodyBytes |
Body truncation limit |
contentTypeAllowList |
Only these bodies are logged |
captureResponseBodyWhenRequestOptedIn |
Disable to never capture responses |
Headers
AuthorizationCookieSet-CookieJSON keys
passwordtokenrefreshTokenaccessTokensecretapiKeyDevelopers can supply their own redactor through NetLensConfig.redactor to override header or body sanitization logic.
NetLensConfig(
redactor = object : NetLensRedactor {
override fun redactHeaders(headers: Map<String, List<String>>): Map<String, List<String>> = headers
override fun redactBody(contentType: String?, body: String): String = body
}
)Android
enabled = BuildConfig.DEBUGiOS User you own flag or configuration
| Good for | Not for |
|---|---|
| 🟢 Debugging API responses | 🔴 Production analytics |
| 🟢 Verifying serialization / headers | 🔴 Long-term sensitive logging |
| 🟢 Debugging KMP networking issues |
Apache-2.0
Issues and PRs are welcome.
NetLens is designed to be safe, explicit, and developer-friendly — contributions should follow the same philosophy.