From 3ed804c9a6ef3577000ef6e79011e81ffd560af2 Mon Sep 17 00:00:00 2001 From: Artem Golovko Date: Mon, 3 Mar 2025 01:15:37 +0000 Subject: [PATCH 1/4] Issue #480 - Extend the way of how to create the logger --- .../oshai/kotlinlogging/KotlinLogging.kt | 48 ++-- .../internal/KLoggerNameResolver.kt | 2 +- .../internal/KLoggerNameResolver.kt | 2 +- .../internal/KLoggerNameResolver.kt | 21 +- .../oshai/kotlinlogging/KotlinLogging.kt | 29 --- .../internal/KLoggerNameResolver.kt | 48 +++- .../oshai/kotlinlogging/SimpleJsTest.kt | 212 +++++++++++++++-- .../internal/KLoggerNameResolver.kt | 2 +- .../internal/KLoggerNameResolver.kt | 53 +++-- .../oshai/kotlinlogging/SimpleWasmJsTest.kt | 220 ++++++++++++++++-- 10 files changed, 516 insertions(+), 121 deletions(-) delete mode 100644 src/jsMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt diff --git a/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt b/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt index fc350e4a..19c78770 100644 --- a/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt +++ b/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt @@ -2,24 +2,40 @@ package io.github.oshai.kotlinlogging import io.github.oshai.kotlinlogging.internal.KLoggerFactory import io.github.oshai.kotlinlogging.internal.KLoggerNameResolver +import kotlin.js.JsName +/** + * Since this library is intended to be multiplatform, + * there are several ways to create a logger depending on your programming language. + * + * Please note, that creating a logger by reference involves reflection or, in some cases, + * stack trace analysis. Therefore, if performance is a priority, avoid creating loggers + * in frequently executed code sections. + * + * A logger created by reference will take the name of the class of the reference. + * If the reference is a "companion object," the name of its enclosing class will be used instead. + * + * ```kotlin + * val topLevelNamedLogger = KotlinLogging.logger("TopLevelNamedLogger") + * val topLevelLambdaLogger = KotlinLogging.logger {} + * + * class MyClass { + * val classNamedLogger = KotlinLogging.logger("MyClass") + * val classLambdaLogger = KotlinLogging.logger {} + * val classRefLogger = KotlinLogging.logger(this) + * + * companion object { + * val companionNamedLogger = KotlinLogging.logger("MyClassCompanion") + * val companionLambdaLogger = KotlinLogging.logger {} + * val companionRefLogger = KotlinLogging.logger(this) + * } + * } + * ``` + */ public object KotlinLogging { - /** - * This method allow defining the logger in a file in the following way: - * ``` - * private val logger = KotlinLogging.logger {} - * ``` - */ - public fun logger(func: () -> Unit): KLogger = logger(KLoggerNameResolver.name(func)) + @JsName("kotlinLoggerByRef") + public fun logger(ref: Any): KLogger = logger(KLoggerNameResolver.name(ref)) - /** - * This method allow defining the logger in a file in the following way: - * ``` - * private val logger = KotlinLogging.logger("io.github.oshai.kotlinlogging.MyLogger") - * ``` - * - * In most cases the name represents the package notation of the file that the logger is defined - * in. - */ + @JsName("kotlinLoggerByName") public fun logger(name: String): KLogger = KLoggerFactory.logger(name) } diff --git a/src/commonMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/commonMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index cc252f25..87cc9be0 100644 --- a/src/commonMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/commonMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -2,5 +2,5 @@ package io.github.oshai.kotlinlogging.internal internal expect object KLoggerNameResolver { - internal fun name(func: () -> Unit): String + internal fun name(ref: Any): String } diff --git a/src/darwinMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/darwinMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index fb420756..95c4bf91 100644 --- a/src/darwinMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/darwinMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -2,5 +2,5 @@ package io.github.oshai.kotlinlogging.internal internal actual object KLoggerNameResolver { - internal actual fun name(func: () -> Unit): String = func::class.qualifiedName ?: "" + internal actual fun name(ref: Any): String = ref::class.qualifiedName ?: "" } diff --git a/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index c880446f..2c5bc5ce 100644 --- a/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -6,15 +6,20 @@ import java.lang.reflect.Modifier internal actual object KLoggerNameResolver { /** get class name for function by the package of the function */ - internal actual fun name(func: () -> Unit): String { - val name = func.javaClass.name - val slicedName = - when { - name.contains("Kt$") -> name.substringBefore("Kt$") - name.contains("$") -> name.substringBefore("$") - else -> name + internal actual fun name(ref: Any): String { + return ref::class.java.name.toCleanClassName() + } + + private val classNameEndings = listOf("Kt$", "$") + + private fun String.toCleanClassName(): String { + classNameEndings.forEach { ending -> + val indexOfEnding = this.indexOf(ending) + if (indexOfEnding != -1) { + return this.substring(0, indexOfEnding) } - return slicedName + } + return this } /** get class name for java class (that usually represents kotlin class) */ diff --git a/src/jsMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt b/src/jsMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt deleted file mode 100644 index 8a85526c..00000000 --- a/src/jsMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.oshai.kotlinlogging - -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -/** - * The JS way to define a logger without explicit name - * - * ``` - * class MyClass { - * private val logger by KotlinLogging.logger() - * } - * ``` - */ -public fun KotlinLogging.logger(): ReadOnlyProperty = LoggerDelegate() - -private class LoggerDelegate : ReadOnlyProperty { - private lateinit var logger: KLogger - - override fun getValue(thisRef: Any?, property: KProperty<*>): KLogger { - if (!::logger.isInitialized) { - logger = - thisRef.asDynamic()?.constructor?.name.unsafeCast()?.let { - KotlinLogging.logger(it) - } ?: KotlinLogging.logger("root-logger") - } - return logger - } -} diff --git a/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index e83bbfb1..6bcff7e7 100644 --- a/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -1,18 +1,44 @@ package io.github.oshai.kotlinlogging.internal internal actual object KLoggerNameResolver { + private const val DEFAULT_LOGGER_NAME = "root-logger" + private const val LOGGER_FUNCTION_NAME = "kotlinLoggerByRef" + private const val COMPANION_GET_INSTANCE_SUFFIX = "_getInstance" + private val TOP_LEVEL_INIT_PROPERTIES_REGEX = Regex("_init_properties_(\\S+)_kt_") + private val CLASS_LEVEL_INIT_PROPERTIES_REGEX = Regex("new (\\S+)") - internal actual fun name(func: () -> Unit): String { - var found = false - val exception = Exception() - for (line in exception.stackTraceToString().split("\n")) { - if (found) { - return line.substringBefore(".kt").substringAfterLast(".").substringAfterLast("/") - } - if (line.contains("at KotlinLogging")) { - found = true - } + internal actual fun name(ref: Any): String { + return findLoggerCallerClassName() ?: DEFAULT_LOGGER_NAME + } + + private fun findLoggerCallerClassName(): String? { + val stackTrace = Throwable().stackTraceToString().split('\n') + val invokeLoggerLine = stackTrace.indexOfFirst { it.contains(LOGGER_FUNCTION_NAME) } + if (invokeLoggerLine == -1 || invokeLoggerLine + 1 >= stackTrace.size) return null + val callerLine = invokeLoggerLine + 1 + return resolveAsTopLevelProperty(stackTrace, callerLine) + ?: resolveAsClassLevelProperty(stackTrace, callerLine) + } + + private fun resolveAsTopLevelProperty(stackTrace: List, callerLine: Int): String? { + val found = TOP_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null + return found.groupValues[1] + } + + private fun resolveAsClassLevelProperty(stackTrace: List, callerLine: Int): String? { + val found = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null + val className = found.groupValues[1] + // find enclosing class in case of Companion object: + // new MyCompanion() <- found class name + // MyCompanion_getInstance() + // new MyClass() <- enclosing class + if ( + callerLine + 2 >= stackTrace.size || + !stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX") + ) { + return className } - return "" + val enclosingFound = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className + return enclosingFound.groupValues[1] } } diff --git a/src/jsTest/kotlin/io/github/oshai/kotlinlogging/SimpleJsTest.kt b/src/jsTest/kotlin/io/github/oshai/kotlinlogging/SimpleJsTest.kt index 4a684a21..01052726 100644 --- a/src/jsTest/kotlin/io/github/oshai/kotlinlogging/SimpleJsTest.kt +++ b/src/jsTest/kotlin/io/github/oshai/kotlinlogging/SimpleJsTest.kt @@ -2,7 +2,21 @@ package io.github.oshai.kotlinlogging import kotlin.test.* -private val logger = KotlinLogging.logger("SimpleJsTest") +val topLevelNamedLogger = KotlinLogging.logger("topLevelNamedLogger") +val topLevelLambdaLogger = KotlinLogging.logger {} + +class MyClass { + val classNamedLogger = KotlinLogging.logger("MyClass") + val classLambdaLogger = KotlinLogging.logger {} + val classRefLogger = KotlinLogging.logger(this) + + // check with non default "Companion" name also + companion object MyCompanion { + val companionNamedLogger = KotlinLogging.logger("MyClassCompanion") + val companionLambdaLogger = KotlinLogging.logger {} + val companionRefLogger = KotlinLogging.logger(this) + } +} @Suppress("DEPRECATION") class SimpleJsTest { @@ -20,29 +34,200 @@ class SimpleJsTest { KotlinLoggingConfiguration.LOG_LEVEL = Level.INFO } + // TODO: use parameterized test? + + // TopLevelNamedLogger + @Test + fun checkTopLevelNamedLoggerName() { + checkLoggerName(topLevelNamedLogger, "topLevelNamedLogger") + } + + @Test + fun checkTopLevelNamedLoggerInfoMessage() { + checkLoggerInfoMessage(topLevelNamedLogger) + } + + @Test + fun checkTopLevelNamedLoggerErrorMessage() { + checkLoggerErrorMessage(topLevelNamedLogger) + } + + @Test + fun checkTopLevelNamedLoggerOffLevel() { + checkLoggerOffLevel(topLevelNamedLogger) + } + + // TopLevelLambdaLogger + @Test + fun checkTopLevelLambdaLoggerName() { + checkLoggerName(topLevelLambdaLogger, "SimpleJsTest") + } + + @Test + fun checkTopLevelLambdaLoggerInfoMessage() { + checkLoggerInfoMessage(topLevelLambdaLogger) + } + + @Test + fun checkTopLevelLambdaLoggerErrorMessage() { + checkLoggerErrorMessage(topLevelLambdaLogger) + } + + @Test + fun checkTopLevelLambdaLoggerOffLevel() { + checkLoggerOffLevel(topLevelLambdaLogger) + } + + // ClassNamedLogger + @Test + fun checkClassNamedLoggerName() { + checkLoggerName(MyClass().classNamedLogger, "MyClass") + } + + @Test + fun checkClassNamedLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass().classNamedLogger) + } + + @Test + fun checkClassNamedLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass().classNamedLogger) + } + + @Test + fun checkClassNamedLoggerOffLevel() { + checkLoggerOffLevel(MyClass().classNamedLogger) + } + + // ClassLambdaLogger @Test - fun simpleJsTest() { - assertEquals("SimpleJsTest", logger.name) + fun checkClassLambdaLoggerName() { + checkLoggerName(MyClass().classLambdaLogger, "MyClass") + } + + @Test + fun checkClassLambdaLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass().classLambdaLogger) + } + + @Test + fun checkClassLambdaLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass().classLambdaLogger) + } + + @Test + fun checkClassLambdaLoggerOffLevel() { + checkLoggerOffLevel(MyClass().classLambdaLogger) + } + + // ClassRefLogger + @Test + fun checkClassRefLoggerName() { + checkLoggerName(MyClass().classRefLogger, "MyClass") + } + + @Test + fun checkClassRefLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass().classRefLogger) + } + + @Test + fun checkClassRefLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass().classRefLogger) + } + + @Test + fun checkClassRefLoggerOffLevel() { + checkLoggerOffLevel(MyClass().classRefLogger) + } + + // CompanionNamedLogger + @Test + fun checkCompanionNamedLoggerName() { + checkLoggerName(MyClass.MyCompanion.companionNamedLogger, "MyClassCompanion") + } + + @Test + fun checkCompanionNamedLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass.MyCompanion.companionNamedLogger) + } + + @Test + fun checkCompanionNamedLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass.MyCompanion.companionNamedLogger) + } + + @Test + fun checkCompanionNamedLoggerOffLevel() { + checkLoggerOffLevel(MyClass.MyCompanion.companionNamedLogger) + } + + // CompanionLambdaLogger + @Test + fun checkCompanionLambdaLoggerName() { + checkLoggerName(MyClass.MyCompanion.companionLambdaLogger, "MyClass") + } + + @Test + fun checkCompanionLambdaLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass.MyCompanion.companionLambdaLogger) + } + + @Test + fun checkCompanionLambdaLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass.MyCompanion.companionLambdaLogger) + } + + @Test + fun checkCompanionLambdaLoggerOffLevel() { + checkLoggerOffLevel(MyClass.MyCompanion.companionLambdaLogger) + } + + // CompanionRefLogger + @Test + fun checkCompanionRefLoggerName() { + checkLoggerName(MyClass.MyCompanion.companionRefLogger, "MyClass") + } + + @Test + fun checkCompanionRefLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass.MyCompanion.companionRefLogger) + } + + @Test + fun checkCompanionRefLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass.MyCompanion.companionRefLogger) + } + + @Test + fun checkCompanionRefLoggerOffLevel() { + checkLoggerOffLevel(MyClass.MyCompanion.companionRefLogger) + } + + // use cases + private fun checkLoggerName(logger: KLogger, expected: String) { + assertEquals(expected, logger.name) + } + + private fun checkLoggerInfoMessage(logger: KLogger) { logger.info { "info msg" } - assertEquals("INFO: [SimpleJsTest] info msg", appender.lastMessage) + assertEquals("INFO: [${logger.name}] info msg", appender.lastMessage) assertEquals("info", appender.lastLevel) } - @Test - fun logThrowableTest() { + private fun checkLoggerErrorMessage(logger: KLogger) { val errorLog = "Something Bad Happened" val outerMessage = "Outer Message" val innerMessage = "Inner Message" val throwable = Throwable(message = outerMessage, cause = Throwable(message = innerMessage)) logger.error(throwable) { errorLog } assertEquals( - "ERROR: [SimpleJsTest] $errorLog, Caused by: '$outerMessage', Caused by: '$innerMessage'", + "ERROR: [${logger.name}] $errorLog, Caused by: '$outerMessage', Caused by: '$innerMessage'", appender.lastMessage, ) } - @Test - fun offLevelJsTest() { + private fun checkLoggerOffLevel(logger: KLogger) { KotlinLoggingConfiguration.LOG_LEVEL = Level.OFF assertTrue(logger.isLoggingOff()) logger.error { "error msg" } @@ -50,11 +235,6 @@ class SimpleJsTest { assertEquals("NA", appender.lastLevel) } - @Test - fun loggerNameTest() { - assertEquals("MyClass", MyClass().logger2.name) - } - private fun createAppender(): SimpleAppender = SimpleAppender() class SimpleAppender : Appender { @@ -67,7 +247,3 @@ class SimpleJsTest { } } } - -class MyClass { - val logger2 by KotlinLogging.logger() -} diff --git a/src/nativeMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/nativeMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index fb420756..95c4bf91 100644 --- a/src/nativeMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/nativeMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -2,5 +2,5 @@ package io.github.oshai.kotlinlogging.internal internal actual object KLoggerNameResolver { - internal actual fun name(func: () -> Unit): String = func::class.qualifiedName ?: "" + internal actual fun name(ref: Any): String = ref::class.qualifiedName ?: "" } diff --git a/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index 412831cb..0375befb 100644 --- a/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -1,25 +1,44 @@ package io.github.oshai.kotlinlogging.internal -private const val NO_CLASS = "" - internal actual object KLoggerNameResolver { - private val kotlinLoggingRegex = Regex("\\.KotlinLogging\\.logger\\s") - private val topLevelPropertyRegex = Regex("") - private val classPropertyRegex = Regex("\\.(\\S+)\\.") + private const val DEFAULT_LOGGER_NAME = "root-logger" + private const val LOGGER_FUNCTION_NAME = "KotlinLogging.logger" + private const val COMPANION_GET_INSTANCE_SUFFIX = "_getInstance" + private val TOP_LEVEL_INIT_PROPERTIES_REGEX = Regex("") + private val CLASS_LEVEL_INIT_PROPERTIES_REGEX = Regex("\\.([^.\\s]+)\\.") - internal actual fun name(func: () -> Unit): String { - val stackTrace = Exception().stackTraceToString().split("\n") - val invokingClassLine = stackTrace.indexOfFirst(kotlinLoggingRegex::containsMatchIn) + 1 - return if (invokingClassLine in 1 ..< stackTrace.size) { - getInvokingClass(stackTrace[invokingClassLine]) - } else { - NO_CLASS - } + internal actual fun name(ref: Any): String { + return findLoggerCallerClassName() ?: DEFAULT_LOGGER_NAME + } + + private fun findLoggerCallerClassName(): String? { + val stackTrace = Throwable().stackTraceToString().split('\n') + val invokeLoggerLine = stackTrace.indexOfFirst { it.contains(LOGGER_FUNCTION_NAME) } + if (invokeLoggerLine == -1 || invokeLoggerLine + 1 >= stackTrace.size) return null + val callerLine = invokeLoggerLine + 1 + return resolveAsTopLevelProperty(stackTrace, callerLine) + ?: resolveAsClassLevelProperty(stackTrace, callerLine) } - private fun getInvokingClass(line: String): String { - return topLevelPropertyRegex.find(line)?.let { it.groupValues[1].split(".").last() } - ?: classPropertyRegex.find(line)?.let { it.groupValues[1].split(".").last() } - ?: NO_CLASS + private fun resolveAsTopLevelProperty(stackTrace: List, callerLine: Int): String? { + val found = TOP_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null + return found.groupValues[1] + } + + private fun resolveAsClassLevelProperty(stackTrace: List, callerLine: Int): String? { + val found = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null + val className = found.groupValues[1] + // find enclosing class in case of Companion object: + // MyCompanion.() <- found class name + // MyCompanion_getInstance() + // MyClass.() <- enclosing class + if ( + callerLine + 2 >= stackTrace.size || + !stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX") + ) { + return className + } + val enclosingFound = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className + return enclosingFound.groupValues[1] } } diff --git a/src/wasmJsTest/kotlin/io/github/oshai/kotlinlogging/SimpleWasmJsTest.kt b/src/wasmJsTest/kotlin/io/github/oshai/kotlinlogging/SimpleWasmJsTest.kt index 8a762fd7..1d65de65 100644 --- a/src/wasmJsTest/kotlin/io/github/oshai/kotlinlogging/SimpleWasmJsTest.kt +++ b/src/wasmJsTest/kotlin/io/github/oshai/kotlinlogging/SimpleWasmJsTest.kt @@ -2,12 +2,24 @@ package io.github.oshai.kotlinlogging import kotlin.test.* -private val namedLogger = KotlinLogging.logger("SimpleWasmJsTest") -private val anonymousFilePropLogger = KotlinLogging.logger {} +val topLevelNamedLogger = KotlinLogging.logger("topLevelNamedLogger") +val topLevelLambdaLogger = KotlinLogging.logger {} + +class MyClass { + val classNamedLogger = KotlinLogging.logger("MyClass") + val classLambdaLogger = KotlinLogging.logger {} + val classRefLogger = KotlinLogging.logger(this) + + // check with non default "Companion" name also + companion object MyCompanion { + val companionNamedLogger = KotlinLogging.logger("MyClassCompanion") + val companionLambdaLogger = KotlinLogging.logger {} + val companionRefLogger = KotlinLogging.logger(this) + } +} class SimpleWasmJsTest { private lateinit var appender: SimpleAppender - private val anonymousClassPropLogger = KotlinLogging.logger {} @BeforeTest fun setup() { @@ -21,33 +33,203 @@ class SimpleWasmJsTest { KotlinLoggingConfiguration.logLevel = Level.INFO } + // TODO: use parameterized test? + + // TopLevelNamedLogger @Test - fun simpleWasmJsTest() { - assertEquals("SimpleWasmJsTest", namedLogger.name) - namedLogger.info { "info msg" } - assertEquals("INFO: [SimpleWasmJsTest] info msg", appender.lastMessage) - assertEquals("info", appender.lastLevel) + fun checkTopLevelNamedLoggerName() { + checkLoggerName(topLevelNamedLogger, "topLevelNamedLogger") + } + + @Test + fun checkTopLevelNamedLoggerInfoMessage() { + checkLoggerInfoMessage(topLevelNamedLogger) + } + + @Test + fun checkTopLevelNamedLoggerErrorMessage() { + checkLoggerErrorMessage(topLevelNamedLogger) + } + + @Test + fun checkTopLevelNamedLoggerOffLevel() { + checkLoggerOffLevel(topLevelNamedLogger) + } + + // TopLevelLambdaLogger + @Test + fun checkTopLevelLambdaLoggerName() { + checkLoggerName(topLevelLambdaLogger, "SimpleWasmJsTest") + } + + @Test + fun checkTopLevelLambdaLoggerInfoMessage() { + checkLoggerInfoMessage(topLevelLambdaLogger) + } + + @Test + fun checkTopLevelLambdaLoggerErrorMessage() { + checkLoggerErrorMessage(topLevelLambdaLogger) + } + + @Test + fun checkTopLevelLambdaLoggerOffLevel() { + checkLoggerOffLevel(topLevelLambdaLogger) } + // ClassNamedLogger @Test - fun anonymousFilePropWasmJsTest() { - assertEquals("SimpleWasmJsTest", anonymousFilePropLogger.name) - anonymousFilePropLogger.info { "info msg" } - assertEquals("INFO: [SimpleWasmJsTest] info msg", appender.lastMessage) + fun checkClassNamedLoggerName() { + checkLoggerName(MyClass().classNamedLogger, "MyClass") } @Test - fun anonymousClassPropWasmJsTest() { - assertEquals("SimpleWasmJsTest", anonymousClassPropLogger.name) - anonymousFilePropLogger.info { "info msg" } - assertEquals("INFO: [SimpleWasmJsTest] info msg", appender.lastMessage) + fun checkClassNamedLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass().classNamedLogger) } @Test - fun offLevelWasmJsTest() { + fun checkClassNamedLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass().classNamedLogger) + } + + @Test + fun checkClassNamedLoggerOffLevel() { + checkLoggerOffLevel(MyClass().classNamedLogger) + } + + // ClassLambdaLogger + @Test + fun checkClassLambdaLoggerName() { + checkLoggerName(MyClass().classLambdaLogger, "MyClass") + } + + @Test + fun checkClassLambdaLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass().classLambdaLogger) + } + + @Test + fun checkClassLambdaLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass().classLambdaLogger) + } + + @Test + fun checkClassLambdaLoggerOffLevel() { + checkLoggerOffLevel(MyClass().classLambdaLogger) + } + + // ClassRefLogger + @Test + fun checkClassRefLoggerName() { + checkLoggerName(MyClass().classRefLogger, "MyClass") + } + + @Test + fun checkClassRefLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass().classRefLogger) + } + + @Test + fun checkClassRefLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass().classRefLogger) + } + + @Test + fun checkClassRefLoggerOffLevel() { + checkLoggerOffLevel(MyClass().classRefLogger) + } + + // CompanionNamedLogger + @Test + fun checkCompanionNamedLoggerName() { + checkLoggerName(MyClass.MyCompanion.companionNamedLogger, "MyClassCompanion") + } + + @Test + fun checkCompanionNamedLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass.MyCompanion.companionNamedLogger) + } + + @Test + fun checkCompanionNamedLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass.MyCompanion.companionNamedLogger) + } + + @Test + fun checkCompanionNamedLoggerOffLevel() { + checkLoggerOffLevel(MyClass.MyCompanion.companionNamedLogger) + } + + // CompanionLambdaLogger + @Test + fun checkCompanionLambdaLoggerName() { + checkLoggerName(MyClass.MyCompanion.companionLambdaLogger, "MyClass") + } + + @Test + fun checkCompanionLambdaLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass.MyCompanion.companionLambdaLogger) + } + + @Test + fun checkCompanionLambdaLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass.MyCompanion.companionLambdaLogger) + } + + @Test + fun checkCompanionLambdaLoggerOffLevel() { + checkLoggerOffLevel(MyClass.MyCompanion.companionLambdaLogger) + } + + // CompanionRefLogger + @Test + fun checkCompanionRefLoggerName() { + checkLoggerName(MyClass.MyCompanion.companionRefLogger, "MyClass") + } + + @Test + fun checkCompanionRefLoggerInfoMessage() { + checkLoggerInfoMessage(MyClass.MyCompanion.companionRefLogger) + } + + @Test + fun checkCompanionRefLoggerErrorMessage() { + checkLoggerErrorMessage(MyClass.MyCompanion.companionRefLogger) + } + + @Test + fun checkCompanionRefLoggerOffLevel() { + checkLoggerOffLevel(MyClass.MyCompanion.companionRefLogger) + } + + // use cases + private fun checkLoggerName(logger: KLogger, expected: String) { + assertEquals(expected, logger.name) + } + + private fun checkLoggerInfoMessage(logger: KLogger) { + logger.info { "info msg" } + assertEquals("INFO: [${logger.name}] info msg", appender.lastMessage) + assertEquals("info", appender.lastLevel) + } + + private fun checkLoggerErrorMessage(logger: KLogger) { + val errorLog = "Something Bad Happened" + val outerMessage = "Outer Message" + val innerMessage = "Inner Message" + val throwable = Throwable(message = outerMessage, cause = Throwable(message = innerMessage)) + logger.error(throwable) { errorLog } + assertEquals( + "ERROR: [${logger.name}] $errorLog, Caused by: '$outerMessage', Caused by: '$innerMessage'", + appender.lastMessage, + ) + } + + private fun checkLoggerOffLevel(logger: KLogger) { KotlinLoggingConfiguration.logLevel = Level.OFF - assertTrue(namedLogger.isLoggingOff()) - namedLogger.error { "error msg" } + assertTrue(logger.isLoggingOff()) + logger.error { "error msg" } assertEquals("NA", appender.lastMessage) assertEquals("NA", appender.lastLevel) } From 122892f4bff987e7f521818c5c21f2d8d999d78d Mon Sep 17 00:00:00 2001 From: Artem Golovko Date: Mon, 3 Mar 2025 01:35:11 +0000 Subject: [PATCH 2/4] Issue #480 - apply formatting --- .../io/github/oshai/kotlinlogging/KotlinLogging.kt | 14 +++++++------- .../kotlinlogging/internal/KLoggerNameResolver.kt | 5 +++-- .../kotlinlogging/internal/KLoggerNameResolver.kt | 5 +++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt b/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt index 19c78770..9f8d1d35 100644 --- a/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt +++ b/src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt @@ -5,15 +5,15 @@ import io.github.oshai.kotlinlogging.internal.KLoggerNameResolver import kotlin.js.JsName /** - * Since this library is intended to be multiplatform, - * there are several ways to create a logger depending on your programming language. + * Since this library is intended to be multiplatform, there are several ways to create a logger + * depending on your programming language. * - * Please note, that creating a logger by reference involves reflection or, in some cases, - * stack trace analysis. Therefore, if performance is a priority, avoid creating loggers - * in frequently executed code sections. + * Please note, that creating a logger by reference involves reflection or, in some cases, stack + * trace analysis. Therefore, if performance is a priority, avoid creating loggers in frequently + * executed code sections. * - * A logger created by reference will take the name of the class of the reference. - * If the reference is a "companion object," the name of its enclosing class will be used instead. + * A logger created by reference will take the name of the class of the reference. If the reference + * is a "companion object," the name of its enclosing class will be used instead. * * ```kotlin * val topLevelNamedLogger = KotlinLogging.logger("TopLevelNamedLogger") diff --git a/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index 6bcff7e7..1f06cb8e 100644 --- a/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/jsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -34,11 +34,12 @@ internal actual object KLoggerNameResolver { // new MyClass() <- enclosing class if ( callerLine + 2 >= stackTrace.size || - !stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX") + !stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX") ) { return className } - val enclosingFound = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className + val enclosingFound = + CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className return enclosingFound.groupValues[1] } } diff --git a/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index 0375befb..faf67026 100644 --- a/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/wasmJsMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -34,11 +34,12 @@ internal actual object KLoggerNameResolver { // MyClass.() <- enclosing class if ( callerLine + 2 >= stackTrace.size || - !stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX") + !stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX") ) { return className } - val enclosingFound = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className + val enclosingFound = + CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className return enclosingFound.groupValues[1] } } From 896158c76fe3e559402f97a97174268bb681d08c Mon Sep 17 00:00:00 2001 From: Artem Golovko Date: Mon, 3 Mar 2025 02:40:01 +0000 Subject: [PATCH 3/4] Issue #480 - remove clashed KotlinLogging extension --- .../io/github/oshai/kotlinlogging/slf4j/Slf4jExtensions.kt | 7 +------ .../io/github/oshai/kotlinlogging/KotlinLoggingTest.kt | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/javaMain/kotlin/io/github/oshai/kotlinlogging/slf4j/Slf4jExtensions.kt b/src/javaMain/kotlin/io/github/oshai/kotlinlogging/slf4j/Slf4jExtensions.kt index 0d618a18..c98db837 100644 --- a/src/javaMain/kotlin/io/github/oshai/kotlinlogging/slf4j/Slf4jExtensions.kt +++ b/src/javaMain/kotlin/io/github/oshai/kotlinlogging/slf4j/Slf4jExtensions.kt @@ -1,7 +1,6 @@ package io.github.oshai.kotlinlogging.slf4j import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.Level import io.github.oshai.kotlinlogging.Marker import io.github.oshai.kotlinlogging.slf4j.internal.Slf4jLoggerFactory @@ -27,8 +26,4 @@ public fun Level.toSlf4j(): org.slf4j.event.Level = Level.OFF -> throw IllegalArgumentException("OFF level is not supported") } -@Suppress("UnusedReceiverParameter") -public fun KotlinLogging.logger(underlyingLogger: Logger): KLogger = - Slf4jLoggerFactory.wrapJLogger(underlyingLogger) - -public fun Logger.toKLogger(): KLogger = KotlinLogging.logger(this) +public fun Logger.toKLogger(): KLogger = Slf4jLoggerFactory.wrapJLogger(this) diff --git a/src/jvmTest/kotlin/io/github/oshai/kotlinlogging/KotlinLoggingTest.kt b/src/jvmTest/kotlin/io/github/oshai/kotlinlogging/KotlinLoggingTest.kt index dc051c65..d4dfb397 100644 --- a/src/jvmTest/kotlin/io/github/oshai/kotlinlogging/KotlinLoggingTest.kt +++ b/src/jvmTest/kotlin/io/github/oshai/kotlinlogging/KotlinLoggingTest.kt @@ -1,6 +1,5 @@ package io.github.oshai.kotlinlogging -import io.github.oshai.kotlinlogging.slf4j.logger import io.github.oshai.kotlinlogging.slf4j.toKLogger import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -9,7 +8,7 @@ import org.slf4j.LoggerFactory private val logger = KotlinLogging.logger {} private val loggerFromSlf4j = - KotlinLogging.logger(LoggerFactory.getLogger("io.github.oshai.kotlinlogging.slf4jLogger")) + LoggerFactory.getLogger("io.github.oshai.kotlinlogging.slf4jLogger").toKLogger() private val loggerFromSlf4jExtension = LoggerFactory.getLogger("io.github.oshai.kotlinlogging.slf4jLoggerExtension").toKLogger() From e45ca14db856ece016e0a9f80462e25d9038b254 Mon Sep 17 00:00:00 2001 From: Artem Golovko Date: Thu, 17 Apr 2025 22:57:10 +0100 Subject: [PATCH 4/4] Issue #480 - merge master --- .../oshai/kotlinlogging/internal/KLoggerNameResolver.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt b/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt index cbf1e024..7e56ac29 100644 --- a/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt +++ b/src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt @@ -6,8 +6,8 @@ import kotlin.reflect.KClass internal actual object KLoggerNameResolver { /** get class name for function by the package of the function */ - internal actual fun name(func: () -> Unit): String { - return name(func::class) + internal actual fun name(ref: Any): String { + return name(ref::class) } internal fun name(clazz: KClass<*>): String {