Skip to content

Commit

Permalink
Add tracing span support and refactor logger levels
Browse files Browse the repository at this point in the history
Introduced tracing span support for logging events, enabling better traceability. Refactored various logger methods and internal structures to accommodate the new span parameter. Moved appender classes under the `impl` package for better project organization.
  • Loading branch information
smyrgeorge committed Oct 20, 2024
1 parent c145b51 commit 8a9227b
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 52 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ val parent: TracingEvent.Span = trace.span("parent")
trace.span("test", parent) {
// Send events that are related to the current span.
it.event(name = "event-1", level = Level.DEBUG)
it.debug(name = "event-1") // Same as event(name = "event-1", level = Level.DEBUG)
// Include attributes in the event.
it.event(name = "event-2", attributes = mapOf("key" to "value"))
it.event(name = "event-2") { attrs: MutableMap<String, Any?> ->
Expand Down
56 changes: 36 additions & 20 deletions log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/Logger.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
package io.github.smyrgeorge.log4k

import io.github.smyrgeorge.log4k.TracingEvent.Span
import io.github.smyrgeorge.log4k.impl.registry.LoggerRegistry
import kotlin.reflect.KClass

@Suppress("unused", "MemberVisibilityCanBePrivate")
abstract class Logger(
override val name: String,
private var level: Level
override var level: Level
) : LoggerRegistry.Collector {
private var levelBeforeMute: Level = level
private lateinit var levelBeforeMute: Level

private fun log(level: Level, msg: String, args: Array<out Any?>) =
log(level, msg, null, args)

private fun log(level: Level, msg: String, throwable: Throwable?, args: Array<out Any?>) {
private fun log(level: Level, span: Span?, msg: String, args: Array<out Any?>) = log(level, span, msg, null, args)
private fun log(level: Level, span: Span?, msg: String, throwable: Throwable?, args: Array<out Any?>) {
if (!level.shouldLog()) return
val event = toLoggingEvent(level, msg, throwable, args)
val event = toLoggingEvent(level, span, msg, throwable, args)
RootLogger.log(event)
}

abstract fun toLoggingEvent(level: Level, msg: String, throwable: Throwable?, args: Array<out Any?>): LoggingEvent
abstract fun toLoggingEvent(
level: Level,
span: Span?,
msg: String,
throwable: Throwable?,
args: Array<out Any?>
): LoggingEvent

private fun Level.shouldLog(): Boolean =
ordinal >= level.ordinal

override fun getLevel(): Level = level

override fun setLevel(level: Level) {
this.level = level
}

override fun mute() {
levelBeforeMute = level
level = Level.OFF
Expand All @@ -41,17 +40,34 @@ abstract class Logger(
}

fun trace(f: () -> String): Unit = if (Level.TRACE.shouldLog()) trace(f()) else Unit
fun trace(msg: String, vararg args: Any?): Unit = log(Level.TRACE, msg, args)
fun debug(f: () -> String): Unit = if (Level.DEBUG.shouldLog()) debug(f()) else Unit
fun debug(msg: String, vararg args: Any?): Unit = log(Level.DEBUG, msg, args)
fun info(f: () -> String): Unit = if (Level.INFO.shouldLog()) info(f()) else Unit
fun info(msg: String, vararg args: Any?): Unit = log(Level.INFO, msg, args)
fun warn(f: () -> String): Unit = if (Level.WARN.shouldLog()) warn(f()) else Unit
fun warn(msg: String, vararg args: Any?): Unit = log(Level.WARN, msg, args)
fun error(f: () -> String?): Unit = if (Level.ERROR.shouldLog()) error(f()) else Unit
fun error(msg: String?, vararg args: Any?): Unit = log(Level.ERROR, msg ?: "", args)
fun error(t: Throwable, f: () -> String?): Unit = if (Level.ERROR.shouldLog()) error(f(), t) else Unit
fun error(msg: String?, t: Throwable, vararg args: Any?): Unit = log(Level.ERROR, msg ?: "", t, args)

fun trace(span: Span, f: () -> String): Unit = if (Level.TRACE.shouldLog()) trace(span, f()) else Unit
fun debug(span: Span, f: () -> String): Unit = if (Level.DEBUG.shouldLog()) debug(span, f()) else Unit
fun info(span: Span, f: () -> String): Unit = if (Level.INFO.shouldLog()) info(span, f()) else Unit
fun warn(span: Span, f: () -> String): Unit = if (Level.WARN.shouldLog()) warn(span, f()) else Unit
fun error(span: Span, f: () -> String?): Unit = if (Level.ERROR.shouldLog()) error(span, f()) else Unit
fun error(span: Span, t: Throwable, f: () -> String?): Unit =
if (Level.ERROR.shouldLog()) error(span, f(), t) else Unit

fun trace(msg: String, vararg args: Any?): Unit = log(Level.TRACE, null, msg, args)
fun debug(msg: String, vararg args: Any?): Unit = log(Level.DEBUG, null, msg, args)
fun info(msg: String, vararg args: Any?): Unit = log(Level.INFO, null, msg, args)
fun warn(msg: String, vararg args: Any?): Unit = log(Level.WARN, null, msg, args)
fun error(msg: String?, vararg args: Any?): Unit = log(Level.ERROR, null, msg ?: "", args)
fun error(msg: String?, t: Throwable, vararg args: Any?): Unit = log(Level.ERROR, null, msg ?: "", t, args)

fun trace(span: Span, msg: String, vararg args: Any?): Unit = log(Level.TRACE, span, msg, args)
fun debug(span: Span, msg: String, vararg args: Any?): Unit = log(Level.DEBUG, span, msg, args)
fun info(span: Span, msg: String, vararg args: Any?): Unit = log(Level.INFO, span, msg, args)
fun warn(span: Span, msg: String, vararg args: Any?): Unit = log(Level.WARN, span, msg, args)
fun error(span: Span, msg: String?, vararg args: Any?): Unit = log(Level.ERROR, span, msg ?: "", args)
fun error(span: Span, msg: String?, t: Throwable, vararg args: Any?): Unit =
log(Level.ERROR, span, msg ?: "", t, args)

companion object {
fun of(name: String): Logger = RootLogger.Logging.factory.get(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.datetime.Instant
interface LoggingEvent {
var id: Long
val level: Level
val span: TracingEvent.Span?
val timestamp: Instant
val logger: String
val message: String
Expand Down
16 changes: 8 additions & 8 deletions log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/Tracer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ import kotlin.reflect.KClass
@Suppress("unused", "MemberVisibilityCanBePrivate")
abstract class Tracer(
override val name: String,
private var level: Level
override var level: Level
) : LoggerRegistry.Collector {
private var levelBeforeMute: Level = level

override fun getLevel(): Level = level

override fun setLevel(level: Level) {
this.level = level
}
private lateinit var levelBeforeMute: Level

override fun mute() {
levelBeforeMute = level
Expand All @@ -36,6 +30,9 @@ abstract class Tracer(
val span = span(name, parent).start()
return try {
f(span)
} catch (e: Throwable) {
span.end(e)
throw e
} finally {
span.end()
}
Expand All @@ -51,6 +48,9 @@ abstract class Tracer(
val span = span(id, name, parent).start()
return try {
f(span)
} catch (e: Throwable) {
span.end(e)
throw e
} finally {
span.end()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface TracingEvent {
val tracer: String
val thread: String?

@Suppress("unused")
class Span(
val id: String,
val name: String,
Expand All @@ -22,7 +23,7 @@ interface TracingEvent {
val tracer: Tracer,
) {
private fun shouldLog(level: Level): Boolean =
level.ordinal >= tracer.getLevel().ordinal
level.ordinal >= tracer.level.ordinal

private val mutex = Mutex()
private var idx: Int = 0
Expand All @@ -32,7 +33,7 @@ interface TracingEvent {

fun start(): Span = withLock {
started = true
if (!shouldLog(tracer.getLevel())) return@withLock this
if (!shouldLog(tracer.level)) return@withLock this
val event = Start(
id = id,
name = name,
Expand All @@ -44,19 +45,29 @@ interface TracingEvent {
this
}

fun event(name: String, f: (MutableMap<String, Any?>) -> Unit): Unit =
event(name, tracer.getLevel(), f)
fun trace(name: String, f: (MutableMap<String, Any?>) -> Unit) = event(name, Level.TRACE, f)
fun debug(name: String, f: (MutableMap<String, Any?>) -> Unit) = event(name, Level.DEBUG, f)
fun info(name: String, f: (MutableMap<String, Any?>) -> Unit) = event(name, Level.INFO, f)
fun warn(name: String, f: (MutableMap<String, Any?>) -> Unit) = event(name, Level.WARN, f)
fun error(name: String, f: (MutableMap<String, Any?>) -> Unit) = event(name, Level.ERROR, f)

fun trace(name: String, attrs: Map<String, Any?> = emptyMap()) = event(name, Level.TRACE, attrs)
fun debug(name: String, attrs: Map<String, Any?> = emptyMap()) = event(name, Level.DEBUG, attrs)
fun info(name: String, attrs: Map<String, Any?> = emptyMap()) = event(name, Level.INFO, attrs)
fun warn(name: String, attrs: Map<String, Any?> = emptyMap()) = event(name, Level.WARN, attrs)
fun error(name: String, attrs: Map<String, Any?> = emptyMap()) = event(name, Level.ERROR, attrs)

fun event(name: String, f: (MutableMap<String, Any?>) -> Unit) = event(name, tracer.level, f)
fun event(name: String, attrs: Map<String, Any?> = emptyMap()) = event(name, tracer.level, attrs)

fun event(name: String, level: Level, f: (MutableMap<String, Any?>) -> Unit) {
val attributes = mutableMapOf<String, Any?>()
f(attributes)
event(name, level, attributes)
mutableMapOf<String, Any?>().also {
f(it)
event(name, level, it)
}
}

fun event(name: String, attributes: Map<String, Any?> = emptyMap()): Unit =
event(name, tracer.getLevel(), attributes)

fun event(name: String, level: Level, attributes: Map<String, Any?> = emptyMap()): Unit = withLock {
fun event(name: String, level: Level, attrs: Map<String, Any?> = emptyMap()): Unit = withLock {
// If not started, return
if (!started) return@withLock
// If already ended, return.
Expand All @@ -66,7 +77,7 @@ interface TracingEvent {
id = "$id-${idx()}",
name = name,
spanId = id,
attributes = attributes,
attributes = attrs,
level = level,
tracer = tracer.name,
timestamp = Clock.System.now()
Expand All @@ -80,7 +91,7 @@ interface TracingEvent {
// If already ended, return.
if (closed) return@withLock
closed = true
if (!shouldLog(tracer.getLevel())) return@withLock
if (!shouldLog(tracer.level)) return@withLock
val event = End(
id = id,
level = level,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ package io.github.smyrgeorge.log4k.impl
import io.github.smyrgeorge.log4k.Level
import io.github.smyrgeorge.log4k.Logger
import io.github.smyrgeorge.log4k.LoggingEvent
import io.github.smyrgeorge.log4k.TracingEvent.Span
import kotlinx.datetime.Clock

class SimpleLogger(name: String, level: Level) : Logger(name, level) {
override fun toLoggingEvent(level: Level, msg: String, throwable: Throwable?, args: Array<out Any?>): LoggingEvent {
override fun toLoggingEvent(
level: Level,
span: Span?,
msg: String,
throwable: Throwable?,
args: Array<out Any?>
): LoggingEvent {
return SimpleLoggingEvent(
level = level,
span = span,
timestamp = Clock.System.now(),
logger = name,
message = msg,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package io.github.smyrgeorge.log4k.impl

import io.github.smyrgeorge.log4k.Level
import io.github.smyrgeorge.log4k.LoggingEvent
import io.github.smyrgeorge.log4k.TracingEvent
import kotlinx.datetime.Instant

class SimpleLoggingEvent(
override var id: Long = 0,
override val level: Level,
override val span: TracingEvent.Span?,
override val timestamp: Instant,
override val logger: String,
override val message: String,
override val arguments: Array<out Any?>,
override val thread: String? = null,
override val throwable: Throwable? = null,
override val thread: String?,
override val throwable: Throwable?,
) : LoggingEvent
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.smyrgeorge.log4k.appenders
package io.github.smyrgeorge.log4k.impl.appenders

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.smyrgeorge.log4k.appenders
package io.github.smyrgeorge.log4k.impl.appenders

import io.github.smyrgeorge.log4k.Appender
import io.github.smyrgeorge.log4k.Logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SimpleConsoleLoggingAppender : Appender<LoggingEvent> {
private fun LoggingEvent.format(): String {
var formatted = PATTERN
formatted = formatted.replace("%idx", id.toString())
formatted = formatted.replace(" [%span] ", span?.id?.let { " [$it] " } ?: " ")
formatted = formatted.replace("%d{HH:mm:ss.SSS}", timestamp.toString())
formatted = formatted.replace("%-5level", level.name.padEnd(5))
formatted = formatted.replace("%logger{36}", logger.take(36))
Expand All @@ -24,6 +25,6 @@ class SimpleConsoleLoggingAppender : Appender<LoggingEvent> {
}

companion object {
private const val PATTERN = "%idx %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"
private const val PATTERN = "%idx [%span] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class LoggerRegistry<T> where T : LoggerRegistry.Collector {

fun setLevel(clazz: KClass<*>, level: Level): Unit = setLevel(clazz.toName(), level)
fun setLevel(name: String, level: Level): Unit = mutex.witLock {
loggers[name]?.setLevel(level)
loggers[name]?.level = level
}

fun mute(clazz: KClass<*>): Unit = mute(clazz.toName())
Expand All @@ -45,9 +45,8 @@ class LoggerRegistry<T> where T : LoggerRegistry.Collector {

interface Collector {
val name: String
var level: Level
fun mute()
fun unmute()
fun getLevel(): Level
fun setLevel(level: Level)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.smyrgeorge.log4k

import io.github.smyrgeorge.log4k.appenders.BatchAppender
import io.github.smyrgeorge.log4k.impl.appenders.BatchAppender
import io.github.smyrgeorge.log4k.impl.appenders.SimpleConsoleTracingAppender
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -54,10 +54,12 @@ class MainTests {
val parent: TracingEvent.Span = trace.span("parent")
// Starts immediately the span.
trace.span("test", parent) {
log.info(it, "this is a test with span")
// Send events that are related to the current span.
it.event(name = "event-1", level = Level.DEBUG)
it.debug(name = "event-1") // Same as event(name = "event-1", level = Level.DEBUG)
// Include attributes in the event.
it.event(name = "event-2", attributes = mapOf("key" to "value"))
it.event(name = "event-2", attrs = mapOf("key" to "value"))
it.event(name = "event-2") { attrs ->
attrs["key"] = "value"
}
Expand Down

0 comments on commit 8a9227b

Please sign in to comment.