
Enhances logging capabilities with color-coded output, log rotation, and structured logging. Supports custom output locations including console, file, and streams, and allows integration with platforms like Slack.
COLOTOK; Code-Base Logging Runtime for Kotlin
✅ Print log with color
✅ Formatter
✅ Print log where you want
🌟 ConsoleProvider
🌟 FileProvider
🌟 StreamProvider
✅ Log Rotation
🌟 SizeBaseRotation
🌟 DateBaseRotation(; DurationBase)
✅ Customize output location
🌟 example print log into slack
✅ Structure Logging
✅ MDC (Mapped Diagnostic Context)
✅ Metrics Collection
🌟 Built-in metrics (Log count, Error count, Buffer size, Write duration)
🌟 Multiple collection strategies (Inherit, Explicit, Internal Logging)
🌟 Composite metrics (Collect to multiple destinations simultaneously)
basic dependency
dependencies {
// add this line
implementation("io.github.milkcocoa0902:colotok:0.4.2")
}or when you use kotlin multiplatform(;KMP)
commonMain.dependncies{
implementation("io.github.milkcocoa0902:colotok:0.4.2")
}
jvmMain.dependencies{
implementation("io.github.milkcocoa0902:colotok-jvm:0.4.2")
}
androidMain.dependencies{
implementation("io.github.milkcocoa0902:colotok-android:0.4.2")
}
jsMain.dependencies{
implementation("io.github.milkcocoa0902:colotok-js:0.4.2")
}Colotok provides several plugins to extend its functionality:
| plugin | artifact | feature | Platform |
|---|---|---|---|
| colotok-coroutines | io.github.milkcocoa0902:colotok-coroutines:0.4.2 |
coroutine support | Multi Platform |
| colotok-slf4j | io.github.milkcocoa0902:colotok-slf4j:0.4.2 |
SLF4J 1.7.x bindings (JVM only) | JVM |
| colotok-slf4j2 | io.github.milkcocoa0902:colotok-slf4j2:0.4.2 |
SLF4J 2.x bindings (JVM only) | JVM |
| colotok-cloudwatch | io.github.milkcocoa0902:colotok-cloudwatch:0.4.2 |
send logs to AWS CloudWatch | JVM |
| colotok-loki | io.github.milkcocoa0902:colotok-loki:0.4.2 |
send logs to Grafana Loki | Multi Platform |
If you want to use Structured Logging or Internal Metrics Logging, you need to enable the Kotlin Serialization plugin in your project.
Colotok already includes the necessary serialization libraries, so you generally don't need to add them manually unless you want to use a specific version.
plugins {
// Required for @Serializable
kotlin("plugin.serialization") version "2.1.10"
}configure colotok with code.
see below.
val logger = ColotokLoggerContext()
.addProvider(ConsoleProvider())
.getLogger()
more details config
val fileProvider: FileProvider
val logger = ColotokLoggerContext()
.addProvider(ConsoleProvider(ConsoleProviderConfig().apply {
// show above info level in console
level = LogLevel.INFO
}))
.addProvider(FileProvider(File("test.log").toOkioPath()){
level = LogLevel.INFO
// use size base rotation
rotation = SizeBaseRotation(size = 4096L)
}.apply {
fileProvider = this
}).getLogger()
logger.trace("TRACE LEVEL LOG")
logger.debug("DEBUG LEVEL LOG")
logger.info("INFO LEVEL LOG")
logger.warn("WARN LEVEL LOG")
logger.error("ERROR LEVEL LOG")now, you can print log into your space.
logger.trace("TRACE LEVEL LOG")
logger.debug("DEBUG LEVEL LOG")
logger.info("INFO LEVEL LOG")
logger.warn("WARN LEVEL LOG")
logger.error("ERROR LEVEL LOG")
logger.atInfo {
print("in this block")
print("all of logs are printed out with INFO level")
}
// or you can add additional parameters
logger.info("INFO LEVEL LOG", mapOf("param1" to "a custom attr"))colotok has builtin text formatter.
this formatter shows as below style's log
logger.info("message what happen")
// message what happen
this formatter shows as below style's log
logger.info("message what happen")
// 2023-12-29 12:23:14.220383 [INFO] - message what happenthis formatter shows as below style's log
logger.ingo("message what happen", mapOf("param1" to "a custom attribute"))
// 2023-12-29T12:21:13.354328+09:00 (main)[INFO] - message what happen, additional = {param1=a custom attribute}colotok has builtin structured formatter.
if you has a class
@Serializable
class LogDetail(val scope: String, val message: String): LogStructure
@Serializable
class Log(val name: String, val logDetail: LogDetail): LogStructurethis formatter shows bellow style's log
logger.info(
Log(
name = "illegal state",
LogDetail(
"args",
"argument must be greater than zero"
)
)
)
// // {"message":{"name":"illegal state","logDetail":{"scope":"args","message":"argument must be greater than zero"}},"level":"INFO","date":"2023-12-29"}
logger.info("message what happen")
// {"msg":"message what happen","level":"INFO","date":"2023-12-29"}this formatter shows bellow style's log
logger.info(
Log(
name = "illegal state",
LogDetail(
"args",
"argument must be greater than zero"
)
),
// you can pass additional attrs
mapOf("additional" to "additional param")
)
// {"message":{"name":"illegal state","logDetail":{"scope":"args","message":"argument must be greater than zero"}},"level":"INFO","additional":"additional param","date":"2023-12-29T12:34:56"}
logger.info("message what happen")
// {"message":"message what happen","level":"INFO","thread":"main","date":"2023-12-29T12:27:22.5908"}colotok has builtin provider. Provider is used for output log.
this provider outputs log into console with ansi-color
this provider output log into file without ansi-color.
this provider output log into stream where you specified.
you can also output to remote or sql or others by create own provider.
example
if you want to write log into slack, you create a SlackProvider like this
@Serializable
data class SlackWebhookPayload(
val text: String,
)
class SlackProvider(config: SlackProviderConfig): Provider {
constructor(config: SlackProviderConfig.() -> Unit): this(SlackProviderConfig().apply(config))
class SlackProviderConfig() : ProviderConfig {
var webhook_url: String = ""
override var level: level = LogLevel.DEBUG
override var formatter: Formatter = DetailTextFormatter
}
private val webhookUrl = config.webhook_url
private val logLevel = config.level
private val formatter = config.formatter
override fun write(name: String, msg: String, level: LogLevel, attr: Map<String, String>) {
if(level.isEnabledFor(logLevel).not()){
return
}
kotlin.runCatching {
webhookUrl.httpPost()
.appendHeader("Content-Type" to "application/json")
.body(Json.encodeToString(text = formatter.format(msg, level, attr)))
.response()
}.getOrElse { println(it) }
}
override fun <T : LogStructure> write(
name: String,
msg: T,
serializer: KSerializer<T>,
level: LogLevel,
attr: Map<String, String>
) {
if(level.isEnabledFor(logLevel).not()){
return
}
kotlin.runCatching {
webhookUrl.httpPost()
.appendHeader("Content-Type" to "application/json")
.body(Json.encodeToString(text = formatter.format(msg, serializer, level, attr)))
.response()
}.getOrElse { println(it) }
}
}now you can use SlackProvider to write the log into slack.
val logger = ColotokLoggerContext()
.addProvider(ConsoleProvider(ConsoleProviderConfig().apply {
formatter = DetailTextFormatter
level = LogLevel.DEBUG
}))
.addProvider(SlackProvider{
webhook_url = "your slack webhook url"
formatter = SimpleTextFormatter
level = LogLevel.WARN
})
.getLogger()logger.info("info level log")
// written the log only console
logger.error("error level log")
// written the log both of console and slackColotok supports MDC functionality since version 0.3.0, which allows you to add contextual information to your logs. MDC is designed to be coroutine-friendly.
MDC replace the Element.CUSTOM when formatting.
// Set MDC values
MDC.put("requestId", "12345")
MDC.put("userId", "user-abc")
// Log with MDC context
logger.info("Processing request") // MDC values will be included automatically
// Clear specific MDC value
MDC.remove("userId")
// Clear all MDC values
MDC.clear()
// Using MDC with coroutines
// On JVM platform
suspend fun processRequest() {
withMdcScope(Dispatchers.IO) {
MDC.put("requestId", "12345")
// MDC context is preserved across coroutine boundaries
someAsyncOperation()
}
}
// On JS platform
fun processRequest() {
MDC.withContext {
MDC.put("requestId", "12345")
// MDC context is preserved within this block
someOperation()
}
}
// Alternative syntax on JS
fun processRequest() {
withMdcScope {
MDC.put("requestId", "12345")
// MDC context is preserved within this block
someOperation()
}
}Colotok can collect metrics about its operation.
val logger = ColotokLoggerContext()
.withMetrics(CustomMetricsCollector()) // Global metrics collector
.addProvider(ConsoleProvider {
// This provider will inherit the global collector
metricsSpec = MetricsCollectorSpec.Inherit
})
.addProvider(LokiProvider {
// This provider logs metrics to itself as LogRecord.Metrics
enableInternalMetricsLogging = true
// And also reports to an explicit collector, inheriting the global one too
metricsSpec = MetricsCollectorSpec.Explicit(lokiCollector, inheritParent = true)
})
.getLogger()Since version 0.4.2, Colotok supports explicit shutdown to ensure all logs are flushed and resources are released.
val context = ColotokLoggerContext()
.addProvider(FileProvider(path))
val logger = context.getLogger()
// ... logging ...
// Shutdown the context
context.shutdown()
// Or force shutdown if you want to close immediately
context.forceShutdown()https://milkcocoa0902.github.io/colotok/01-colotok-introduce.html
COLOTOK; Code-Base Logging Runtime for Kotlin
✅ Print log with color
✅ Formatter
✅ Print log where you want
🌟 ConsoleProvider
🌟 FileProvider
🌟 StreamProvider
✅ Log Rotation
🌟 SizeBaseRotation
🌟 DateBaseRotation(; DurationBase)
✅ Customize output location
🌟 example print log into slack
✅ Structure Logging
✅ MDC (Mapped Diagnostic Context)
✅ Metrics Collection
🌟 Built-in metrics (Log count, Error count, Buffer size, Write duration)
🌟 Multiple collection strategies (Inherit, Explicit, Internal Logging)
🌟 Composite metrics (Collect to multiple destinations simultaneously)
basic dependency
dependencies {
// add this line
implementation("io.github.milkcocoa0902:colotok:0.4.2")
}or when you use kotlin multiplatform(;KMP)
commonMain.dependncies{
implementation("io.github.milkcocoa0902:colotok:0.4.2")
}
jvmMain.dependencies{
implementation("io.github.milkcocoa0902:colotok-jvm:0.4.2")
}
androidMain.dependencies{
implementation("io.github.milkcocoa0902:colotok-android:0.4.2")
}
jsMain.dependencies{
implementation("io.github.milkcocoa0902:colotok-js:0.4.2")
}Colotok provides several plugins to extend its functionality:
| plugin | artifact | feature | Platform |
|---|---|---|---|
| colotok-coroutines | io.github.milkcocoa0902:colotok-coroutines:0.4.2 |
coroutine support | Multi Platform |
| colotok-slf4j | io.github.milkcocoa0902:colotok-slf4j:0.4.2 |
SLF4J 1.7.x bindings (JVM only) | JVM |
| colotok-slf4j2 | io.github.milkcocoa0902:colotok-slf4j2:0.4.2 |
SLF4J 2.x bindings (JVM only) | JVM |
| colotok-cloudwatch | io.github.milkcocoa0902:colotok-cloudwatch:0.4.2 |
send logs to AWS CloudWatch | JVM |
| colotok-loki | io.github.milkcocoa0902:colotok-loki:0.4.2 |
send logs to Grafana Loki | Multi Platform |
If you want to use Structured Logging or Internal Metrics Logging, you need to enable the Kotlin Serialization plugin in your project.
Colotok already includes the necessary serialization libraries, so you generally don't need to add them manually unless you want to use a specific version.
plugins {
// Required for @Serializable
kotlin("plugin.serialization") version "2.1.10"
}configure colotok with code.
see below.
val logger = ColotokLoggerContext()
.addProvider(ConsoleProvider())
.getLogger()
more details config
val fileProvider: FileProvider
val logger = ColotokLoggerContext()
.addProvider(ConsoleProvider(ConsoleProviderConfig().apply {
// show above info level in console
level = LogLevel.INFO
}))
.addProvider(FileProvider(File("test.log").toOkioPath()){
level = LogLevel.INFO
// use size base rotation
rotation = SizeBaseRotation(size = 4096L)
}.apply {
fileProvider = this
}).getLogger()
logger.trace("TRACE LEVEL LOG")
logger.debug("DEBUG LEVEL LOG")
logger.info("INFO LEVEL LOG")
logger.warn("WARN LEVEL LOG")
logger.error("ERROR LEVEL LOG")now, you can print log into your space.
logger.trace("TRACE LEVEL LOG")
logger.debug("DEBUG LEVEL LOG")
logger.info("INFO LEVEL LOG")
logger.warn("WARN LEVEL LOG")
logger.error("ERROR LEVEL LOG")
logger.atInfo {
print("in this block")
print("all of logs are printed out with INFO level")
}
// or you can add additional parameters
logger.info("INFO LEVEL LOG", mapOf("param1" to "a custom attr"))colotok has builtin text formatter.
this formatter shows as below style's log
logger.info("message what happen")
// message what happen
this formatter shows as below style's log
logger.info("message what happen")
// 2023-12-29 12:23:14.220383 [INFO] - message what happenthis formatter shows as below style's log
logger.ingo("message what happen", mapOf("param1" to "a custom attribute"))
// 2023-12-29T12:21:13.354328+09:00 (main)[INFO] - message what happen, additional = {param1=a custom attribute}colotok has builtin structured formatter.
if you has a class
@Serializable
class LogDetail(val scope: String, val message: String): LogStructure
@Serializable
class Log(val name: String, val logDetail: LogDetail): LogStructurethis formatter shows bellow style's log
logger.info(
Log(
name = "illegal state",
LogDetail(
"args",
"argument must be greater than zero"
)
)
)
// // {"message":{"name":"illegal state","logDetail":{"scope":"args","message":"argument must be greater than zero"}},"level":"INFO","date":"2023-12-29"}
logger.info("message what happen")
// {"msg":"message what happen","level":"INFO","date":"2023-12-29"}this formatter shows bellow style's log
logger.info(
Log(
name = "illegal state",
LogDetail(
"args",
"argument must be greater than zero"
)
),
// you can pass additional attrs
mapOf("additional" to "additional param")
)
// {"message":{"name":"illegal state","logDetail":{"scope":"args","message":"argument must be greater than zero"}},"level":"INFO","additional":"additional param","date":"2023-12-29T12:34:56"}
logger.info("message what happen")
// {"message":"message what happen","level":"INFO","thread":"main","date":"2023-12-29T12:27:22.5908"}colotok has builtin provider. Provider is used for output log.
this provider outputs log into console with ansi-color
this provider output log into file without ansi-color.
this provider output log into stream where you specified.
you can also output to remote or sql or others by create own provider.
example
if you want to write log into slack, you create a SlackProvider like this
@Serializable
data class SlackWebhookPayload(
val text: String,
)
class SlackProvider(config: SlackProviderConfig): Provider {
constructor(config: SlackProviderConfig.() -> Unit): this(SlackProviderConfig().apply(config))
class SlackProviderConfig() : ProviderConfig {
var webhook_url: String = ""
override var level: level = LogLevel.DEBUG
override var formatter: Formatter = DetailTextFormatter
}
private val webhookUrl = config.webhook_url
private val logLevel = config.level
private val formatter = config.formatter
override fun write(name: String, msg: String, level: LogLevel, attr: Map<String, String>) {
if(level.isEnabledFor(logLevel).not()){
return
}
kotlin.runCatching {
webhookUrl.httpPost()
.appendHeader("Content-Type" to "application/json")
.body(Json.encodeToString(text = formatter.format(msg, level, attr)))
.response()
}.getOrElse { println(it) }
}
override fun <T : LogStructure> write(
name: String,
msg: T,
serializer: KSerializer<T>,
level: LogLevel,
attr: Map<String, String>
) {
if(level.isEnabledFor(logLevel).not()){
return
}
kotlin.runCatching {
webhookUrl.httpPost()
.appendHeader("Content-Type" to "application/json")
.body(Json.encodeToString(text = formatter.format(msg, serializer, level, attr)))
.response()
}.getOrElse { println(it) }
}
}now you can use SlackProvider to write the log into slack.
val logger = ColotokLoggerContext()
.addProvider(ConsoleProvider(ConsoleProviderConfig().apply {
formatter = DetailTextFormatter
level = LogLevel.DEBUG
}))
.addProvider(SlackProvider{
webhook_url = "your slack webhook url"
formatter = SimpleTextFormatter
level = LogLevel.WARN
})
.getLogger()logger.info("info level log")
// written the log only console
logger.error("error level log")
// written the log both of console and slackColotok supports MDC functionality since version 0.3.0, which allows you to add contextual information to your logs. MDC is designed to be coroutine-friendly.
MDC replace the Element.CUSTOM when formatting.
// Set MDC values
MDC.put("requestId", "12345")
MDC.put("userId", "user-abc")
// Log with MDC context
logger.info("Processing request") // MDC values will be included automatically
// Clear specific MDC value
MDC.remove("userId")
// Clear all MDC values
MDC.clear()
// Using MDC with coroutines
// On JVM platform
suspend fun processRequest() {
withMdcScope(Dispatchers.IO) {
MDC.put("requestId", "12345")
// MDC context is preserved across coroutine boundaries
someAsyncOperation()
}
}
// On JS platform
fun processRequest() {
MDC.withContext {
MDC.put("requestId", "12345")
// MDC context is preserved within this block
someOperation()
}
}
// Alternative syntax on JS
fun processRequest() {
withMdcScope {
MDC.put("requestId", "12345")
// MDC context is preserved within this block
someOperation()
}
}Colotok can collect metrics about its operation.
val logger = ColotokLoggerContext()
.withMetrics(CustomMetricsCollector()) // Global metrics collector
.addProvider(ConsoleProvider {
// This provider will inherit the global collector
metricsSpec = MetricsCollectorSpec.Inherit
})
.addProvider(LokiProvider {
// This provider logs metrics to itself as LogRecord.Metrics
enableInternalMetricsLogging = true
// And also reports to an explicit collector, inheriting the global one too
metricsSpec = MetricsCollectorSpec.Explicit(lokiCollector, inheritParent = true)
})
.getLogger()Since version 0.4.2, Colotok supports explicit shutdown to ensure all logs are flushed and resources are released.
val context = ColotokLoggerContext()
.addProvider(FileProvider(path))
val logger = context.getLogger()
// ... logging ...
// Shutdown the context
context.shutdown()
// Or force shutdown if you want to close immediately
context.forceShutdown()https://milkcocoa0902.github.io/colotok/01-colotok-introduce.html