diff --git a/README.md b/README.md index 7d81a8af..05f08fc4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ ![](https://img.shields.io/static/v1?label=&message=iOS&color=blue) ![](https://img.shields.io/static/v1?label=&message=Android&color=blue) - A Comprehensive Logging and Tracing Solution for Kotlin Multiplatform. This project provides a robust, event-driven logging and tracing platform specifically designed for Kotlin @@ -35,7 +34,6 @@ This project also tries to be fully compatible with `OpenTelemetry` standard. - [ ] Support for OpenTelemetry's Metrics (in progress) - [ ] `CoroutineContexAwareLogger`: `Logger` that will collect more info from the coroutine context (in progress) -- [ ] Json console logger (in progress) - [ ] Ability to chain appenders - [ ] `LogbackAppender`: `Appender` that will publish the logging events to the logback. @@ -146,6 +144,13 @@ log.error { e.message } log.error(e) { e.message } // e: Throwable ``` +### Json Appender + +```kotlin +// You can register the `SimpleJsonConsoleLoggingAppender` for json logs in the console. +RootLogger.Logging.appenders.register(SimpleJsonConsoleLoggingAppender()) +``` + ## Tracing API The tracing API is fully compatible with the `OpenTelemetry` standard, enabling seamless distributed tracing, metric diff --git a/build.gradle.kts b/build.gradle.kts index 23cd7dae..bbbcde71 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ group = "io.github.smyrgeorge" -version = "0.15.0" +version = "0.16.0" plugins { alias(libs.plugins.dokka) diff --git a/examples/src/nativeMain/kotlin/Main.kt b/examples/src/nativeMain/kotlin/Main.kt index fdff2dab..da0f0499 100644 --- a/examples/src/nativeMain/kotlin/Main.kt +++ b/examples/src/nativeMain/kotlin/Main.kt @@ -9,6 +9,7 @@ import io.github.smyrgeorge.log4k.impl.appenders.BatchAppender import io.github.smyrgeorge.log4k.impl.appenders.FlowFloodProtectedAppender import io.github.smyrgeorge.log4k.impl.appenders.simple.SimpleConsoleLoggingAppender.Companion.print import io.github.smyrgeorge.log4k.impl.appenders.simple.SimpleConsoleTracingAppender +import io.github.smyrgeorge.log4k.impl.appenders.simple.SimpleJsonConsoleLoggingAppender import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.async @@ -100,6 +101,17 @@ class Main { delay(2000) + RootLogger.Logging.appenders.unregisterAll() + RootLogger.Logging.appenders.register(SimpleJsonConsoleLoggingAppender()) + log.info { "This is a test json log" } + try { + error("An error occurred!") + } catch (e: Exception) { + log.error(e) { e.message } + } + + delay(2000) + RootLogger.Logging.appenders.unregisterAll() RootLogger.Logging.appenders.register( SimpleFloodProtectedAppender(requestPerSecond = 50, burstDurationMillis = 100) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0bb674ea..ebbb12ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,8 @@ kotlin = "2.0.21" kotlinx-datetime = "0.6.1" # https://github.com/Kotlin/kotlinx.coroutines kotlinx-coroutines = "1.9.0" +# https://github.com/Kotlin/kotlinx.serialization +kotlinx-serialisation = "1.7.3" # https://github.com/vanniktech/gradle-maven-publish-plugin publish = "0.30.0" # https://github.com/Kotlin/dokka @@ -18,10 +20,10 @@ gradle-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-serialisation-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialisation" } assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" } [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } pubhish = { id = "com.vanniktech.maven.publish", version.ref = "publish" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } diff --git a/log4k/build.gradle.kts b/log4k/build.gradle.kts index 3934701b..d61da392 100644 --- a/log4k/build.gradle.kts +++ b/log4k/build.gradle.kts @@ -11,6 +11,7 @@ kotlin { val commonMain by getting { dependencies { api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.serialisation.json) api(libs.kotlinx.datetime) } } diff --git a/log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/impl/appenders/simple/SimpleJsonConsoleLoggingAppender.kt b/log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/impl/appenders/simple/SimpleJsonConsoleLoggingAppender.kt new file mode 100644 index 00000000..ea11f085 --- /dev/null +++ b/log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/impl/appenders/simple/SimpleJsonConsoleLoggingAppender.kt @@ -0,0 +1,74 @@ +package io.github.smyrgeorge.log4k.impl.appenders.simple + +import io.github.smyrgeorge.log4k.Appender +import io.github.smyrgeorge.log4k.Level +import io.github.smyrgeorge.log4k.LoggingEvent +import io.github.smyrgeorge.log4k.impl.extensions.format +import io.github.smyrgeorge.log4k.impl.extensions.platformPrintlnError +import io.github.smyrgeorge.log4k.impl.extensions.toName +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive + +@Suppress("MemberVisibilityCanBePrivate") +class SimpleJsonConsoleLoggingAppender : Appender { + override val name: String = this::class.toName() + override suspend fun append(event: LoggingEvent) = event.printJson() + + companion object { + fun LoggingEvent.printJson() { + val message = formatJson() + if (level == Level.ERROR) platformPrintlnError(message) + else println(message) + } + + private fun LoggingEvent.formatJson(): String { + val map = buildMap { + if (id > 0) put("id", id) + put("level", level.name) + put("span_id", span?.context?.spanId) + put("trace_id", span?.context?.traceId) + put("timestamp", timestamp) + put("logger", logger) + put("message", message.format(arguments)) + put("thread", thread) + put("throwable", throwable?.stackTraceToString()) + } + return map.toJsonElement().toString() + } + + fun primitive(value: Any): JsonPrimitive = when (value) { + is Number -> JsonPrimitive(value) + is Boolean -> JsonPrimitive(value) + else -> JsonPrimitive(value.toString()) + } + + fun List<*>.toJsonElement(): JsonElement { + val list: MutableList = mutableListOf() + forEach { + val value = it ?: return@forEach + when (value) { + is Map<*, *> -> list.add((value).toJsonElement()) + is List<*> -> list.add(value.toJsonElement()) + else -> list.add(primitive(value)) + } + } + return JsonArray(list) + } + + fun Map<*, *>.toJsonElement(): JsonElement { + val map: MutableMap = mutableMapOf() + forEach { + val key = it.key as? String ?: return@forEach + val value = it.value ?: return@forEach + when (value) { + is Map<*, *> -> map[key] = (value).toJsonElement() + is List<*> -> map[key] = value.toJsonElement() + else -> map[key] = primitive(value) + } + } + return JsonObject(map) + } + } +} \ No newline at end of file