Skip to content

Commit

Permalink
Add JSON console logging support
Browse files Browse the repository at this point in the history
Introduced `SimpleJsonConsoleLoggingAppender` to provide JSON-formatted logs for simple console logging. Updated dependencies to include kotlinx.serialization and added documentation and examples for the new feature.
  • Loading branch information
smyrgeorge committed Oct 25, 2024
1 parent 276d36f commit cdaecff
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 4 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group = "io.github.smyrgeorge"
version = "0.15.0"
version = "0.16.0"

plugins {
alias(libs.plugins.dokka)
Expand Down
12 changes: 12 additions & 0 deletions examples/src/nativeMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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" }
1 change: 1 addition & 0 deletions log4k/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ kotlin {
val commonMain by getting {
dependencies {
api(libs.kotlinx.coroutines.core)
api(libs.kotlinx.serialisation.json)
api(libs.kotlinx.datetime)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LoggingEvent> {
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<String, Any?> {
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<JsonElement> = 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<String, JsonElement> = 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)
}
}
}

0 comments on commit cdaecff

Please sign in to comment.