Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logback backend for kotlin-logging #452

Merged
merged 7 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -4,4 +4,13 @@ public class KLoggingEventBuilder {
public var message: String? = null
public var cause: Throwable? = null
public var payload: Map<String, Any?>? = null
public var internalCompilerData: InternalCompilerData? = null
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please document that this field is internal and should not be set

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added JavaDoc


public class InternalCompilerData(
public val messageTemplate: String? = null,
public val className: String? = null,
public val methodName: String? = null,
public val lineNumber: Int? = null,
public val fileName: String? = 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 @@ -11,6 +12,8 @@ internal actual object KLoggerFactory {
internal actual fun logger(name: String): KLogger {
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,48 @@
package io.github.oshai.kotlinlogging.logback.internal

import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.LoggingEvent
import io.github.oshai.kotlinlogging.KLoggingEventBuilder
import io.github.oshai.kotlinlogging.Level
import io.github.oshai.kotlinlogging.logback.toLogbackLevel

public class LogbackLogEvent(
fqcn: String,
logger: Logger,
level: Level,
private val kLoggingEvent: KLoggingEventBuilder,
) :
LoggingEvent(
fqcn,
logger,
level.toLogbackLevel(),
kLoggingEvent.internalCompilerData?.messageTemplate ?: kLoggingEvent.message,
oshai marked this conversation as resolved.
Show resolved Hide resolved
kLoggingEvent.cause,
emptyArray(),
) {

override fun getFormattedMessage(): String? {
return kLoggingEvent.message
}

override fun getCallerData(): Array<StackTraceElement> =
if (kLoggingEvent.internalCompilerData?.fileName != null) {
arrayOf(
StackTraceElement(
kLoggingEvent.internalCompilerData?.className,
kLoggingEvent.internalCompilerData?.methodName,
kLoggingEvent.internalCompilerData?.fileName,
kLoggingEvent.internalCompilerData?.lineNumber ?: 0,
)
)
} else {
super.getCallerData()
}

override fun hasCallerData(): Boolean =
if (kLoggingEvent.internalCompilerData?.fileName != null) {
true
} else {
super.hasCallerData()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 */
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually here there is no difference in location awerness so this can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. And renamed methods and fields a bit.

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,44 @@
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,
kLoggingEvent = this,
)
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,89 @@
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 java.io.ByteArrayOutputStream
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test

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"
Loading