Thanks to visit codestin.com
Credit goes to github.com

Skip to content

05nelsonm/kmp-log

Repository files navigation

kmp-log

badge-license badge-latest

badge-kotlin

badge-platform-android badge-platform-jvm badge-platform-js badge-platform-js-node badge-platform-wasm badge-platform-wasi badge-platform-android-native badge-platform-linux badge-platform-macos badge-platform-ios badge-platform-tvos badge-platform-watchos badge-platform-windows

A small, simple, highly extensible logging library for Kotlin Multiplatform. Inspired by Timber, and the unfortunate state of available logging libraries for Kotlin Multiplatform.

By default, the only Log instance available from Log.Root is the Log.AbortHandler, which only accepts Log.Level.Fatal logs and will abort the process in a system dependant manner after the log is captured. Simply Log.Root.uninstall it to disable. Other than that, no logging happens unless you choose to Log.Root.install a Log.

Logger compatibility dependencies are available for:

API docs are available at https://kmp-log.matthewnelson.io

Usage

  1. Log.Root.install desired Log instance(s) at application startup.
  2. Create Log.Logger instances throughout your codebase and log to them.
class MyClass {
    private companion object {
        // Setup Log.Logger instances.
        private val LOG = Log.Logger.of(tag = "MyClass")
    }

    fun doSomething(withThis: String): Int {
        var result = withThis.length

        // Lazy logging with inline functions to mitigate
        // unnecessary String creation. If no Log instances
        // are installed to accept logs from this Log.Logger
        // and at the specified level, then nothing happens.
        //
        // Jvm/Android extension functions are also available
        // for lazy logging via String.format
        result += LOG.v { "Log.Level.Verbose >> $withThis" }
        result += LOG.d { "Log.Level.Debug   >> $withThis" }
        result += LOG.i { "Log.Level.Info    >> $withThis" }
        result += LOG.w { "Log.Level.Warn    >> $withThis" }
        result += LOG.e { "Log.Level.Error   >> $withThis" }
//        result += LOG.wtf { "Log.Level.Fatal >> $withThis" }
        return result
    }
}

fun main() {
    // Install desired Log implementation(s) at startup
    Log.Root.install(SysLog.Debug)

    val doSomethingResult = MyClass().doSomething("Hello!")

    // Optionally, define a Log.Logger.domain, separate from its tag.
    // This is useful for library developers as users can receive
    // granular insight into the library's interworkings, if and only
    // if there is a Log instance installed.
    //
    // The domain can be used by Log.isLoggable implementations to either
    // blacklist or whitelist logging for the entire domain.
    val logger = Log.Logger.of(tag = "Main", domain = "my-library:submodule")

    // Return values of all Log.Logger functions indicate if logging happened.
    val numberOfLogInstancesThatLoggedThisThing = logger.log(
        level = Log.Level.Info,
        msg = "MyClass.doSomething returned $doSomethingResult",
    )
    assertEquals(1, numberOfLogInstancesThatLoggedThisThing)

    Log.Root.uninstall(SysLog.Debug)
    assertEquals(0, logger.e { throw IllegalStateException("Won't happen...") })

    // Create your own Log implementation(s) and install them.
    Log.Root.install(object : Log(uid = "MyOwnLog", min = Level.Warn) {
        // See docs.
        override fun log(
            level: Level,
            domain: String?,
            tag: String,
            msg: String?,
            t: Throwable?,
        ): Boolean {
            var wasLogged = true
            // Format & log data. If something happened and
            // no data could be logged, return false instead.
            return wasLogged
        }

        // Optional override... See docs.
        override fun isLoggable(level: Level, domain: String?, tag: String): Boolean {
            // e.g. Whitelist logging to this Log instance by specific domain
            return domain == "my-library:submodule"
        }
        // Optional override... See docs.
        override fun onInstall() { /* allocate resources */ }
        // Optional override... See docs.
        override fun onUninstall() { /* deallocate resources */ }
    })

    // Log.Level.Fatal logging that works how you want it to. By default,
    // Log.AbortHandler (if installed) is always the last Log instances
    // that gets logged to and only accepts Log.Level.Fatal logs. Simply
    // uninstall it to disable.
    assertEquals(2, Log.Root.installed().size)
    assertTrue(Log.AbortHandler.isInstalled)
    Log.Root.uninstallAll(evenAbortHandler = true)

    assertEquals(0, logger.wtf { StringBuilder("Nothing...") })

    Log.Root.install(Log.AbortHandler)
    Log.Root.installOrThrow(SysLog.of(min = Level.Fatal))

    assertEquals(0, logger.e(Throwable()) { "Still nothing.." })

    
    // This won't return because Log.AbortHandler will abort the process,
    // but for the sake of the example the return value would be 2, as
    // SysLog & Log.AbortHandler logged the log.
    assertEquals(2, logger.wtf { "ABORT!" })

    Log.Root.uninstallAll(evenAbortHandler = false)

    // If no other Log instances captured the Log.Level.Fatal log, then
    // Log.AbortHandler will create an exception and use Throwable.printStackTrace
    // before aborting.
    assertEquals(1, logger.wtf { "ABORT AGAIN!" })
}

Get Started

// build.gradle.kts
dependencies {
    val vKmpLog = "0.1.0-alpha02"
    implementation("io.matthewnelson.kmp-log:log:$vKmpLog")
    
    // If you need SysLog
    implementation("io.matthewnelson.kmp-log:sys:$vKmpLog")

    // If you need to convert Log.Logger to org.slf4j.Logger
    implementation("io.matthewnelson.kmp-log:compat-slf4j:$vKmpLog")

    // If you need to convert Log.Logger to io.ktor.util.logging.Logger
    implementation("io.matthewnelson.kmp-log:compat-ktor:$vKmpLog")
}

About

Logging for Kotlin Multiplatform

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages