kotlin-logging icon indicating copy to clipboard operation
kotlin-logging copied to clipboard

Add a separate android module that calls Android API directly

Open oshai opened this issue 5 years ago • 0 comments

Currently Android is supported via slf4j extension which have issues and seems deprecated for that use. In #121 it was suggested to use Kotlin Multi Platform and have a module for android separated from the regular jvm module. Below is a code sample that can assist.
Basically such module should be similar to the js module without slf4j dependency. Here are the docs for android multiplatform support.

import android.util.Log
import mu.KLoggable
import mu.KLogger
import mu.Marker
import mu.KLoggerFactory
import java.lang.reflect.Modifier

/**
 * Android Logging implementation of [KLogger]
 */
class AndroidLogger(override val name: String) : KLogger {
    // No implementation, "catching" support not applied to android logger, unlikely to be used at this point
    override fun <T : Throwable> catching(throwable: T) = Unit

    override fun debug(msg: () -> Any?) {
        if (Log.isLoggable(name, Log.DEBUG)) {
            Log.d(name, msg.toStringSafely())
        }
    }

    override fun debug(t: Throwable?, msg: () -> Any?) {
        if (Log.isLoggable(name, Log.DEBUG)) {
            Log.d(name, msg.toStringSafely(), t)
        }
    }

        // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun debug(marker: Marker?, msg: () -> Any?) = Unit

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun debug(marker: Marker?, t: Throwable?, msg: () -> Any?) = Unit

    // No implementation, "entry" support not applied to android logger, unlikely to be used at this point
    override fun entry(vararg argArray: Any?) = Unit

    override fun error(msg: () -> Any?) {
        if (Log.isLoggable(name, Log.ERROR)) {
            Log.e(name, msg.toStringSafely())
        }
    }

    override fun error(t: Throwable?, msg: () -> Any?) {
        if (Log.isLoggable(name, Log.ERROR)) {
            Log.e(name, msg.toStringSafely(), t)
        }
    }

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun error(marker: Marker?, msg: () -> Any?) = Unit

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun error(marker: Marker?, t: Throwable?, msg: () -> Any?) = Unit

    // No implementation, "exit" support not applied to android logger, unlikely to be used at this point
    override fun exit() = Unit

    // No implementation, "exit" support not applied to android logger, unlikely to be used at this point
    override fun <T> exit(result: T): T = throw NotImplementedError("Exit not supported by android logger")

    override fun info(msg: () -> Any?) {
        if (Log.isLoggable(name, Log.INFO)) {
            Log.i(name, msg.toStringSafely())
        }
    }

    override fun info(t: Throwable?, msg: () -> Any?) {
        if (Log.isLoggable(name, Log.INFO)) {
            Log.i(name, msg.toStringSafely(), t)
        }
    }

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun info(marker: Marker?, msg: () -> Any?) = Unit

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun info(marker: Marker?, t: Throwable?, msg: () -> Any?) = Unit

    // No implementation, "throwing" support not applied to android logger, unlikely to be used at this point
    override fun <T : Throwable> throwing(throwable: T): T =
        throw NotImplementedError("throwing not supported by android logger")

    override fun trace(msg: () -> Any?) {
        if (Log.isLoggable(name, Log.VERBOSE)) {
            Log.v(name, msg.toStringSafely())
        }
    }

    override fun trace(t: Throwable?, msg: () -> Any?) {
        if (Log.isLoggable(name, Log.VERBOSE)) {
            Log.v(name, msg.toStringSafely(), t)
        }
    }

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun trace(marker: Marker?, msg: () -> Any?) = Unit

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun trace(marker: Marker?, t: Throwable?, msg: () -> Any?) = Unit

    override fun warn(msg: () -> Any?) {
        if (Log.isLoggable(name, Log.WARN)) {
            Log.w(name, msg.toStringSafely())
        }
    }

    override fun warn(t: Throwable?, msg: () -> Any?) {
        if (Log.isLoggable(name, Log.WARN)) {
            Log.w(name, msg.toStringSafely(), t)
        }
    }

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun warn(marker: Marker?, msg: () -> Any?) = Unit

    // No implementation, "marker" support not applied to android logger, unlikely to be used at this point
    override fun warn(marker: Marker?, t: Throwable?, msg: () -> Any?) = Unit
}

private fun (() -> Any?)?.toStringSafely() = this?.invoke()?.toString().orEmpty()

/**
 * [KLoggerFactory] that emits [android.util.Log] backed log entries
 */
class AndroidLoggerFactory : KLoggerFactory {
    override fun logger(func: () -> Unit): KLogger = logger(name(func))

    override fun logger(name: String): KLogger = AndroidLogger(name)

    override fun logger(loggable: KLoggable): KLogger = logger(unwrapCompanionClass(loggable.javaClass).name)
}

/**
 * unwrap companion class to enclosing class given a Java Class
 */
private fun <T : Any> unwrapCompanionClass(clazz: Class<T>): Class<*> {
    clazz.enclosingClass?.also { enclosingClass ->
        try {
            val field = enclosingClass.getField(clazz.simpleName)
            if (Modifier.isStatic(field.modifiers) && field.type == clazz) {
                // && field.get(null) === obj
                // the above might be safer but problematic with initialization order
                return enclosingClass
            }
        } catch (e: Exception) {
            // ok, it is not a companion object
        }
    }
    return clazz
}

/**
 * get class name for function by the package of the function
 */
private fun name(func: () -> Unit): String {
    val name = func.javaClass.name
    return when {
        name.contains("Kt$") -> name.substringBefore("Kt$")
        name.contains("$") -> name.substringBefore("$")
        else -> name
    }
}

oshai avatar Jul 16 '20 21:07 oshai