Skip to content

Commit 4ef9be7

Browse files
committed
Issue oshai#499 - improve Kotlin/JS logger
1 parent cc332a5 commit 4ef9be7

File tree

6 files changed

+383
-92
lines changed

6 files changed

+383
-92
lines changed

src/commonMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.github.oshai.kotlinlogging
22

33
import io.github.oshai.kotlinlogging.internal.KLoggerFactory
44
import io.github.oshai.kotlinlogging.internal.KLoggerNameResolver
5+
import kotlin.js.JsName
56

67
public object KotlinLogging {
78
/**
@@ -10,6 +11,7 @@ public object KotlinLogging {
1011
* private val logger = KotlinLogging.logger {}
1112
* ```
1213
*/
14+
@JsName("kotlinLoggerByFunc")
1315
public fun logger(func: () -> Unit): KLogger = logger(KLoggerNameResolver.name(func))
1416

1517
/**
@@ -21,5 +23,6 @@ public object KotlinLogging {
2123
* In most cases the name represents the package notation of the file that the logger is defined
2224
* in.
2325
*/
26+
@JsName("kotlinLoggerByName")
2427
public fun logger(name: String): KLogger = KLoggerFactory.logger(name)
2528
}

src/jsMain/kotlin/io/github/oshai/kotlinlogging/KotlinLogging.kt

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
11
package io.github.oshai.kotlinlogging.internal
22

33
internal actual object KLoggerNameResolver {
4+
private const val DEFAULT_LOGGER_NAME = "root-logger"
5+
private const val LOGGER_FUNCTION_NAME = "kotlinLoggerByFunc"
6+
private const val COMPANION_GET_INSTANCE_SUFFIX = "_getInstance"
7+
private val TOP_LEVEL_INIT_PROPERTIES_REGEX = Regex("_init_properties_(\\S+)_kt_")
8+
private val CLASS_LEVEL_INIT_PROPERTIES_REGEX = Regex("new (\\S+)")
49

510
internal actual fun name(func: () -> Unit): String {
6-
var found = false
7-
val exception = Exception()
8-
for (line in exception.stackTraceToString().split("\n")) {
9-
if (found) {
10-
return line.substringBefore(".kt").substringAfterLast(".").substringAfterLast("/")
11-
}
12-
if (line.contains("at KotlinLogging")) {
13-
found = true
14-
}
11+
return findLoggerCallerClassName() ?: DEFAULT_LOGGER_NAME
12+
}
13+
14+
private fun findLoggerCallerClassName(): String? {
15+
val stackTrace = Throwable().stackTraceToString().split('\n')
16+
val invokeLoggerLine = stackTrace.indexOfFirst { it.contains(LOGGER_FUNCTION_NAME) }
17+
if (invokeLoggerLine == -1 || invokeLoggerLine + 1 >= stackTrace.size) return null
18+
val callerLine = invokeLoggerLine + 1
19+
return resolveAsTopLevelProperty(stackTrace, callerLine)
20+
?: resolveAsClassLevelProperty(stackTrace, callerLine)
21+
}
22+
23+
private fun resolveAsTopLevelProperty(stackTrace: List<String>, callerLine: Int): String? {
24+
val found = TOP_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null
25+
return found.groupValues[1]
26+
}
27+
28+
private fun resolveAsClassLevelProperty(stackTrace: List<String>, callerLine: Int): String? {
29+
val found = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null
30+
val className = found.groupValues[1]
31+
// find enclosing class in case of Companion object:
32+
// new MyCompanion() <- found class name
33+
// MyCompanion_getInstance()
34+
// new MyClass() <- enclosing class
35+
if (
36+
callerLine + 2 >= stackTrace.size ||
37+
!stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX")
38+
) {
39+
return className
1540
}
16-
return ""
41+
val enclosingFound =
42+
CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className
43+
return enclosingFound.groupValues[1]
1744
}
1845
}

src/jsTest/kotlin/io/github/oshai/kotlinlogging/SimpleJsTest.kt

Lines changed: 150 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@ package io.github.oshai.kotlinlogging
22

33
import kotlin.test.*
44

5-
private val logger = KotlinLogging.logger("SimpleJsTest")
5+
val topLevelNamedLogger = KotlinLogging.logger("topLevelNamedLogger")
6+
val topLevelLambdaLogger = KotlinLogging.logger {}
7+
8+
class MyClass {
9+
val classNamedLogger = KotlinLogging.logger("MyClass")
10+
val classLambdaLogger = KotlinLogging.logger {}
11+
12+
// check with non default "Companion" name also
13+
companion object MyCompanion {
14+
val companionNamedLogger = KotlinLogging.logger("MyClassCompanion")
15+
val companionLambdaLogger = KotlinLogging.logger {}
16+
}
17+
}
618

719
@Suppress("DEPRECATION")
820
class SimpleJsTest {
@@ -20,41 +32,165 @@ class SimpleJsTest {
2032
KotlinLoggingConfiguration.LOG_LEVEL = Level.INFO
2133
}
2234

35+
// TODO: use parameterized test?
36+
37+
// TopLevelNamedLogger
38+
@Test
39+
fun checkTopLevelNamedLoggerName() {
40+
checkLoggerName(topLevelNamedLogger, "topLevelNamedLogger")
41+
}
42+
43+
@Test
44+
fun checkTopLevelNamedLoggerInfoMessage() {
45+
checkLoggerInfoMessage(topLevelNamedLogger)
46+
}
47+
48+
@Test
49+
fun checkTopLevelNamedLoggerErrorMessage() {
50+
checkLoggerErrorMessage(topLevelNamedLogger)
51+
}
52+
53+
@Test
54+
fun checkTopLevelNamedLoggerOffLevel() {
55+
checkLoggerOffLevel(topLevelNamedLogger)
56+
}
57+
58+
// TopLevelLambdaLogger
59+
@Test
60+
fun checkTopLevelLambdaLoggerName() {
61+
checkLoggerName(topLevelLambdaLogger, "SimpleJsTest")
62+
}
63+
64+
@Test
65+
fun checkTopLevelLambdaLoggerInfoMessage() {
66+
checkLoggerInfoMessage(topLevelLambdaLogger)
67+
}
68+
69+
@Test
70+
fun checkTopLevelLambdaLoggerErrorMessage() {
71+
checkLoggerErrorMessage(topLevelLambdaLogger)
72+
}
73+
74+
@Test
75+
fun checkTopLevelLambdaLoggerOffLevel() {
76+
checkLoggerOffLevel(topLevelLambdaLogger)
77+
}
78+
79+
// ClassNamedLogger
80+
@Test
81+
fun checkClassNamedLoggerName() {
82+
checkLoggerName(MyClass().classNamedLogger, "MyClass")
83+
}
84+
2385
@Test
24-
fun simpleJsTest() {
25-
assertEquals("SimpleJsTest", logger.name)
86+
fun checkClassNamedLoggerInfoMessage() {
87+
checkLoggerInfoMessage(MyClass().classNamedLogger)
88+
}
89+
90+
@Test
91+
fun checkClassNamedLoggerErrorMessage() {
92+
checkLoggerErrorMessage(MyClass().classNamedLogger)
93+
}
94+
95+
@Test
96+
fun checkClassNamedLoggerOffLevel() {
97+
checkLoggerOffLevel(MyClass().classNamedLogger)
98+
}
99+
100+
// ClassLambdaLogger
101+
@Test
102+
fun checkClassLambdaLoggerName() {
103+
checkLoggerName(MyClass().classLambdaLogger, "MyClass")
104+
}
105+
106+
@Test
107+
fun checkClassLambdaLoggerInfoMessage() {
108+
checkLoggerInfoMessage(MyClass().classLambdaLogger)
109+
}
110+
111+
@Test
112+
fun checkClassLambdaLoggerErrorMessage() {
113+
checkLoggerErrorMessage(MyClass().classLambdaLogger)
114+
}
115+
116+
@Test
117+
fun checkClassLambdaLoggerOffLevel() {
118+
checkLoggerOffLevel(MyClass().classLambdaLogger)
119+
}
120+
121+
// CompanionNamedLogger
122+
@Test
123+
fun checkCompanionNamedLoggerName() {
124+
checkLoggerName(MyClass.MyCompanion.companionNamedLogger, "MyClassCompanion")
125+
}
126+
127+
@Test
128+
fun checkCompanionNamedLoggerInfoMessage() {
129+
checkLoggerInfoMessage(MyClass.MyCompanion.companionNamedLogger)
130+
}
131+
132+
@Test
133+
fun checkCompanionNamedLoggerErrorMessage() {
134+
checkLoggerErrorMessage(MyClass.MyCompanion.companionNamedLogger)
135+
}
136+
137+
@Test
138+
fun checkCompanionNamedLoggerOffLevel() {
139+
checkLoggerOffLevel(MyClass.MyCompanion.companionNamedLogger)
140+
}
141+
142+
// CompanionLambdaLogger
143+
@Test
144+
fun checkCompanionLambdaLoggerName() {
145+
checkLoggerName(MyClass.MyCompanion.companionLambdaLogger, "MyClass")
146+
}
147+
148+
@Test
149+
fun checkCompanionLambdaLoggerInfoMessage() {
150+
checkLoggerInfoMessage(MyClass.MyCompanion.companionLambdaLogger)
151+
}
152+
153+
@Test
154+
fun checkCompanionLambdaLoggerErrorMessage() {
155+
checkLoggerErrorMessage(MyClass.MyCompanion.companionLambdaLogger)
156+
}
157+
158+
@Test
159+
fun checkCompanionLambdaLoggerOffLevel() {
160+
checkLoggerOffLevel(MyClass.MyCompanion.companionLambdaLogger)
161+
}
162+
163+
// use cases
164+
private fun checkLoggerName(logger: KLogger, expected: String) {
165+
assertEquals(expected, logger.name)
166+
}
167+
168+
private fun checkLoggerInfoMessage(logger: KLogger) {
26169
logger.info { "info msg" }
27-
assertEquals("INFO: [SimpleJsTest] info msg", appender.lastMessage)
170+
assertEquals("INFO: [${logger.name}] info msg", appender.lastMessage)
28171
assertEquals("info", appender.lastLevel)
29172
}
30173

31-
@Test
32-
fun logThrowableTest() {
174+
private fun checkLoggerErrorMessage(logger: KLogger) {
33175
val errorLog = "Something Bad Happened"
34176
val outerMessage = "Outer Message"
35177
val innerMessage = "Inner Message"
36178
val throwable = Throwable(message = outerMessage, cause = Throwable(message = innerMessage))
37179
logger.error(throwable) { errorLog }
38180
assertEquals(
39-
"ERROR: [SimpleJsTest] $errorLog, Caused by: '$outerMessage', Caused by: '$innerMessage'",
181+
"ERROR: [${logger.name}] $errorLog, Caused by: '$outerMessage', Caused by: '$innerMessage'",
40182
appender.lastMessage,
41183
)
42184
}
43185

44-
@Test
45-
fun offLevelJsTest() {
186+
private fun checkLoggerOffLevel(logger: KLogger) {
46187
KotlinLoggingConfiguration.LOG_LEVEL = Level.OFF
47188
assertTrue(logger.isLoggingOff())
48189
logger.error { "error msg" }
49190
assertEquals("NA", appender.lastMessage)
50191
assertEquals("NA", appender.lastLevel)
51192
}
52193

53-
@Test
54-
fun loggerNameTest() {
55-
assertEquals("MyClass", MyClass().logger2.name)
56-
}
57-
58194
private fun createAppender(): SimpleAppender = SimpleAppender()
59195

60196
class SimpleAppender : Appender {
@@ -67,7 +203,3 @@ class SimpleJsTest {
67203
}
68204
}
69205
}
70-
71-
class MyClass {
72-
val logger2 by KotlinLogging.logger()
73-
}
Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
11
package io.github.oshai.kotlinlogging.internal
22

3-
private const val NO_CLASS = ""
4-
53
internal actual object KLoggerNameResolver {
6-
private val kotlinLoggingRegex = Regex("\\.KotlinLogging\\.logger\\s")
7-
private val topLevelPropertyRegex = Regex("<init properties (\\S+)\\.kt>")
8-
private val classPropertyRegex = Regex("\\.(\\S+)\\.<init>")
4+
private const val DEFAULT_LOGGER_NAME = "root-logger"
5+
private const val LOGGER_FUNCTION_NAME = "KotlinLogging.logger"
6+
private const val COMPANION_GET_INSTANCE_SUFFIX = "_getInstance"
7+
private val TOP_LEVEL_INIT_PROPERTIES_REGEX = Regex("<init properties (\\S+)\\.kt>")
8+
private val CLASS_LEVEL_INIT_PROPERTIES_REGEX = Regex("\\.([^.\\s]+)\\.<init>")
99

1010
internal actual fun name(func: () -> Unit): String {
11-
val stackTrace = Exception().stackTraceToString().split("\n")
12-
val invokingClassLine = stackTrace.indexOfFirst(kotlinLoggingRegex::containsMatchIn) + 1
13-
return if (invokingClassLine in 1 ..< stackTrace.size) {
14-
getInvokingClass(stackTrace[invokingClassLine])
15-
} else {
16-
NO_CLASS
17-
}
11+
return findLoggerCallerClassName() ?: DEFAULT_LOGGER_NAME
12+
}
13+
14+
private fun findLoggerCallerClassName(): String? {
15+
val stackTrace = Throwable().stackTraceToString().split('\n')
16+
val invokeLoggerLine = stackTrace.indexOfFirst { it.contains(LOGGER_FUNCTION_NAME) }
17+
if (invokeLoggerLine == -1 || invokeLoggerLine + 1 >= stackTrace.size) return null
18+
val callerLine = invokeLoggerLine + 1
19+
return resolveAsTopLevelProperty(stackTrace, callerLine)
20+
?: resolveAsClassLevelProperty(stackTrace, callerLine)
1821
}
1922

20-
private fun getInvokingClass(line: String): String {
21-
return topLevelPropertyRegex.find(line)?.let { it.groupValues[1].split(".").last() }
22-
?: classPropertyRegex.find(line)?.let { it.groupValues[1].split(".").last() }
23-
?: NO_CLASS
23+
private fun resolveAsTopLevelProperty(stackTrace: List<String>, callerLine: Int): String? {
24+
val found = TOP_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null
25+
return found.groupValues[1]
26+
}
27+
28+
private fun resolveAsClassLevelProperty(stackTrace: List<String>, callerLine: Int): String? {
29+
val found = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine]) ?: return null
30+
val className = found.groupValues[1]
31+
// find enclosing class in case of Companion object:
32+
// MyCompanion.<init>() <- found class name
33+
// MyCompanion_getInstance()
34+
// MyClass.<init>() <- enclosing class
35+
if (
36+
callerLine + 2 >= stackTrace.size ||
37+
!stackTrace[callerLine + 1].contains("$className$COMPANION_GET_INSTANCE_SUFFIX")
38+
) {
39+
return className
40+
}
41+
val enclosingFound =
42+
CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className
43+
return enclosingFound.groupValues[1]
2444
}
2545
}

0 commit comments

Comments
 (0)