Skip to content

Commit

Permalink
Logback backend for kotlin-logging
Browse files Browse the repository at this point in the history
  • Loading branch information
Neeme Praks committed Oct 24, 2024
1 parent 4452814 commit f5f7fe2
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ kotlin {
dependsOn(javaMain)
dependencies {
compileOnly("org.slf4j:slf4j-api:${extra["slf4j_version"]}")
compileOnly("ch.qos.logback:logback-classic:${extra["logback_version"]}")
compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:${extra["coroutines_version"]}")
}
}
Expand All @@ -132,6 +133,7 @@ kotlin {
implementation("org.apache.logging.log4j:log4j-core:${extra["log4j_version"]}")
implementation("org.apache.logging.log4j:log4j-slf4j2-impl:${extra["log4j_version"]}")
implementation("org.slf4j:slf4j-api:${extra["slf4j_version"]}")
implementation("ch.qos.logback:logback-classic:${extra["logback_version"]}")
// our jul test just forward the logs jul -> slf4j -> log4j
implementation("org.slf4j:jul-to-slf4j:${extra["slf4j_version"]}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:${extra["coroutines_version"]}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.oshai.kotlinlogging

public class KLoggingEventBuilder {
public var message: String? = null
public var messageTemplate: String? = null
public var cause: Throwable? = null
public var payload: Map<String, Any?>? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.oshai.kotlinlogging.internal

import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.jul.internal.JulLoggerFactory
import io.github.oshai.kotlinlogging.logback.internal.LogbackLoggerFactory
import io.github.oshai.kotlinlogging.slf4j.internal.Slf4jLoggerFactory

/** factory methods to obtain a [KLogger] */
Expand All @@ -12,6 +13,9 @@ internal actual object KLoggerFactory {
if (System.getProperty("kotlin-logging-to-jul") != null) {
return JulLoggerFactory.wrapJLogger(JulLoggerFactory.jLogger(name))
}
else if (System.getProperty("kotlin-logging-to-logback") != null) {
return LogbackLoggerFactory.wrapJLogger(LogbackLoggerFactory.jLogger(name))
}
// default to slf4j
return Slf4jLoggerFactory.wrapJLogger(Slf4jLoggerFactory.jLogger(name))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.oshai.kotlinlogging.logback

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.spi.LogbackServiceProvider
import io.github.oshai.kotlinlogging.Level.DEBUG
import io.github.oshai.kotlinlogging.Level.ERROR
import io.github.oshai.kotlinlogging.Level.INFO
import io.github.oshai.kotlinlogging.Level.OFF
import io.github.oshai.kotlinlogging.Level.TRACE
import io.github.oshai.kotlinlogging.Level.WARN
import io.github.oshai.kotlinlogging.Marker
import io.github.oshai.kotlinlogging.slf4j.internal.Slf4jMarker

public fun io.github.oshai.kotlinlogging.Level.toLogbackLevel(): Level {
val logbackLevel: Level =
when (this) {
TRACE -> Level.TRACE
DEBUG -> Level.DEBUG
INFO -> Level.INFO
WARN -> Level.WARN
ERROR -> Level.ERROR
OFF -> Level.OFF
}
return logbackLevel
}

public fun Marker.toLogback(logbackServiceProvider: LogbackServiceProvider): org.slf4j.Marker =
when (this) {
is Slf4jMarker -> marker
else -> logbackServiceProvider.markerFactory.getMarker(getName())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.oshai.kotlinlogging.logback.internal

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.LoggingEvent

public class LogbackLogEvent(
fqcn: String,
logger: Logger,
level: Level,
message: String?,
private val finalFormattedMessage: String?,
throwable: Throwable?,
argArray: Array<Any>
) : LoggingEvent(fqcn, logger, level, message, throwable, argArray) {

override fun getFormattedMessage(): String? {
return finalFormattedMessage
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.oshai.kotlinlogging.logback.internal

import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.spi.LogbackServiceProvider
import io.github.oshai.kotlinlogging.KLogger

internal object LogbackLoggerFactory {

private val logbackServiceProvider = createLogbackServiceProvider()

private fun createLogbackServiceProvider(): LogbackServiceProvider {
val logbackServiceProvider = LogbackServiceProvider()
logbackServiceProvider.initialize()
return logbackServiceProvider
}

/** get a java logger by name. Logback relies on SLF4J logger factory */
internal fun jLogger(name: String): Logger = logbackServiceProvider.loggerFactory.getLogger(name) as Logger

/** wrap java logger based on location awareness */
internal fun wrapJLogger(jLogger: Logger): KLogger = LogbackLoggerWrapper(jLogger, logbackServiceProvider)

fun getLoggerContext() = logbackServiceProvider.loggerFactory as LoggerContext

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.github.oshai.kotlinlogging.logback.internal

import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.LogbackServiceProvider
import io.github.oshai.kotlinlogging.DelegatingKLogger
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KLoggingEventBuilder
import io.github.oshai.kotlinlogging.Level
import io.github.oshai.kotlinlogging.Marker
import io.github.oshai.kotlinlogging.logback.toLogback
import io.github.oshai.kotlinlogging.logback.toLogbackLevel
import io.github.oshai.kotlinlogging.slf4j.internal.LocationAwareKLogger
import org.slf4j.event.KeyValuePair

internal class LogbackLoggerWrapper(
override val underlyingLogger: Logger,
private val logbackServiceProvider: LogbackServiceProvider
) : KLogger, DelegatingKLogger<Logger> {

override val name: String
get() = underlyingLogger.name

private val fqcn: String = LocationAwareKLogger::class.java.name

override fun at(level: Level, marker: Marker?, block: KLoggingEventBuilder.() -> Unit) {
if (isLoggingEnabledFor(level, marker)) {
KLoggingEventBuilder().apply(block).run {
val logbackEvent = LogbackLogEvent(
fqcn = fqcn,
logger = underlyingLogger,
level = level.toLogbackLevel(),
message = messageTemplate ?: message,
finalFormattedMessage = message,
throwable = cause,
argArray = emptyArray()
)
marker?.toLogback(logbackServiceProvider)?.let { logbackEvent.addMarker(it) }
payload?.forEach { (key, value) -> logbackEvent.addKeyValuePair(KeyValuePair(key, value)) }
underlyingLogger.callAppenders(logbackEvent)
}
}
}

override fun isLoggingEnabledFor(level: Level, marker: Marker?)
= underlyingLogger.isEnabledFor(marker?.toLogback(logbackServiceProvider), level.toLogbackLevel())

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.github.oshai.kotlinlogging.logback.internal

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.OutputStreamAppender
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import java.io.ByteArrayOutputStream

class LogbackLoggerWrapperTest {

companion object {
private lateinit var logger: KLogger
private lateinit var warnLogger: KLogger
private lateinit var errorLogger: KLogger
private lateinit var logOutputStream: ByteArrayOutputStream
private lateinit var appender: OutputStreamAppender<ILoggingEvent>
private lateinit var rootLogger: Logger

@BeforeAll
@JvmStatic
fun init() {
val loggerContext = LogbackLoggerFactory.getLoggerContext()
loggerContext.reset()
System.setProperty("kotlin-logging-to-logback", "true")

val encoder = PatternLayoutEncoder()
encoder.context = loggerContext
encoder.pattern = "%-5p %c %marker - %m%n"
encoder.charset = Charsets.UTF_8
encoder.start()

logOutputStream = ByteArrayOutputStream()
appender = OutputStreamAppender<ILoggingEvent>()
appender.context = loggerContext
appender.encoder = encoder
appender.outputStream = logOutputStream
appender.start()

rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME)
rootLogger.addAppender(appender)
rootLogger.level = Level.TRACE

logger = KotlinLogging.logger {}
warnLogger = KotlinLogging.logger("warnLogger")
loggerContext.getLogger("warnLogger").level = Level.WARN
errorLogger = KotlinLogging.logger("errorLogger")
loggerContext.getLogger("errorLogger").level = Level.ERROR
}

@AfterAll
@JvmStatic
fun teardown() {
System.clearProperty("kotlin-logging-to-logback")
val loggerContext = LogbackLoggerFactory.getLoggerContext()
loggerContext.reset()
}
}

@Test
fun testLogbackLogger() {
assertTrue(logger is LogbackLoggerWrapper)
assertTrue(warnLogger is LogbackLoggerWrapper)
assertTrue(errorLogger is LogbackLoggerWrapper)
logger.info { "simple logback info message" }
warnLogger.warn { "simple logback warn message" }
errorLogger.error { "simple logback error message" }
val lines =
logOutputStream.toByteArray().toString(Charsets.UTF_8)
.trim()
.replace("\r", "\n")
.replace("\n\n", "\n")
.split("\n")
assertEquals(
"INFO io.github.oshai.kotlinlogging.logback.internal.LogbackLoggerWrapperTest - simple logback info message",
lines[0],
)
assertEquals("WARN warnLogger - simple logback warn message", lines[1])
assertEquals("ERROR errorLogger - simple logback error message", lines[2])
}
}
1 change: 1 addition & 0 deletions versions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ extra["coroutines_version"] = "1.8.0"
extra["log4j_version"] = "2.22.0"
extra["mockito_version"] = "4.11.0"
extra["junit_version"] = "5.9.2"
extra["logback_version"] = "1.5.11"

0 comments on commit f5f7fe2

Please sign in to comment.