
Multiplatform logging library targets Android, iOS, JVM, JS and Wasm, utilizing native logging facilities. Offers high performance, minimal overhead, customizable log levels, and thread-safety without requiring configuration.
Kotlin multiplatform logging library targeting Android, iOS, JVM, WasmJS and JS.
Version 2.0 changed the package from org.lighthousegames.logging to com.diamondedge.logging
To do the migration simply change the dependency to
implementation("com.diamondedge:logging:$logging_version")
and replace all occurrences of org.lighthousegames.logging in your code base with
com.diamondedge.logging.
The library is available from the Maven Central repository with the current version
of
You should use version
2.0.21 or later of the kotlin multiplatform plugin (Older version of Kotlin
are supported in older versions of the library). Place the following in the commonMain section.
build.gradle.kts
sourceSets {
val commonMain by getting {
dependencies {
api("com.diamondedge:logging:$logging_version")
}
}
}build.gradle
sourceSets {
commonMain {
dependencies {
api "com.diamondedge:logging:$logging_version"
}
}
}So, you want the best and easiest to use kotlin logging library but are not yet ready for
multiplatform development then just include on the following in your dependencies section:
implementation("com.diamondedge:logging-android:$logging_version")
implementation("com.diamondedge:logging-jvm:$logging_version")
implementation("com.diamondedge:logging-js:$logging_version")Create an instance of logging class by using the convenience function logging().
On Android, iOS and JVM the class from where logging() was called will be used as the tag in the
logs. For JS or when a specific tag is desired it can be supplied i.e val log = logging("mytag")
or val log = KmLog("mytag")
class MyClass {
fun easyPeasy() {
log.i { "use traditional Android short function name" }
}
fun easyPeasyLemonSqueesy() {
log.info { "use longer more explicit function name" }
}
companion object {
val log = logging()
}
}There are 3 aspects to logging that have significant overhead:
KmLogging addresses and improves on each of these as compared to other logging libraries:
val log = logging() is executed. Most logging libraries calculate it on every log call or
require you to supply it. Calculating the tag is expensive since it normally requires creating
and parsing a call stack to get the class name. KmLogging chose to eliminate this performance
drain by asking the developer to add an additional line of code val log = logging() to each
class or module so this cost is only paid one time per class.Since KmLogging has very little overhead when it is disabled, if you turn off logging in release builds you can leave a lot of logging in your code without paying performance penalties in production.
Note: if any logger has a given log level enabled then the lambda for that log level will be evaluated. Suppose you used the default configuration and you added a Crashlytics logger that logs at info, warn and error levels. This would mean that the lambda for info, warn and error levels will be evaluated because the Crashlytics logger needs it. So in this scenario you would want to have minimal info logging code so as to not slow down the application at runtime and put most of the logging at verbose and debug levels where it will not be evaluated in release builds.
With no configuration, logging is enabled for all log levels.
If logging is not desired for release builds then use the following to turn off the logging of the default PlatformLogger
KmLogging.setLogLevel(LogLevel.Off)
// in Android this could be based on the debug flag:
KmLogging.setLogLevel(if (BuildConfig.DEBUG) LogLevel.Verbose else LogLevel.Off)or use PlatformLogger and supply it a log level controller that is disabled for all log levels:
KmLogging.setLoggers(PlatformLogger(FixedLogLevel(false)))
// in Android this could be based on the debug flag:
KmLogging.setLoggers(PlatformLogger(FixedLogLevel(BuildConfig.DEBUG)))| KmLogging version | Kotlin version |
|---|---|
| 2.1.0 | 2.2.20 |
| 2.0.3 | 2.0.21 |
| 1.5.0 | 1.9.24 |
| 1.4.2 | 1.8.22 |
| 1.3.0 | 1.8.10 |
| 1.2.1 | 1.7.21 |
| 1.2.0 | 1.6.10 |
| 1.1.0 | 1.5.32 |
| 1.0.0 | 1.4.30 |
KmLogging.setLoggers() the existing loggers are removed and the supplied ones are
added in.KmLogging.addLoggers() should be used.PlatformLogger uses Log on Android, os_log on iOS, SLF4j on JVM and console on WasmJS and JS.KmLogging.setupLoggingFlags() should be called when the logger's log levels were
changed to calculate which log levels are enabled. KmLogging maintains variables for each log
level corresponding to whether any logger is enabled at that level. This is done for performance
reason so only a single boolean check will be done at runtime to minimize the overhead when
running in production. The android sample app demonstrates this. @BeforeTest
fun setup() {
KmLogging.setLoggers(PrintLogger(FixedLogLevel(true)))
}android {
testOptions {
unitTests {
isReturnDefaultValues = true
}
}
}If logging is only desired at certain levels that can be setup. For example, if only the more important logs should be sent to Crashlytics to give some context to crashes then only log info level and above. That can be easily done by by defining and adding in a logger to do that. The sample android app implement this.
class CrashlyticsLogger : Logger {
override fun verbose(tag: String?, msg: String) {}
override fun debug(tag: String?, msg: String) {}
override fun info(tag: String?, msg: String) {
FirebaseCrashlytics.getInstance().log(msg)
}
override fun warn(tag: String?, msg: String, t: Throwable?) {
FirebaseCrashlytics.getInstance().log(msg)
}
override fun error(tag: String?, msg: String, t: Throwable?) {
FirebaseCrashlytics.getInstance().log(msg)
}
override fun isLoggingVerbose(): Boolean = false
override fun isLoggingDebug(): Boolean = false
override fun isLoggingInfo(): Boolean = true
override fun isLoggingWarning(): Boolean = true
override fun isLoggingError(): Boolean = true
}
// at App creation time configure logging
// use addLogger to keep the existing loggers
KmLogging.addLogger(CrashlyticsLogger())By default the kotlin multiplatform toolchain will not export all KmLogging classes and those that
are will be prefaced with the stringLogging.
If you want to use classes from Swift code you will need to direct the plugin to export the logging
library in your build.gradle.kts:
ios {
binaries {
framework {
baseName = "my-shared-module-name"
export("com.diamondedge:logging:$logging_version")
}
}
}Note: logging must also be included as an api dependency. See https://kotlinlang.org/docs/reference/mpp-build-native-binaries.html
The code to figure out what class KmLog was instantiated from does not work from within Swift, so you will always want to pass in the class name:
class MyClass {
let log = KmLog(tag: "MyClass")
}You will need to include a dependency on the logging library of your choice that is compatible with
SLF4J. See the sample app which uses logback. Since SLF4J controls the log level using its own
configurations it is advisable to retain the use of FixedLogLevel in the default configuration as
the log level controller. The log level controller in KmLogging controls the possibility of logging
occurring with SLF4J controlling whether a particular log usage is output or not.
A best practice for libraries is to not have its logging turned on be default. If both a library and an application both use KmLogging then the library will have its logging turned on automatically. To enable or disable a library's logging independently of the application, the library needs to use a wrapper so logging can be turned on/off using a variable.
Example usage with code implemented in ChartsLogging.kt:
object ChartsLogging {
var enabled = true
}
fun moduleLogging(tag: String? = null): KmModuleLog {
// string passed into createTag should be the name of the class that this function is implemented in
// if it is a top level function then the class name is the file name with Kt appended
val t = tag ?: KmLogging.createTag("ChartsLoggingKt").first
return KmModuleLog(logging(t), ChartsLogging::enabled)
}The library code would then use this new function to do its logging:
val log = moduleLogging()Once you have adopted KmLogging, what do you do with all your existing Android code that is using
the Log class? The first quick and easy step is to switch all your code to using
com.diamondedge.logging.Log class which mimics the Android Log class but sends all output
through KmLogging so you can turn it on and off at the same time as with all other KmLog usages and
have all its benefits. To do this simply replace all occurrences of import android.util.Log in
your code base with import com.diamondedge.logging.Log
Kotlin multiplatform logging library targeting Android, iOS, JVM, WasmJS and JS.
Version 2.0 changed the package from org.lighthousegames.logging to com.diamondedge.logging
To do the migration simply change the dependency to
implementation("com.diamondedge:logging:$logging_version")
and replace all occurrences of org.lighthousegames.logging in your code base with
com.diamondedge.logging.
The library is available from the Maven Central repository with the current version
of
You should use version
2.0.21 or later of the kotlin multiplatform plugin (Older version of Kotlin
are supported in older versions of the library). Place the following in the commonMain section.
build.gradle.kts
sourceSets {
val commonMain by getting {
dependencies {
api("com.diamondedge:logging:$logging_version")
}
}
}build.gradle
sourceSets {
commonMain {
dependencies {
api "com.diamondedge:logging:$logging_version"
}
}
}So, you want the best and easiest to use kotlin logging library but are not yet ready for
multiplatform development then just include on the following in your dependencies section:
implementation("com.diamondedge:logging-android:$logging_version")
implementation("com.diamondedge:logging-jvm:$logging_version")
implementation("com.diamondedge:logging-js:$logging_version")Create an instance of logging class by using the convenience function logging().
On Android, iOS and JVM the class from where logging() was called will be used as the tag in the
logs. For JS or when a specific tag is desired it can be supplied i.e val log = logging("mytag")
or val log = KmLog("mytag")
class MyClass {
fun easyPeasy() {
log.i { "use traditional Android short function name" }
}
fun easyPeasyLemonSqueesy() {
log.info { "use longer more explicit function name" }
}
companion object {
val log = logging()
}
}There are 3 aspects to logging that have significant overhead:
KmLogging addresses and improves on each of these as compared to other logging libraries:
val log = logging() is executed. Most logging libraries calculate it on every log call or
require you to supply it. Calculating the tag is expensive since it normally requires creating
and parsing a call stack to get the class name. KmLogging chose to eliminate this performance
drain by asking the developer to add an additional line of code val log = logging() to each
class or module so this cost is only paid one time per class.Since KmLogging has very little overhead when it is disabled, if you turn off logging in release builds you can leave a lot of logging in your code without paying performance penalties in production.
Note: if any logger has a given log level enabled then the lambda for that log level will be evaluated. Suppose you used the default configuration and you added a Crashlytics logger that logs at info, warn and error levels. This would mean that the lambda for info, warn and error levels will be evaluated because the Crashlytics logger needs it. So in this scenario you would want to have minimal info logging code so as to not slow down the application at runtime and put most of the logging at verbose and debug levels where it will not be evaluated in release builds.
With no configuration, logging is enabled for all log levels.
If logging is not desired for release builds then use the following to turn off the logging of the default PlatformLogger
KmLogging.setLogLevel(LogLevel.Off)
// in Android this could be based on the debug flag:
KmLogging.setLogLevel(if (BuildConfig.DEBUG) LogLevel.Verbose else LogLevel.Off)or use PlatformLogger and supply it a log level controller that is disabled for all log levels:
KmLogging.setLoggers(PlatformLogger(FixedLogLevel(false)))
// in Android this could be based on the debug flag:
KmLogging.setLoggers(PlatformLogger(FixedLogLevel(BuildConfig.DEBUG)))| KmLogging version | Kotlin version |
|---|---|
| 2.1.0 | 2.2.20 |
| 2.0.3 | 2.0.21 |
| 1.5.0 | 1.9.24 |
| 1.4.2 | 1.8.22 |
| 1.3.0 | 1.8.10 |
| 1.2.1 | 1.7.21 |
| 1.2.0 | 1.6.10 |
| 1.1.0 | 1.5.32 |
| 1.0.0 | 1.4.30 |
KmLogging.setLoggers() the existing loggers are removed and the supplied ones are
added in.KmLogging.addLoggers() should be used.PlatformLogger uses Log on Android, os_log on iOS, SLF4j on JVM and console on WasmJS and JS.KmLogging.setupLoggingFlags() should be called when the logger's log levels were
changed to calculate which log levels are enabled. KmLogging maintains variables for each log
level corresponding to whether any logger is enabled at that level. This is done for performance
reason so only a single boolean check will be done at runtime to minimize the overhead when
running in production. The android sample app demonstrates this. @BeforeTest
fun setup() {
KmLogging.setLoggers(PrintLogger(FixedLogLevel(true)))
}android {
testOptions {
unitTests {
isReturnDefaultValues = true
}
}
}If logging is only desired at certain levels that can be setup. For example, if only the more important logs should be sent to Crashlytics to give some context to crashes then only log info level and above. That can be easily done by by defining and adding in a logger to do that. The sample android app implement this.
class CrashlyticsLogger : Logger {
override fun verbose(tag: String?, msg: String) {}
override fun debug(tag: String?, msg: String) {}
override fun info(tag: String?, msg: String) {
FirebaseCrashlytics.getInstance().log(msg)
}
override fun warn(tag: String?, msg: String, t: Throwable?) {
FirebaseCrashlytics.getInstance().log(msg)
}
override fun error(tag: String?, msg: String, t: Throwable?) {
FirebaseCrashlytics.getInstance().log(msg)
}
override fun isLoggingVerbose(): Boolean = false
override fun isLoggingDebug(): Boolean = false
override fun isLoggingInfo(): Boolean = true
override fun isLoggingWarning(): Boolean = true
override fun isLoggingError(): Boolean = true
}
// at App creation time configure logging
// use addLogger to keep the existing loggers
KmLogging.addLogger(CrashlyticsLogger())By default the kotlin multiplatform toolchain will not export all KmLogging classes and those that
are will be prefaced with the stringLogging.
If you want to use classes from Swift code you will need to direct the plugin to export the logging
library in your build.gradle.kts:
ios {
binaries {
framework {
baseName = "my-shared-module-name"
export("com.diamondedge:logging:$logging_version")
}
}
}Note: logging must also be included as an api dependency. See https://kotlinlang.org/docs/reference/mpp-build-native-binaries.html
The code to figure out what class KmLog was instantiated from does not work from within Swift, so you will always want to pass in the class name:
class MyClass {
let log = KmLog(tag: "MyClass")
}You will need to include a dependency on the logging library of your choice that is compatible with
SLF4J. See the sample app which uses logback. Since SLF4J controls the log level using its own
configurations it is advisable to retain the use of FixedLogLevel in the default configuration as
the log level controller. The log level controller in KmLogging controls the possibility of logging
occurring with SLF4J controlling whether a particular log usage is output or not.
A best practice for libraries is to not have its logging turned on be default. If both a library and an application both use KmLogging then the library will have its logging turned on automatically. To enable or disable a library's logging independently of the application, the library needs to use a wrapper so logging can be turned on/off using a variable.
Example usage with code implemented in ChartsLogging.kt:
object ChartsLogging {
var enabled = true
}
fun moduleLogging(tag: String? = null): KmModuleLog {
// string passed into createTag should be the name of the class that this function is implemented in
// if it is a top level function then the class name is the file name with Kt appended
val t = tag ?: KmLogging.createTag("ChartsLoggingKt").first
return KmModuleLog(logging(t), ChartsLogging::enabled)
}The library code would then use this new function to do its logging:
val log = moduleLogging()Once you have adopted KmLogging, what do you do with all your existing Android code that is using
the Log class? The first quick and easy step is to switch all your code to using
com.diamondedge.logging.Log class which mimics the Android Log class but sends all output
through KmLogging so you can turn it on and off at the same time as with all other KmLog usages and
have all its benefits. To do this simply replace all occurrences of import android.util.Log in
your code base with import com.diamondedge.logging.Log