Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.oshai.kotlinlogging

import io.github.oshai.kotlinlogging.internal.KLoggerFactory
import io.github.oshai.kotlinlogging.internal.KLoggerNameResolver
import kotlin.js.JsName

public object KotlinLogging {
/**
Expand All @@ -10,6 +11,7 @@ public object KotlinLogging {
* private val logger = KotlinLogging.logger {}
* ```
*/
@JsName("kotlinLoggerByFunc")
public fun logger(func: () -> Unit): KLogger = logger(KLoggerNameResolver.name(func))

/**
Expand All @@ -21,5 +23,6 @@ public object KotlinLogging {
* 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)
}
29 changes: 0 additions & 29 deletions src/jsMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
package io.github.oshai.kotlinlogging.internal

internal actual object KLoggerNameResolver {
private const val DEFAULT_LOGGER_NAME = "root-logger"
private const val LOGGER_FUNCTION_NAME = "kotlinLoggerByFunc"
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
}
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<String>, callerLine: Int): String? {
val found = TOP_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null
return found.groupValues[1]
}

private fun resolveAsClassLevelProperty(stackTrace: List<String>, 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]
}
}
168 changes: 150 additions & 18 deletions src/jsTest/kotlin/io/github/oshai/kotlinlogging/SimpleJsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ 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 {}

// check with non default "Companion" name also
companion object MyCompanion {
val companionNamedLogger = KotlinLogging.logger("MyClassCompanion")
val companionLambdaLogger = KotlinLogging.logger {}
}
}

@Suppress("DEPRECATION")
class SimpleJsTest {
Expand All @@ -20,41 +32,165 @@ 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 simpleJsTest() {
assertEquals("SimpleJsTest", logger.name)
fun checkClassNamedLoggerInfoMessage() {
checkLoggerInfoMessage(MyClass().classNamedLogger)
}

@Test
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)
}

// 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)
}

// 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)
Copy link
Owner

Choose a reason for hiding this comment

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

can you change this to an explicit string param with logger name, so it will be clear what was the name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's checking via checkLoggerName(logger: KLogger, expected: String)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@oshai Hi, could we move this PR forward?

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" }
assertEquals("NA", appender.lastMessage)
assertEquals("NA", appender.lastLevel)
}

@Test
fun loggerNameTest() {
assertEquals("MyClass", MyClass().logger2.name)
}

private fun createAppender(): SimpleAppender = SimpleAppender()

class SimpleAppender : Appender {
Expand All @@ -67,7 +203,3 @@ class SimpleJsTest {
}
}
}

class MyClass {
val logger2 by KotlinLogging.logger()
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
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("<init properties (\\S+)\\.kt>")
private val classPropertyRegex = Regex("\\.(\\S+)\\.<init>")
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("<init properties (\\S+)\\.kt>")
private val CLASS_LEVEL_INIT_PROPERTIES_REGEX = Regex("\\.([^.\\s]+)\\.<init>")

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
}
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<String>, callerLine: Int): String? {
val found = TOP_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null
return found.groupValues[1]
}

private fun resolveAsClassLevelProperty(stackTrace: List<String>, 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.<init>() <- found class name
// MyCompanion_getInstance()
// MyClass.<init>() <- 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]
}
}
Loading
Loading