AELog

Extensible on-device dev tools for Kotlin Multiplatform.

AELog is a lightweight, plugin-based developer tools library for KMP apps. It provides a beautiful Compose UI overlay for inspecting logs, network traffic, and more — all without leaving the app.

What Is AELog?

The Big Picture

AELog is an in-app debugging overlay for Kotlin Multiplatform apps. Imagine you're developing a mobile app and you want to see, while the app is running:

  • All log messages your code emits
  • Every HTTP request your app makes
  • Every analytics event being tracked

Today, developers attach a laptop, hook up Logcat (Android) or Console.app (iOS), and read logs externally. That's painful — especially on iOS, or when testing on someone else's device.

AELog solves this by drawing a debug panel directly on top of your app's UI. Tap a floating bug button → a panel slides up showing tabs for Logs, Network, Analytics. No external tools needed.

The Core Idea: Plugins

Everything in AELog is a plugin. The library itself is just a host that:

  • Manages a list of installed plugins
  • Renders them as tabs in an overlay panel
  • Provides each plugin with a coroutine scope, an event bus, and a way to find sibling plugins

Three built-in plugins ship with the library:

Plugin Description
LogPlugin Captures text logs (like Logcat) with severity filtering
NetworkPlugin Captures HTTP traffic — requests, responses, and headers
AnalyticsPlugin Captures analytics events and screen views with custom properties
CrashPlugin Intercepts, records and persists fatal and non-fatal exceptions on device

You can write your own plugins for anything else (database inspector, feature flag toggler, etc.).

Features

  • Log Viewer — Real-time log inspection with severity filtering
  • Network Inspector — Detailed view of HTTP traffic and JSON payloads
  • Analytics Tracker — Monitor properties across app flow
  • Crash Reporter — Capture and persist fatal and non-fatal exceptions on device
  • Plugin System — Easily extend with custom decoupled UIPlugin or headless Plugin
  • Compose UI — Material 3 themed overlay with dark/light mode support
  • Multiplatform — Works seamlessly across Android, iOS, and Desktop

Quick Links

Getting Started

Requirements

Platform Minimum
Android API 24 (7.0)
iOS 15.0
Kotlin 2.2.0
Compose Multiplatform 1.7.3

Installation

gradle/libs.versions.toml
[versions]
aelog = "1.1.7"

[libraries]
aelog-logs      = { module = "io.github.abdo-essam:ae-log-logs", version.ref = "aelog" }
aelog-network   = { module = "io.github.abdo-essam:ae-log-network", version.ref = "aelog" }
aelog-analytics = { module = "io.github.abdo-essam:ae-log-analytics", version.ref = "aelog" }
aelog-crashes   = { module = "io.github.abdo-essam:ae-log-crashes", version.ref = "aelog" }
shared/build.gradle.kts
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.aelog.logs)
            implementation(libs.aelog.network)
            implementation(libs.aelog.analytics)
            implementation(libs.aelog.crashes)
        }
    }
}
Transitive Dependencies

AELog uses api dependencies to keep your build file clean.

  • If you use ae-log-network-ktor, it automatically includes ae-log-network and ae-log-core.
  • You do not need to manually add the core library if you are already using a plugin.

Verify Installation

import com.ae.log.AELog

fun main() {
    // Simply check if AELog is accessible
    AELog.isEnabled = true
    println("AELog is accessible and enabled: ${AELog.isEnabled}")
}
Expected Output
AELog is accessible and enabled: true

Next Steps

Quick Start

Get up and running in just a few seconds.

1. Zero-Config (Automatic Setup)

AELog features zero-config auto-initialisation on both Android and iOS. Just add the dependencies for the plugins you want, and they automatically register themselves. No initialization code is required in your application!

2. Custom Configuration (Optional)

To disable the floating trigger notch globally (across all screens), set the static property AELog.showNotch at your app entry point (e.g. Application.onCreate() on Android):

// Disable the floating notch globally (defaults to true)
AELog.showNotch = false

3. Drop in the Overlay

Add AELogOverlay() as a sibling anywhere in your root composable — no wrapping required:

@Composable
fun App() {
    AELogOverlay() // ← Renders floating center-right notch + inspector panel as a Popup above your UI
    MaterialTheme {
        YourAppContent()
    }
}

To disable in release builds: AELog.isEnabled = BuildConfig.DEBUG
To hide the floating notch trigger locally but still open the panel programmatically: AELogOverlay(showNotch = false) then AELog.show()

3. Start Logging

Use the discoverable static APIs. AELog handles everything else behind the scenes.

// 1. Logs (with automatic tag derivation from the calling class)
AELog.log.i("App launched!")
AELog.log.d("Auth", "Token refreshed") // Or specify an explicit tag

// 2. Network (handled automatically via interceptors)
// val client = HttpClient { install(AELogKtorInterceptor) }

// 3. Analytics
AELog.analytics.logEvent("button_tap")

// 4. Crashes (manually record non-fatal exceptions)
try {
    performDangerousTask()
} catch (t: Throwable) {
    AELog.crashes.recordNonFatal(t)
}

Show / Hide the Overlay

The overlay can be triggered by:

  • Floating Notch — the Dynamic Island-style pill at the top of the screen (shown by default)
  • Programmatically via AELog.show() / AELog.hide() from anywhere in your code
  • Custom triggers — wire AELog.show() to a shake gesture, debug settings, or any other event

Configuration

AELog is designed to be "zero-config" by default, but provides hooks to customize both data collection and UI behavior.

Core Configuration (Data & UI Globals)

You can toggle global features like the floating notch trigger or enable/disable the logger using properties directly on AELog:

// Disable the floating notch globally (across XML & Compose screens)
AELog.showNotch = false

// Enable or disable the entire library programmatically
AELog.isEnabled = BuildConfig.DEBUG

UI Configuration

Control whether the floating notch is shown, or hide it and trigger the overlay programmatically:

// Default — shows the vertical notch trigger on the right side of the screen
AELogOverlay()

// Hidden notch trigger — open programmatically from custom buttons, shake gestures, etc.
AELogOverlay(showNotch = false)
// Then from anywhere in your codebase:
AELog.show()
AELog.hide()

// Disable the entire library (e.g. in release builds)
AELog.isEnabled = BuildConfig.DEBUG

Plugins Overview

AELog uses a plugin architecture. Each plugin adds a dedicated tab to the AELog overlay panel. The library itself is just a host — plugins are what make it useful.

How Plugins Work

When you call AELog.install(...), the library:

  1. Installs each plugin and calls its onAttach() lifecycle hook
  2. Renders UIPlugin instances as tabs in the overlay panel
  3. Provides each plugin with access to a coroutine scope and sibling plugins via PluginContext

Built-in Plugins

Plugin Module Type Description
LogPlugin :ae-log-logs UIPlugin Real-time log viewer with level filtering (VERBOSE / DEBUG / INFO / WARN / ERROR)
NetworkPlugin :ae-log-network UIPlugin HTTP traffic inspector with method badges, status filtering, and full body view
AnalyticsPlugin :ae-log-analytics UIPlugin Tracks analytics events and screen views with expandable custom properties
CrashPlugin :ae-log-crashes UIPlugin Captures, records, and persists fatal crashes and non-fatal exceptions on device

Logs Inspector

AELog Logs Plugin UI

Browse console output with search capabilities and filter logs by severity level.

Network Inspector

AELog Network Plugin UI

Inspect Ktor and OkHttp HTTP request/response payloads, status codes, and headers.

Analytics Tracker

AELog Analytics Plugin UI

View analytics events, screen views, and user property parameters as they trigger in real-time.

Crash Reporter

AELog Crash Plugin UI

Review local fatal stack traces and persisted exception reports directly on the test device.

Plugin Types

UIPlugin

Renders a tab inside the AELog panel. Ideal for visually inspecting data (logs, network calls, feature flags, database records, etc.). UI plugins now own their entire layout inside the Content() composable (e.g. sticky search bars, custom headers, and scrollable lists).

Plugin (Headless)

A headless background collector with no UI. Simply implement the standard Plugin interface directly. Use it for crash collectors, performance samplers, background interceptors, or anything that needs to observe data or listen to event signals without a dedicated visual tab.

Installing Plugins

// Auto-registered on Android via ContentProvider — no code needed!
// But if you are on other platforms (like iOS) or adding custom plugins:

AELog.install(MyCustomPlugin())

Plugin Lifecycle

install() onAttach(context) Active State onDetach() uninstall()

Each hook has a clear responsibility:

Hook When Called Use For
onAttach(context) Once, on install Store PluginContext, launch background collection, subscribe to EventBus events
onClear() User taps "Clear All" Reset stored entries or memory states
onDetach() On uninstall or reset Cancel coroutines, release resources, teardown native callbacks

Creating Custom Plugins

Build Your Own Custom Tooling

AELog is fully extensible. Learn how to write custom UI modules and headless background plugins to tailor the debugger to your team's exact workflow.

Read Custom Plugins Guide

Creating Custom Plugins

AELog is designed to be extended. You can create two types of plugins:

Plugin Types

Type Interface Has UI Use Case
UI Plugin UIPlugin ✅ Tab + Content Log viewer, network inspector, DB browser
Headless Plugin Plugin ❌ Headless Crash collector, performance sampler, background worker

Creating a UI Plugin

Step 1: Implement UIPlugin

class FeatureFlagsPlugin : UIPlugin {
    // override val id = "feature_flags" // Optional: Defaults to qualified class name
    override val name = "Flags"
    override val icon: @Composable () -> Unit = { Icon(Icons.Default.Flag, null) }

    // Override badgeCount only when your plugin tracks a meaningful live count.
    // Omit it entirely if you don't need a badge — the default shows nothing.
    private val _badgeCount = MutableStateFlow(0)
    override val badgeCount: StateFlow<Int> = _badgeCount

    private var context: PluginContext? = null

    // Called once when installed
    override fun onAttach(context: PluginContext) {
        this.context = context
        _badgeCount.value = getFlags().size
    }

    // Called when user taps "Clear All"
    override fun onClear() {
        resetFlags()
        _badgeCount.value = 0
    }

    // Called when plugin is uninstalled
    override fun onDetach() {
        context = null
    }

    // Main content rendered in the AELog panel (owns entire layout!)
    @Composable
    override fun Content(modifier: Modifier) {
        val flags = remember { getFlags() }

        Column(modifier = modifier.fillMaxSize()) {
            // Sticky search/filter bar and action headers go directly inside Content()!
            Row(
                modifier = Modifier.fillMaxWidth().padding(8.dp),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text("Toggle feature flags for testing", style = MaterialTheme.typography.bodySmall)
                IconButton(onClick = { resetAllFlags() }) {
                    Icon(Icons.Default.Refresh, "Reset all")
                }
            }

            LazyColumn(modifier = Modifier.weight(1f)) {
                items(flags) { flag ->
                    FlagRow(
                        name = flag.name,
                        enabled = flag.enabled,
                        onToggle = { toggleFlag(flag.id) }
                    )
                }
            }
        }
    }
}

Step 2: Install the Plugin

// Add your custom plugin alongside the auto-registered built-ins
AELog.install(FeatureFlagsPlugin())

Step 3: Done!

Your plugin now appears as a tab in the AELog panel.

Creating a Headless Plugin

class PerformancePlugin : Plugin {
    // override val id = "performance" // Optional: Defaults to qualified class name
    override val name = "Performance"

    private val _metrics = MutableStateFlow<List<Metric>>(emptyList())
    val metrics: StateFlow<List<Metric>> = _metrics.asStateFlow()

    override fun onAttach(context: PluginContext) {
        startCollecting(context.scope)
    }

    override fun onDetach() {
        stopCollecting()
    }

    fun recordMetric(name: String, durationMs: Long) {
        _metrics.update { it + Metric(name, durationMs) }
    }
}

// Usage
val perfPlugin = AELog.getPlugin<PerformancePlugin>()
perfPlugin?.recordMetric("api_call", 250L)

Plugin Lifecycle

install() onAttach(context) Active State onDetach() uninstall()
⚠️ Important Lifecycle Notes
  • onAttach is called exactly once when the plugin is installed
  • Stale imperative lifecycle hooks (like onStart, onStop, onOpen, onClose) are removed to keep the API surface simple and focused (YAGNI).
  • Always safety-check resources in Content() — it may recompose at any time
  • Handle UI errors gracefully inside Content()

Best Practices

  • Keep plugins focused — one responsibility per plugin
  • Use StateFlow — all reactive data should use StateFlow
  • Minimize main-thread work — heavy computation goes in background coroutine scopes
  • Clean up in onDetach — cancel background tasks and release state
  • Test independently — plugins can be unit-tested fully in KMP commonMain

Logging Integrations

AELog works with any logging library. Just forward logs to the static AELog.log methods.

Kermit

import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Severity
import com.ae.log.AELog

class AELogKermitWriter : LogWriter() {
    override fun log(
        severity: Severity,
        message: String,
        tag: String,
        throwable: Throwable?
    ) {
        when (severity) {
            Severity.Verbose -> AELog.log.v(tag, message, throwable)
            Severity.Debug -> AELog.log.d(tag, message, throwable)
            Severity.Info -> AELog.log.i(tag, message, throwable)
            Severity.Warn -> AELog.log.w(tag, message, throwable)
            Severity.Error -> AELog.log.e(tag, message, throwable)
            Severity.Assert -> AELog.log.wtf(tag, message, throwable)
        }
    }
}

// Setup
Logger.addLogWriter(AELogKermitWriter())

Napier

import io.github.aakira.napier.Antilog
import io.github.aakira.napier.LogLevel
import com.ae.log.AELog

class AELogNapierAntilog : Antilog() {
    override fun performLog(
        priority: LogLevel,
        tag: String?,
        throwable: Throwable?,
        message: String?
    ) {
        val resolvedTag = tag ?: "Napier"
        val resolvedMsg = message ?: ""
        when (priority) {
            LogLevel.VERBOSE -> AELog.log.v(resolvedTag, resolvedMsg, throwable)
            LogLevel.DEBUG -> AELog.log.d(resolvedTag, resolvedMsg, throwable)
            LogLevel.INFO -> AELog.log.i(resolvedTag, resolvedMsg, throwable)
            LogLevel.WARNING -> AELog.log.w(resolvedTag, resolvedMsg, throwable)
            LogLevel.ERROR -> AELog.log.e(resolvedTag, resolvedMsg, throwable)
            LogLevel.ASSERT -> AELog.log.wtf(resolvedTag, resolvedMsg, throwable)
        }
    }
}

// Setup
Napier.base(AELogNapierAntilog())

Timber (Android)

import timber.log.Timber
import com.ae.log.AELog

class AELogTimberTree : Timber.Tree() {
    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
        val resolvedTag = tag ?: "Timber"
        when (priority) {
            android.util.Log.VERBOSE -> AELog.log.v(resolvedTag, message, t)
            android.util.Log.DEBUG -> AELog.log.d(resolvedTag, message, t)
            android.util.Log.INFO -> AELog.log.i(resolvedTag, message, t)
            android.util.Log.WARN -> AELog.log.w(resolvedTag, message, t)
            android.util.Log.ERROR -> AELog.log.e(resolvedTag, message, t)
            android.util.Log.ASSERT -> AELog.log.wtf(resolvedTag, message, t)
            else -> AELog.log.d(resolvedTag, message, t)
        }
    }
}

// Setup
Timber.plant(AELogTimberTree())

Ktor Client Logging

import io.ktor.client.*
import io.ktor.client.plugins.logging.*
import com.ae.log.AELog

val client = HttpClient {
    install(Logging) {
        logger = object : Logger {
            override fun log(message: String) {
                // Forward to AELog
                AELog.log.d("HTTP", message)
            }
        }
        level = LogLevel.ALL
    }
}

Direct Usage (No Library)

// Use the static shorthands
AELog.log.i("MyApp", "App started")
AELog.log.e("Auth", "Login failed", error)

Changelog

All notable changes and releases of AELog are detailed below in our interactive project roadmap.

v1.1.7 — June 8, 2026

Fixed

  • Ktor Interceptor Response Capture: Fixed Ktor interceptor in log-network-ktor to correctly capture response body payloads on error responses (like HTTP 401). By intercepting the response body in receivePipeline.State after Ktor's SaveBody plugin has buffered it, we ensure rawContent can be read as a fresh channel and the response is safely logged without exhausting the stream or throwing exceptions.
  • iOS Test Name Compatibility: Renamed backtick-quoted test names containing parentheses to prevent Kotlin/Native compilation failures on iOS simulator targets.

v1.1.6 — June 8, 2026

Added

  • Global Notch Toggle: Added static property AELog.showNotch to allow globally enabling/disabling the floating trigger notch across all screens.
  • Zero-Config iOS & Android Auto-Init: Synchronized documentation and setups to highlight zero-config auto-initialization on both Android and iOS targets.

Removed

  • AELog.configure & LogConfig: Removed the configure DSL block and LogConfig class, enforcing zero-config sensible defaults for all plugins.
  • onMigrateFrom Lifecycle Hook: Removed the obsolete onMigrateFrom(oldPlugin) hook from the Plugin interface and its overrides in built-in plugins (LogPlugin, NetworkPlugin, AnalyticsPlugin) due to the deprecation of dynamic configuration hot-swaps.

Fixed

  • iOS test isolation — duplicate plugin rejected silently: Fixed by calling AELog.resetForTesting() at the top of every @BeforeTest to evict any eagerly-registered plugins before the test installs its own.

v1.1.5 — June 3, 2026

Fixed

  • MaterialTheme Linkage Crash on iOS: Completely removed references to standard Compose MaterialTheme companion property accesses across core and plugins, replacing them with a custom static LogTheme. This prevents IrLinkageError crashes on iOS when linking AELog into applications using mismatched Compose Multiplatform versions.

v1.1.4 — June 2, 2026

Added

  • Zero-Config iOS Auto-Initialization: Added auto-initialization for all four core plugins on iOS using @EagerInitialization in KMP shared modules. iOS consumers now enjoy completely zero-code plugin setup just like Android!

Fixed

  • Ktor Interceptor Payloads: Fixed handling of raw String and ByteArray request bodies in Ktor client when ContentNegotiation is not installed.
  • Notch Label Clipping: Resolved notch button label clipping/invisibility on iOS by removing the static requiredWidth constraint and setting softWrap = false on the rotated Compose Text.

v1.1.1 — June 1, 2026

Added

  • Request Body on Error: Network logs now capture and display the request body early in the request pipeline, ensuring it remains visible even when requests fail with client/server errors or network exceptions.
  • Smart Binary Upload Handling: Binary uploads (like images/documents via multipart/form-data) are no longer dumped as raw binary strings into logs. AELog now displays a clean summary like <image/png: 110370 bytes>, preventing memory bloat and UI lag.

Changed

  • Premium Code Block Styles: Request and response body sections in the network details view now render with a solid, clean white background and black text in light theme for maximum readability.
  • Enhanced Contrast for Success Responses: Successful network entry detail sections are highlighted with a subtle green tint (Color(0xFF4CAF50).copy(alpha = 0.08f)) to distinguish them from the sheet background.

v1.1.0 — June 1, 2026

Added

  • DeviceInfo in crash reports: Crash events now capture a DeviceInfo snapshot at crash time (device model, OS version, app version, build number). When a QA tester taps Copy on any crash, the clipboard text now includes a ── Device Info ── section with full context — no need to ask the developer which build or device was affected.
  • DeviceInfo expect/actual implemented for Android (android.os.Build + PackageManager), iOS (UIDevice + NSBundle), and JVM (system properties fallback).

v1.0.9 — May 31, 2026

Removed

  • EventBus & lifecycle signals (YAGNI): The EventBus, Event, and AELogLifecycle classes have been deleted. None of the built-in plugins consumed these signals, so they were speculative plumbing that added unnecessary complexity. The collectEvents extension on PluginContext and the eventBus property are also gone.

Changed

  • AELog.clearAll() now delegates directly to PluginManager — the intermediate AELogLifecycle.clearAll() indirection is gone.
  • PluginContext is now simpler: only scope, config, and getPlugin() — no event infrastructure.

v1.0.8 — May 31, 2026

Fixed

  • Ktor request body not logged for JSON POST requests: When ContentNegotiation serializes a data class, Ktor produces a WriteChannelContent (stream). The interceptor now drains the stream into a byte array, logs the decoded JSON body, and re-wraps it as ByteArrayContent — so the actual HTTP request body is fully preserved.

Tests

  • Added records JSON request body test asserting that a setBody(...) POST with ContentType.Application.Json records the body content in the log export.
  • Extended records POST request with body assertions (name, test) to prevent regression.

v1.0.7 — May 29, 2026

Simplified

  • Unified Plugin Architecture: Consolidated the redundant DataPlugin marker interface and UIPlugin under a single, streamlined Plugin base contract.
  • Auto-Generated IDs: Added reflection-based qualified class name id defaults, eliminating manual ID registration boilerplate.
  • Consolidated UI layout: Removed legacy layout slots (HeaderContent() and HeaderActions()) from UIPlugin. Plugins now have 100% layout control (like sticky search/headers) directly inside Content().
  • YAGNI Lifecycle Cleanups: Purged unused imperative hooks (onStart(), onStop(), onOpen(), onClose()) from the plugin interface to keep the API surface simple and minimal.

v1.0.6 — May 28, 2026

Added

  • Unified Configuration DSL: Replaced legacy, fragmented initialization methods (configureCore, configurePlugin) with a robust, type-safe, and atomic AELog.configure { ... } builder DSL.
  • Global Notch Configuration: Added showNotch parameter to LogConfig and AELogBuilder to allow developers to completely disable the floating notch trigger globally during startup.
  • High-Fidelity Edge-Docked Notch: Redesigned the floating trigger notch from a top-center pill into a premium vertical button docked snug against the right edge of the screen.

Fixed

  • Overlay Layout Clipping: Resolved the 0x0 parent measurement constraint bug in AELogOverlay.kt by applying Modifier.fillMaxSize() with touch pass-through bounds.
  • Removed Obsolete APIs: Completely purged deprecated legacy configuration methods and old UI wrappers (such as LogProvider and UiConfig).

v1.0.5 — May 10, 2026

Added

  • stability-config.conf — targeted Compose compiler stability config.
  • NetworkMethod.UNKNOWN — safe fallback for custom/unknown HTTP verbs.
  • androidMain callerTag() — added Android-specific skip prefixes to stack walker.
  • Network UI: Single-scroll detail view, custom Status Badges showing HTTP status codes, and animated in-flight request row indicator.
  • UI Timestamps: Localized 12-hour AM/PM format display.
  • Interceptors: Excluded headers configurations completely hiding credentials/tokens.

Fixed

  • UI: Bottom sheet selected tab state persistence.
  • OkHttp: Captured request bodies with null Content-Type.
  • Critical: Removed LogEntry default parameter side-effects and Napler/Timber/Ktor pipeline duplicate error messages.

v1.0.4 — May 7, 2026

Added

  • Modular Subprojects: Completely reorganized and split the AELog project into independent sub-modules (e.g., :core, :log-logs, :log-network, :log-network-ktor, :log-network-okhttp, :log-analytics, :log-crashes).
  • Comprehensive Testing Suite: Added full test coverage for Ktor and OkHttp interceptors alongside automated stress and performance benchmark suites.

Changed

  • Standardized UI Naming: Renamed all UI components to standardized Log* prefixes (like LogNotchButton, LogInspectorPanel).
  • AELog Interceptor Prefixes: Relocated and standardized interceptor naming conventions using AELog prefixes (e.g., AELogKtorInterceptor, AELogOkHttpInterceptor).

v1.0.3 — May 5, 2026

Added

  • Pulsing Animation: Added a dynamic, pulsing status dot indicator for in-flight requests in the Network inspector list and detail view.
  • Interactive UI Components: Added reusable ActionButton and ActionCard layout components for plugin builders.

Changed

  • Decoupled Architecture: Extracted core functionality to simplify plugin registration and decouple core engine from plugin lifecycles.
  • Improved Security: Standardized header exclude management in network interceptors.

Fixed

  • API Robustness: Enhanced thread safety and memory leak prevention across the core event bus and database storage layers.

v1.0.2 — May 3, 2026

Fixed

  • Fixed lint errors in LogProvider and OkHttpInterceptor.
  • Resolved prefix naming consistency issues.

v1.0.1 — May 2, 2026

Added

  • Standardized IdGenerator across all modules.
  • Improved URL decoding for query parameters.

v1.0.0 — May 1, 2026

Added

  • 🎉 Initial production release.
  • Core AELog engine with modular plugin architecture.
  • Material3 themed UI with light/dark mode support and adaptive layout bounds.
  • Floating debug button trigger and interactive inspector panel.

Contributing to AELog

First off, thank you for considering contributing! 🎉

Table of Contents

Code of Conduct

This project follows our Code of Conduct. By participating, you agree to uphold this code.

Getting Started

Prerequisites

  • JDK 17+
  • Android Studio Ladybug or later / Fleet
  • Xcode 15+ (for iOS targets)
  • Kotlin 2.2.0+

Development Setup

# Clone the repository
git clone https://github.com/abdo-essam/AELog.git
cd AELog

# Build the project
./gradlew build

# Run tests
./gradlew allTests

# Run the sample app (Android)
./gradlew :sample:composeApp:installDebug

# Check code formatting
./gradlew spotlessCheck

# Fix code formatting
./gradlew spotlessApply

# Generate API docs
./gradlew dokkaGeneratePublicationHtml

# Verify binary compatibility
./gradlew apiCheck

How to Contribute

🐛 Reporting Bugs

  • Use the Bug Report template
  • Include: KMP version, platform, steps to reproduce, expected vs actual behavior

💡 Suggesting Features

  • Use the Feature Request template
  • Describe the use case, not just the solution

🔧 Submitting Changes

  • Fork the repository
  • Create a feature branch: git checkout -b feat/my-feature
  • Make your changes
  • Add/update tests
  • Run the full check: ./gradlew check
  • Commit using Conventional Commits
  • Push and open a Pull Request

Pull Request Process

  • Fill out the PR template completely
  • Ensure CI passes — all checks must be green
  • One approval required from a maintainer
  • Squash merge is preferred for clean history
  • Update CHANGELOG.md under [Unreleased]

Coding Standards

Kotlin Style

  • Follow Kotlin Coding Conventions
  • Use explicitApi() — all public APIs need visibility modifiers
  • Mark internal APIs with internal keyword
  • KDoc on all public classes, functions, and properties

Commit Conventions

We use Conventional Commits:

feat: add network inspection plugin
fix: resolve theme bleed into host app
docs: update plugin creation guide
test: add LogStorage unit tests
refactor: extract log filtering to separate class
chore: update Kotlin to 2.3.0
perf: cache LogEntry computed properties
ci: add iOS build to CI pipeline

Architecture Rules

  • Plugins must extend the core Plugin lifecycle classes
  • No Android-specific imports in commonMain
  • All state exposed as StateFlow (no LiveData)
  • Compose UI uses Material3 exclusively
  • Keep the public API surface minimal

Architecture Overview

AELogProvider AELog (Core Engine) PLUGIN SYSTEM LogPlugin :ae-log-logs CrashPlugin :ae-log-crashes NetworkPlugin :ae-log-network AnalyticsPlugin :ae-log-analytics STORAGE LAYER RAM Storage InMemoryPluginStorage Disk Storage PersistentPluginStorage