
Intercept and display network requests/responses in real time with an expandable, syntax-highlighted JSON viewer, global search, advanced filters, cURL export, shake/floating shortcut, secure redaction and configurable auto-reset.
A network logging library for Android and iOS, built with Kotlin Multiplatform, Compose Multiplatform, Room, and Clean Architecture.
netlogger/src/commonMain: shared models, use cases, Room schema/DAO/repository, redaction, Ktor capture, Compose screens and ViewModels.netlogger/src/androidMain: Android initialization, Koin integration, OkHttp interceptor, SharedPreferences, activity, shake/FAB and clipboard/console adapters.netlogger/src/iosMain: Darwin integration, Room database builder, NSUserDefaults settings, UIKit controllers and clipboard/console adapters.app: Android sample application.iosApp/NetloggerSample.xcodeproj: iOS sample application hosting shared Compose UI.commonTest, androidHostTest, iosTest: shared capture/redaction tests, Android compatibility tests, and native Room storage tests.Requirements: JDK 21, Android SDK 36 and 36.1, macOS with full Xcode and an iOS Simulator runtime. The iOS minimum deployment target is 16.0; Android minSdk is 24. Native targets are iosArm64 (devices) and iosSimulatorArm64 (Apple Silicon simulators).
# Android APKs and host tests
./gradlew :app:assembleDebug :app:assembleRelease :netlogger:testAndroidHostTest
# iOS frameworks and native tests
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
./gradlew :netlogger:linkDebugFrameworkIosArm64 :netlogger:linkDebugFrameworkIosSimulatorArm64 :netlogger:iosSimulatorArm64Test
# Both platforms, including Debug/Release iOS sample builds
./scripts/verify-builds.sh
# On machines with limited disk space, remove framework intermediates after each build
NETLOGGER_CLEAN_FRAMEWORK_OUTPUTS=1 ./scripts/verify-builds.shOpen iosApp/NetloggerSample.xcodeproj in Xcode and select the NetloggerSample scheme. Its build phase runs embedAndSignAppleFrameworkForXcode; Gradle chooses the device/simulator and Debug/Release framework from Xcode's environment. For running on a physical device, set your development team in Xcode. The verification script compiles device apps without signing and does not produce a distribution archive. The host Info.plist must set CADisableMinimumFrameDurationOnPhone to true for Compose iOS; the sample includes this required setting.
This migration is version 1.5.0 in the source tree; it has not been published. Consume the local module with implementation(project(":netlogger")) in a KMP source set, or debugImplementation(project(":netlogger")) in an Android host. Maven Central publishing is configured for io.github.koai-dev:netlogger:1.5.0, including all Android/iOS publications and signing. See Maven Central setup and release commands for namespace verification, credentials, local staging and the manual GitHub Actions workflow. publishToMavenLocal also requires a signing key for release versions. Older JitPack releases are Android-only.
Initialize on the main thread before creating the client or viewer:
// iOS Kotlin host
Netlogger.init(NetloggerConfig(
maximumLogLevel = LogLevel.ALL,
captureBodies = true,
captureGeneralLogs = true
))
val client = HttpClient(Darwin) {
Netlogger.configureClient(this)
}
val controller = NetloggerViewController(onClose = { /* dismiss from host */ })On Android, call Netlogger.init(application, config) before Netlogger.configureClient(this) in a Ktor client. The existing Netlogger.getInterceptor() API remains available for OkHttp. Install one capture adapter per client to avoid duplicate entries.
For a common Kotlin host that owns its own repositories, the com.netlogger.lib.network.netlogger extension accepts the configuration, SettingsRepository, and INetloggerRepository directly. The application retains ownership of its HTTP client and should close it normally.
Ktor captures bounded TextContent request bodies and known-length textual response bodies when body capture is approved. Binary, compressed, unknown-length and oversized response bodies, and streaming/upload request bodies, are omitted. The original response bytes remain available to the caller; captured values are redacted before entering the bounded queue. API logging and body capture always respect both configured caps and current settings.
The shared viewer supports list/detail, JSON expansion, search, filters, tab ordering, settings and cURL export on both platforms. Shake/FAB shortcuts and screenshot blocking through secureWindow are Android-specific; iOS settings hide those shortcuts. iOS has no equivalent to Android FLAG_SECURE, so secureWindow does not prevent iOS screenshots. iOS copies use a local-only pasteboard entry that expires after two minutes. PERSISTENT_NO_BACKUP uses an Application Support directory excluded from iCloud backup on iOS; memory storage is the default on both platforms.
In your Application class, initialize Netlogger:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Netlogger.init(
this,
NetloggerConfig(
maximumLogLevel = LogLevel.ALL,
captureBodies = true,
captureGeneralLogs = true,
enableLogcatOutput = true,
allowShakeDetector = true,
allowFloatingButton = true,
tagTabs = listOf("AuthModule", "Database"),
storage = NetloggerStorage.MEMORY_ONLY,
maxBodyBytes = 256 * 1024L,
maxLogEntries = 500,
retentionMillis = 24 * 60 * 60 * 1000L
)
)
}
}Attach the Netlogger interceptor to your OkHttpClient:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(Netlogger.getInterceptor())
.build()There are three ways to open the Netlogger UI:
NetloggerConfig or enable it in Settings.NetloggerConfig or enable it in Settings.val intent = Intent(context, NetloggerActivity::class.java)
startActivity(intent)You can also manually log general messages, debug info, or errors using LogUtil:
LogUtil.debug("TAG", "Your message here")
LogUtil.error("TAG", "Something went wrong")
LogUtil.info("TAG", "Informational message")These logs will appear in the "General" filter category in the log list. Tags listed in
NetloggerConfig.tagTabs also appear as quick-filter tabs and show matching General logs.
Long-press and drag any quick-filter tab to reorder it; the order is restored on the next launch.
To ensure Netlogger has zero code footprint in your production APK, use a scoped dependency
such as debugImplementation together with Android source sets. A runtime BuildConfig.DEBUG
check alone does not remove the library, manifest entries, or transitive dependencies from an APK.
You will create two files with the exact same package and name in different source sets.
object NetloggerProxy {
fun init(application: Application) {
Netlogger.init(
application,
NetloggerConfig(
maximumLogLevel = LogLevel.ALL,
captureBodies = true,
captureGeneralLogs = true,
enableLogcatOutput = true
)
)
}
fun getInterceptor(): Interceptor {
return Netlogger.getInterceptor()
}
}object NetloggerProxy {
fun init(application: Application) {
// No-op in release
}
fun getInterceptor(): Interceptor? {
return null // Return null or a dummy interceptor
}
}Now your main application code remains clean and doesn't need to check for BuildConfig.DEBUG:
// In Application.onCreate
NetloggerProxy.init(this)
// In your OkHttp configuration
val builder = OkHttpClient.Builder()
NetloggerProxy.getInterceptor()?.let {
builder.addInterceptor(it)
}Calling Netlogger.init(application) without a configuration is safe by default:
NONE.When captureBodies is enabled, Netlogger still skips one-shot, duplex, binary, unknown-length
and oversized bodies. Add organization-specific names through additionalRedactedHeaders,
additionalRedactedQueryParameters, and additionalRedactedBodyFields.
maximumLogLevel, allowShakeDetector, and allowFloatingButton are environment-level caps.
Values restored from Settings can lower these capabilities but cannot exceed what the integrating
app approved in NetloggerConfig.
enableLogcatOutput = true is intended only for explicitly approved beta/staging source sets.
Console output uses the same bounded capture as the UI and is redacted again immediately before
calling Logcat. It does not bypass maximumLogLevel or captureBodies; credentials, cookies,
tokens, passwords, common PII and configured custom fields remain redacted.
Netlogger owns a namespaced Koin module for its database, repositories, use cases, interceptor,
manager and ViewModels. Netlogger.init(...) is synchronized and safely coexists with the host:
startKoin, Netlogger only loads its module into that container.Context/Application definitions.If the host application owns additional Koin modules, start the host Koin container before calling
Netlogger.init(...). Otherwise, add later host modules with loadKoinModules(...) instead of
calling startKoin a second time.
NetloggerStorage.PERSISTENT_NO_BACKUP is an explicit opt-in. Its database is placed under the
host application's no-backup directory and is pruned by both age and entry count. MEMORY_ONLY
remains recommended.
You can also customize runtime behavior in the Settings screen:
NetloggerConfig.Copyright 2024 Koai Dev. Licensed under the Apache License, Version 2.0.
A network logging library for Android and iOS, built with Kotlin Multiplatform, Compose Multiplatform, Room, and Clean Architecture.
netlogger/src/commonMain: shared models, use cases, Room schema/DAO/repository, redaction, Ktor capture, Compose screens and ViewModels.netlogger/src/androidMain: Android initialization, Koin integration, OkHttp interceptor, SharedPreferences, activity, shake/FAB and clipboard/console adapters.netlogger/src/iosMain: Darwin integration, Room database builder, NSUserDefaults settings, UIKit controllers and clipboard/console adapters.app: Android sample application.iosApp/NetloggerSample.xcodeproj: iOS sample application hosting shared Compose UI.commonTest, androidHostTest, iosTest: shared capture/redaction tests, Android compatibility tests, and native Room storage tests.Requirements: JDK 21, Android SDK 36 and 36.1, macOS with full Xcode and an iOS Simulator runtime. The iOS minimum deployment target is 16.0; Android minSdk is 24. Native targets are iosArm64 (devices) and iosSimulatorArm64 (Apple Silicon simulators).
# Android APKs and host tests
./gradlew :app:assembleDebug :app:assembleRelease :netlogger:testAndroidHostTest
# iOS frameworks and native tests
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
./gradlew :netlogger:linkDebugFrameworkIosArm64 :netlogger:linkDebugFrameworkIosSimulatorArm64 :netlogger:iosSimulatorArm64Test
# Both platforms, including Debug/Release iOS sample builds
./scripts/verify-builds.sh
# On machines with limited disk space, remove framework intermediates after each build
NETLOGGER_CLEAN_FRAMEWORK_OUTPUTS=1 ./scripts/verify-builds.shOpen iosApp/NetloggerSample.xcodeproj in Xcode and select the NetloggerSample scheme. Its build phase runs embedAndSignAppleFrameworkForXcode; Gradle chooses the device/simulator and Debug/Release framework from Xcode's environment. For running on a physical device, set your development team in Xcode. The verification script compiles device apps without signing and does not produce a distribution archive. The host Info.plist must set CADisableMinimumFrameDurationOnPhone to true for Compose iOS; the sample includes this required setting.
This migration is version 1.5.0 in the source tree; it has not been published. Consume the local module with implementation(project(":netlogger")) in a KMP source set, or debugImplementation(project(":netlogger")) in an Android host. Maven Central publishing is configured for io.github.koai-dev:netlogger:1.5.0, including all Android/iOS publications and signing. See Maven Central setup and release commands for namespace verification, credentials, local staging and the manual GitHub Actions workflow. publishToMavenLocal also requires a signing key for release versions. Older JitPack releases are Android-only.
Initialize on the main thread before creating the client or viewer:
// iOS Kotlin host
Netlogger.init(NetloggerConfig(
maximumLogLevel = LogLevel.ALL,
captureBodies = true,
captureGeneralLogs = true
))
val client = HttpClient(Darwin) {
Netlogger.configureClient(this)
}
val controller = NetloggerViewController(onClose = { /* dismiss from host */ })On Android, call Netlogger.init(application, config) before Netlogger.configureClient(this) in a Ktor client. The existing Netlogger.getInterceptor() API remains available for OkHttp. Install one capture adapter per client to avoid duplicate entries.
For a common Kotlin host that owns its own repositories, the com.netlogger.lib.network.netlogger extension accepts the configuration, SettingsRepository, and INetloggerRepository directly. The application retains ownership of its HTTP client and should close it normally.
Ktor captures bounded TextContent request bodies and known-length textual response bodies when body capture is approved. Binary, compressed, unknown-length and oversized response bodies, and streaming/upload request bodies, are omitted. The original response bytes remain available to the caller; captured values are redacted before entering the bounded queue. API logging and body capture always respect both configured caps and current settings.
The shared viewer supports list/detail, JSON expansion, search, filters, tab ordering, settings and cURL export on both platforms. Shake/FAB shortcuts and screenshot blocking through secureWindow are Android-specific; iOS settings hide those shortcuts. iOS has no equivalent to Android FLAG_SECURE, so secureWindow does not prevent iOS screenshots. iOS copies use a local-only pasteboard entry that expires after two minutes. PERSISTENT_NO_BACKUP uses an Application Support directory excluded from iCloud backup on iOS; memory storage is the default on both platforms.
In your Application class, initialize Netlogger:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Netlogger.init(
this,
NetloggerConfig(
maximumLogLevel = LogLevel.ALL,
captureBodies = true,
captureGeneralLogs = true,
enableLogcatOutput = true,
allowShakeDetector = true,
allowFloatingButton = true,
tagTabs = listOf("AuthModule", "Database"),
storage = NetloggerStorage.MEMORY_ONLY,
maxBodyBytes = 256 * 1024L,
maxLogEntries = 500,
retentionMillis = 24 * 60 * 60 * 1000L
)
)
}
}Attach the Netlogger interceptor to your OkHttpClient:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(Netlogger.getInterceptor())
.build()There are three ways to open the Netlogger UI:
NetloggerConfig or enable it in Settings.NetloggerConfig or enable it in Settings.val intent = Intent(context, NetloggerActivity::class.java)
startActivity(intent)You can also manually log general messages, debug info, or errors using LogUtil:
LogUtil.debug("TAG", "Your message here")
LogUtil.error("TAG", "Something went wrong")
LogUtil.info("TAG", "Informational message")These logs will appear in the "General" filter category in the log list. Tags listed in
NetloggerConfig.tagTabs also appear as quick-filter tabs and show matching General logs.
Long-press and drag any quick-filter tab to reorder it; the order is restored on the next launch.
To ensure Netlogger has zero code footprint in your production APK, use a scoped dependency
such as debugImplementation together with Android source sets. A runtime BuildConfig.DEBUG
check alone does not remove the library, manifest entries, or transitive dependencies from an APK.
You will create two files with the exact same package and name in different source sets.
object NetloggerProxy {
fun init(application: Application) {
Netlogger.init(
application,
NetloggerConfig(
maximumLogLevel = LogLevel.ALL,
captureBodies = true,
captureGeneralLogs = true,
enableLogcatOutput = true
)
)
}
fun getInterceptor(): Interceptor {
return Netlogger.getInterceptor()
}
}object NetloggerProxy {
fun init(application: Application) {
// No-op in release
}
fun getInterceptor(): Interceptor? {
return null // Return null or a dummy interceptor
}
}Now your main application code remains clean and doesn't need to check for BuildConfig.DEBUG:
// In Application.onCreate
NetloggerProxy.init(this)
// In your OkHttp configuration
val builder = OkHttpClient.Builder()
NetloggerProxy.getInterceptor()?.let {
builder.addInterceptor(it)
}Calling Netlogger.init(application) without a configuration is safe by default:
NONE.When captureBodies is enabled, Netlogger still skips one-shot, duplex, binary, unknown-length
and oversized bodies. Add organization-specific names through additionalRedactedHeaders,
additionalRedactedQueryParameters, and additionalRedactedBodyFields.
maximumLogLevel, allowShakeDetector, and allowFloatingButton are environment-level caps.
Values restored from Settings can lower these capabilities but cannot exceed what the integrating
app approved in NetloggerConfig.
enableLogcatOutput = true is intended only for explicitly approved beta/staging source sets.
Console output uses the same bounded capture as the UI and is redacted again immediately before
calling Logcat. It does not bypass maximumLogLevel or captureBodies; credentials, cookies,
tokens, passwords, common PII and configured custom fields remain redacted.
Netlogger owns a namespaced Koin module for its database, repositories, use cases, interceptor,
manager and ViewModels. Netlogger.init(...) is synchronized and safely coexists with the host:
startKoin, Netlogger only loads its module into that container.Context/Application definitions.If the host application owns additional Koin modules, start the host Koin container before calling
Netlogger.init(...). Otherwise, add later host modules with loadKoinModules(...) instead of
calling startKoin a second time.
NetloggerStorage.PERSISTENT_NO_BACKUP is an explicit opt-in. Its database is placed under the
host application's no-backup directory and is pruned by both age and entry count. MEMORY_ONLY
remains recommended.
You can also customize runtime behavior in the Settings screen:
NetloggerConfig.Copyright 2024 Koai Dev. Licensed under the Apache License, Version 2.0.