Skip to content

About

πŸ“ A lightweight Kotlin Multiplatform (KMP) logging API with lazy evaluation, configurable formatting, disk logging, and tag inference.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

32 Commits

Folders and files

Repository files navigation

kmp-logcat

δΈ­ζ–‡θ―΄ζ˜Ž

Maven Central CI License Kotlin

πŸ“ A lightweight Kotlin Multiplatform (KMP) logging API with lazy evaluation, configurable formatting, disk logging, and tag inference.

Features

  • logcat { } is lazy: message blocks run only when a logger is installed and loggable.
  • Automatic tag from the call site class name (with an overload for explicit tags).
  • Supports different formatted outputs:
    • AndroidLogcatFormatStrategy / IosLogcatFormatStrategy: platform-style console output with configurable fields.
    • PrettyFormatStrategy: bordered output with thread info and call stack.
    • NonFormatStrategy: no formatting; forwards raw messages to the underlying LogStrategy.
  • Disk logging writes logs to disk with buffering, size rotation, time-based cleanup, and dynamic size limits.
  • Multiple loggers can be installed at the same time.
  • Throwable.asLog() for readable stack traces.

Installation

Add the dependency in commonMain:

kotlin {
  sourceSets {
    commonMain.dependencies {
      implementation("com.airsaid:logcat:$version")
    }
  }
}

Make sure you have Maven Central:

repositories {
  mavenCentral()
}

Quick start

1) Install a platform logger

Android (e.g., Application.onCreate):

AndroidLogcatLogger.installOnDebuggableApp(
  application = this,
  minPriority = LogPriority.DEBUG,
) {
  AndroidLogcatFormatStrategy.Builder<AndroidLogcatLogStrategy>()
    .logStrategy(AndroidLogcatLogStrategy())
    .timeStampPattern(
      pattern = "uuuu-MM-dd HH:mm:ss.SSS",
      timeZone = TimeZone.currentSystemDefault(),
    )
    .build()
}

installOnDebuggableApp skips installation when the application is not debuggable. Use AndroidLogcatLogger.install only when logcat output in non-debuggable builds is intentional.

iOS (app startup):

IosLogcatLogger.install(
  minPriority = LogPriority.DEBUG,
) {
  IosLogcatFormatStrategy.Builder<IosLogcatLogStrategy>()
    .logStrategy(IosLogcatLogStrategy())
    .timeStampPattern(
      pattern = "uuuu-MM-dd HH:mm:ss.SSS",
      timeZone = TimeZone.currentSystemDefault(),
    )
    .build()
}

Convenience installers are idempotent and use a private installation key. The first successful call wins; later calls reuse the installed logger without evaluating their factory. To change its configuration, uninstall the existing logger first (or call LogcatLogger.uninstallAll() during a full logging reset), then install it again. To install multiple independently configured loggers of the same type, construct them explicitly and use LogcatLogger.install(...).

IosLogcatLogStrategy() marks dynamic unified log content as private by default. To make logs publicly readable, opt in only when messages are known not to contain sensitive data:

IosLogcatLogStrategy(IosLogcatPrivacy.PUBLIC)

2) Log messages

class Foo {
  fun bar() {
    logcat { "Default log" }
    logcat(LogPriority.INFO) { "Info log" }
    logcat(tag = "CustomTag") { "Custom tag log" }
  }
}

logcat("StandaloneTag") { "Log in a top-level function" }

try {
  error("boom")
} catch (t: Throwable) {
  logcat { t.asLog() }
}

Note: logcat { } is an Any extension. For top-level functions (no this), use the logcat(tag) { } overload.

Android lint checks

The Android artifact ships with a custom lint check that errors when app code calls android.util.Log directly. Prefer routing logs through kmp-logcat so messages stay lazy, consistently formatted, and controlled by the installed loggers.

// Error: LogcatSystemLogUsage
Log.d(tag, msg)

// Quick fix
logcat(tag, LogPriority.DEBUG) { msg }

Throwable overloads are also supported when they can be safely converted:

// Error: LogcatSystemLogUsage
Log.e(tag, msg, throwable)

// Quick fix
logcat(tag, LogPriority.ERROR) { msg + "\n" + throwable.asLog() }

This lint check only runs for Android lint. It does not affect common or iOS source sets. The library's own Android implementation uses android.util.Log internally by design.

If direct platform logging is intentional in your app, suppress the error with @SuppressLint("LogcatSystemLogUsage") or configure the issue in lint.xml.

Format strategies

AndroidLogcatFormatStrategy / IosLogcatFormatStrategy

Formats output similar to platform consoles. You can toggle fields:

  • showTimeStamp(Boolean): include/exclude the timestamp.
  • timeStampPattern(String, TimeZone): customize timestamp output with a Unicode pattern and timezone.
  • timeStampFormatter((Instant) -> String): fully custom timestamp formatter.
  • showProcessId(Boolean): include/exclude the process id.
  • showThreadInfo(Boolean): include/exclude thread name and id.
  • showTag(Boolean): include/exclude the log tag.
  • showLevel(Boolean): include/exclude the log priority.

By default, timestamp uses Instant.toString() (ISO-8601 UTC).

PrettyFormatStrategy

Adds borders, thread info, and call stack lines:

  • showThreadInfo(Boolean): include/exclude thread info.
  • methodCount(Int): number of call stack lines to print.
  • methodOffset(Int): offset into the call stack.

NonFormatStrategy

Bypasses formatting and delegates directly to the underlying LogStrategy.

Disk logging

Disk logging is provided by DiskLogStrategy + DiskLogger and supports writing logs to disk:

val diskLogger = DiskLogger.installOnApp(minPriority = LogPriority.WARN) {
  NonFormatStrategy(
    DiskLogStrategy.Builder()
      .logFileDirectory(logDirectory)
      .logFileGenerator(DefaultLogFileGenerator())
      .logFileMaxSize(1024L * 1024L * 100L) // 100MB
      .logFileMaxTime(7L * 24L * 60L * 60L * 1000L) // 7 days
      .logFileMaxSizeResolver(AvailableSpaceLogFileMaxSizeResolver())
      .logBufferMaxSize(10 * 1024) // 10K chars
      .build()
  )
}

Keep DiskLogStrategy.Builder().build() inside the factory so repeated installation attempts do not allocate unused disk logging resources.

Builder options:

  • logFileDirectory(String)
    • Base directory for log files. Provide a platform-specific path (e.g., Android app files dir, iOS Documents/Library dir).
  • logFileGenerator(LogFileGenerator)
    • File naming/rotation strategy. Default: DefaultLogFileGenerator (daily folder + date files).
  • logFileMaxSize(Long)
    • Max size per log file in bytes before rotation (default 20MB).
  • logFileMaxTime(Long)
    • Max retention time in milliseconds before deletion (default 7 days).
  • logFileMaxSizeResolver(LogFileMaxSizeResolver)
    • Dynamically adjusts max size based on available space.
  • logBufferMaxSize(Int)
    • Buffer size (chars) before flushing to disk (default 10K).

Disk log calls are queued on a private serial background worker. Threshold-based flushes do not perform directory traversal or file writes on the calling thread, and accepted logs are processed in submission order. flush() and close() synchronously wait for previously accepted logs to be written, so avoid calling them frequently from the main thread.

Manual flush:

// ...
diskLogger.flush()

Multiple loggers

val androidLogger = AndroidLogcatLogger(
  minPriority = LogPriority.DEBUG,
  formatStrategy = AndroidLogcatFormatStrategy.Builder<AndroidLogcatLogStrategy>()
    .logStrategy(AndroidLogcatLogStrategy())
    .build()
)
val diskLogger = DiskLogger(
  minPriority = LogPriority.WARN,
  formatStrategy = NonFormatStrategy(
    DiskLogStrategy.Builder()
      .logFileDirectory(logDirectory)
      .build()
  )
)

LogcatLogger.install(androidLogger, diskLogger)

Requirements

  • Android
    • minSdk: 24
    • compileSdk / targetSdk: 35
    • JVM target: 17
  • iOS targets
    • iosArm64 / iosX64 / iosSimulatorArm64
  • Kotlin: 2.2.21

Changelog & releases

Acknowledgements

  • square/logcat
  • orhanobut/logger

License

Apache-2.0. See LICENSE.

About

πŸ“ A lightweight Kotlin Multiplatform (KMP) logging API with lazy evaluation, configurable formatting, disk logging, and tag inference.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages