
Zero-config logging utility inspired by SLF4J and Timber APIs, offering platform-specific logging targets, tag inference, message formatting, SLF4J integration, and customization options.
This library has been archived and will be receiving no further updates. Please switch to touchlab/Kermit for a replacement library, to consolidate the massive spread of Kotlin logging libraries. See discussion here for more context.
Zero-config Kotlin multiplatform logging utility, strongly inspired by the SLF4J and Timber APIs.
Clog is designed with the following goals in mind:
actual/expect declarations to provide natural logging targets for
each platform, rather than printing everything to stdout| Platform | Logging Target | ANSI Colors | Tag Inference | Message Formatting | SLF4J Integration | SLF4J MDC Support |
|---|---|---|---|---|---|---|
| JVM | System.out | ✅ | ✅ | ✅ | ✅ | ✅ |
| Android | android.util.Log | ❌ | ✅ | ✅ | ✅ | ❌ |
| JS | console.log | ❌ | ❌ | ✅ | ❌ | ❌ |
| iOS | NSLog | ❌ | ❌ | ✅ | ❌ | ❌ |
| {.table} |
repositories {
mavenCentral()
}
// for plain JVM or Android projects
dependencies {
implementation("io.github.copper-leaf:clog-core:{{site.version}}")
}
// for multiplatform projects
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.copper-leaf:clog-core:{{site.version}}")
}
}
}
}Clog's logging levels generally follow the SLF4J logging levels, and the API follows a similar API as android.util.log or Timber.
| Clog Level | Clog Method | SLF4J Level |
|---|---|---|
| Verbose | Clog.v() |
logger.trace() |
| Debug | Clog.d() |
logger.debug() |
| Info | Clog.i() |
logger.info() |
| Default | Clog.log() |
N/A |
| Warning | Clog.w() |
logger.warn() |
| Error | Clog.e() |
logger.error() |
| Fatal | Clog.wtf() |
N/A |
| {.table} |
In general, a log consists of a message (which may be formatted with params in SLF4J-style), a tag, and a log level. Below is a description of the API
Tag will be inferred on supported platforms, based on the calling class
Clog.v("message")
Clog.d("message")
Clog.i("message")
Clog.log("message")
Clog.w("message")
Clog.e("message")
Clog.wtf("message")Clog.tag("tag").v("message")
Clog.tag("tag").d("message")
Clog.tag("tag").i("message")
Clog.tag("tag").w("message")
Clog.tag("tag").e("message")
Clog.tag("tag").wtf("message")val e = RuntimeException()
Clog.v(e)
Clog.d(e)
Clog.i(e)
Clog.log(e)
Clog.w(e)
Clog.e(e)
Clog.wtf(e)SLF4j-style formatting is supported, replacing {} with params passed to the logging call. This is supported on all
platforms and all log levels.
val foo = "bar"
Clog.i("message {}", foo) // logs 'message bar'Messages and exceptions can be filtered out by priority.
Clog.setMinPriority(Clog.Priority.ERROR)Messages can be filtered out by tags.
Clog.addTagToWhitelist("tag1")
Clog.addTagToBlacklist("tag2")Using the Clog DSL, simple strings can be logged lazily. The lambda is only evaluated if the logging level and tag is
enabled. By default, messages logged with the lambda DSL are not formatted, but it can be re-enabled by using format()
inside the lambda.
import clog.dsl.*
v { "message" }
d { "message" }
i { "message" }
w { "message" }
e { "message" }
wtf { "message" }import clog.dsl.*
v("tag") { "message" }
d("tag") { "message" }
i("tag") { "message" }
w("tag") { "message" }
e("tag") { "message" }
wtf("tag") { "message" }import clog.dsl.*
val foo = "bar"
v { format("message {}", foo) } // logs 'message bar'
d { format("message {}", foo) } // logs 'message bar'
i { format("message {}", foo) } // logs 'message bar'
w { format("message {}", foo) } // logs 'message bar'
e { format("message {}", foo) } // logs 'message bar'
wtf { format("message {}", foo) } // logs 'message bar'On plain JVM and Android platforms, Clog is set up as an SLF4J binding; that is, SLF4J will pass log messages through to
Clog. Other libraries and frameworks logging to SLF4J will be formatted as normal Clog logs for uniform log output, and
so Clog can be used as a simple SLF4J binding when you don't want to configure Logback. Additionally, SLF Mapped
Diagnostic Context (MDC) is supported, and context data can be added to log messages with the standard format of
%X{mdcKey}.
val slf4j: Logger = LoggerFactory.getLogger(JvmClogSlf4jTest::class.java)
MDC.put("akey", "avalue")
slf4j.trace("message %X{akey}") // logs 'message avalue' to the Clog loggerClog is designed to work out-of-the-box with absolutely zero config required to start logging with it. However, you can
customize all components of Clog to your needs. Clog is comprised of several components wrapped in a ClogProfile,
which is the global instance of Clog.getInstance(). You can customize your Clog by creating a new Profile with your
custom components:
val newProfile = ClogProfile(...)
Clog.setProfile(newProfile)You can also use the Clog.updateProfile helper to create a profile based on the current global instance:
Clog.updateProfile { it.copy(logger = newLogger) }The table below describes the classes that can be customized in the ClogProfile, along with their default
implementation for each supported platform:
| Interface | Description | JVM | Android | JS | iOS |
|---|---|---|---|---|---|
ClogTagProvider |
Infers a tag if one is not provided to the logging call | DefaultTagProvider() |
DefaultTagProvider() |
DefaultTagProvider() |
DefaultTagProvider() |
ClogMessageFormatter |
Formats a message string to pass to the ClogLogger
|
Slf4jMessageFormatter(DefaultMessageFormatter()) |
DefaultMessageFormatter() |
DefaultMessageFormatter() |
DefaultMessageFormatter() |
ClogFilter |
Determines whether to format and log a message | DefaultFilter() |
DefaultFilter() |
DefaultFilter() |
DefaultFilter() |
ClogLogger |
Prints a formatted log to a lower-level platform-specific logger or console | DefaultLogger() |
AndroidLogger() |
JsConsoleLogger() |
NsLogger() |
| {.table} |
val isDebug = ...
Clog.configureLoggingInProduction(isDebug)Replaces the current logging target with a custom one.
val customLogger = object : ClogLogger {
override fun log(priority: Clog.Priority, tag: String?, message: String) {
...
}
override fun logException(priority: Clog.Priority, tag: String?, throwable: Throwable) {
...
}
}
Clog.updateProfile { it.copy(logger = customLogger) }Add an additional logger to the current instance. Calling addLogger multiple times will continue adding loggers, and
messages will be delegated to all loggers.
val customLogger = object : ClogLogger {
override fun log(priority: Clog.Priority, tag: String?, message: String) {
...
}
override fun logException(priority: Clog.Priority, tag: String?, throwable: Throwable) {
...
}
}
Clog.addLogger(customLogger)// Given some classes that depend on a logger
class Controller(val logger: ClogProfile)
// just declare a ClogProfile singleton with any configurations you need
val module = module {
single { ClogProfile() }
single { Controller(get()) }
} This library has been archived and will be receiving no further updates. Please switch to touchlab/Kermit for a replacement library, to consolidate the massive spread of Kotlin logging libraries. See discussion here for more context.
Zero-config Kotlin multiplatform logging utility, strongly inspired by the SLF4J and Timber APIs.
Clog is designed with the following goals in mind:
actual/expect declarations to provide natural logging targets for
each platform, rather than printing everything to stdout| Platform | Logging Target | ANSI Colors | Tag Inference | Message Formatting | SLF4J Integration | SLF4J MDC Support |
|---|---|---|---|---|---|---|
| JVM | System.out | ✅ | ✅ | ✅ | ✅ | ✅ |
| Android | android.util.Log | ❌ | ✅ | ✅ | ✅ | ❌ |
| JS | console.log | ❌ | ❌ | ✅ | ❌ | ❌ |
| iOS | NSLog | ❌ | ❌ | ✅ | ❌ | ❌ |
| {.table} |
repositories {
mavenCentral()
}
// for plain JVM or Android projects
dependencies {
implementation("io.github.copper-leaf:clog-core:{{site.version}}")
}
// for multiplatform projects
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.copper-leaf:clog-core:{{site.version}}")
}
}
}
}Clog's logging levels generally follow the SLF4J logging levels, and the API follows a similar API as android.util.log or Timber.
| Clog Level | Clog Method | SLF4J Level |
|---|---|---|
| Verbose | Clog.v() |
logger.trace() |
| Debug | Clog.d() |
logger.debug() |
| Info | Clog.i() |
logger.info() |
| Default | Clog.log() |
N/A |
| Warning | Clog.w() |
logger.warn() |
| Error | Clog.e() |
logger.error() |
| Fatal | Clog.wtf() |
N/A |
| {.table} |
In general, a log consists of a message (which may be formatted with params in SLF4J-style), a tag, and a log level. Below is a description of the API
Tag will be inferred on supported platforms, based on the calling class
Clog.v("message")
Clog.d("message")
Clog.i("message")
Clog.log("message")
Clog.w("message")
Clog.e("message")
Clog.wtf("message")Clog.tag("tag").v("message")
Clog.tag("tag").d("message")
Clog.tag("tag").i("message")
Clog.tag("tag").w("message")
Clog.tag("tag").e("message")
Clog.tag("tag").wtf("message")val e = RuntimeException()
Clog.v(e)
Clog.d(e)
Clog.i(e)
Clog.log(e)
Clog.w(e)
Clog.e(e)
Clog.wtf(e)SLF4j-style formatting is supported, replacing {} with params passed to the logging call. This is supported on all
platforms and all log levels.
val foo = "bar"
Clog.i("message {}", foo) // logs 'message bar'Messages and exceptions can be filtered out by priority.
Clog.setMinPriority(Clog.Priority.ERROR)Messages can be filtered out by tags.
Clog.addTagToWhitelist("tag1")
Clog.addTagToBlacklist("tag2")Using the Clog DSL, simple strings can be logged lazily. The lambda is only evaluated if the logging level and tag is
enabled. By default, messages logged with the lambda DSL are not formatted, but it can be re-enabled by using format()
inside the lambda.
import clog.dsl.*
v { "message" }
d { "message" }
i { "message" }
w { "message" }
e { "message" }
wtf { "message" }import clog.dsl.*
v("tag") { "message" }
d("tag") { "message" }
i("tag") { "message" }
w("tag") { "message" }
e("tag") { "message" }
wtf("tag") { "message" }import clog.dsl.*
val foo = "bar"
v { format("message {}", foo) } // logs 'message bar'
d { format("message {}", foo) } // logs 'message bar'
i { format("message {}", foo) } // logs 'message bar'
w { format("message {}", foo) } // logs 'message bar'
e { format("message {}", foo) } // logs 'message bar'
wtf { format("message {}", foo) } // logs 'message bar'On plain JVM and Android platforms, Clog is set up as an SLF4J binding; that is, SLF4J will pass log messages through to
Clog. Other libraries and frameworks logging to SLF4J will be formatted as normal Clog logs for uniform log output, and
so Clog can be used as a simple SLF4J binding when you don't want to configure Logback. Additionally, SLF Mapped
Diagnostic Context (MDC) is supported, and context data can be added to log messages with the standard format of
%X{mdcKey}.
val slf4j: Logger = LoggerFactory.getLogger(JvmClogSlf4jTest::class.java)
MDC.put("akey", "avalue")
slf4j.trace("message %X{akey}") // logs 'message avalue' to the Clog loggerClog is designed to work out-of-the-box with absolutely zero config required to start logging with it. However, you can
customize all components of Clog to your needs. Clog is comprised of several components wrapped in a ClogProfile,
which is the global instance of Clog.getInstance(). You can customize your Clog by creating a new Profile with your
custom components:
val newProfile = ClogProfile(...)
Clog.setProfile(newProfile)You can also use the Clog.updateProfile helper to create a profile based on the current global instance:
Clog.updateProfile { it.copy(logger = newLogger) }The table below describes the classes that can be customized in the ClogProfile, along with their default
implementation for each supported platform:
| Interface | Description | JVM | Android | JS | iOS |
|---|---|---|---|---|---|
ClogTagProvider |
Infers a tag if one is not provided to the logging call | DefaultTagProvider() |
DefaultTagProvider() |
DefaultTagProvider() |
DefaultTagProvider() |
ClogMessageFormatter |
Formats a message string to pass to the ClogLogger
|
Slf4jMessageFormatter(DefaultMessageFormatter()) |
DefaultMessageFormatter() |
DefaultMessageFormatter() |
DefaultMessageFormatter() |
ClogFilter |
Determines whether to format and log a message | DefaultFilter() |
DefaultFilter() |
DefaultFilter() |
DefaultFilter() |
ClogLogger |
Prints a formatted log to a lower-level platform-specific logger or console | DefaultLogger() |
AndroidLogger() |
JsConsoleLogger() |
NsLogger() |
| {.table} |
val isDebug = ...
Clog.configureLoggingInProduction(isDebug)Replaces the current logging target with a custom one.
val customLogger = object : ClogLogger {
override fun log(priority: Clog.Priority, tag: String?, message: String) {
...
}
override fun logException(priority: Clog.Priority, tag: String?, throwable: Throwable) {
...
}
}
Clog.updateProfile { it.copy(logger = customLogger) }Add an additional logger to the current instance. Calling addLogger multiple times will continue adding loggers, and
messages will be delegated to all loggers.
val customLogger = object : ClogLogger {
override fun log(priority: Clog.Priority, tag: String?, message: String) {
...
}
override fun logException(priority: Clog.Priority, tag: String?, throwable: Throwable) {
...
}
}
Clog.addLogger(customLogger)// Given some classes that depend on a logger
class Controller(val logger: ClogProfile)
// just declare a ClogProfile singleton with any configurations you need
val module = module {
single { ClogProfile() }
single { Controller(get()) }
}