Skip to content

Commit 3ed804c

Browse files
committed
Issue oshai#480 - Extend the way of how to create the logger
1 parent c8ec888 commit 3ed804c

File tree

10 files changed

+516
-121
lines changed

10 files changed

+516
-121
lines changed

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

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,40 @@ 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

7+
/**
8+
* Since this library is intended to be multiplatform,
9+
* there are several ways to create a logger depending on your programming language.
10+
*
11+
* Please note, that creating a logger by reference involves reflection or, in some cases,
12+
* stack trace analysis. Therefore, if performance is a priority, avoid creating loggers
13+
* in frequently executed code sections.
14+
*
15+
* A logger created by reference will take the name of the class of the reference.
16+
* If the reference is a "companion object," the name of its enclosing class will be used instead.
17+
*
18+
* ```kotlin
19+
* val topLevelNamedLogger = KotlinLogging.logger("TopLevelNamedLogger")
20+
* val topLevelLambdaLogger = KotlinLogging.logger {}
21+
*
22+
* class MyClass {
23+
* val classNamedLogger = KotlinLogging.logger("MyClass")
24+
* val classLambdaLogger = KotlinLogging.logger {}
25+
* val classRefLogger = KotlinLogging.logger(this)
26+
*
27+
* companion object {
28+
* val companionNamedLogger = KotlinLogging.logger("MyClassCompanion")
29+
* val companionLambdaLogger = KotlinLogging.logger {}
30+
* val companionRefLogger = KotlinLogging.logger(this)
31+
* }
32+
* }
33+
* ```
34+
*/
635
public object KotlinLogging {
7-
/**
8-
* This method allow defining the logger in a file in the following way:
9-
* ```
10-
* private val logger = KotlinLogging.logger {}
11-
* ```
12-
*/
13-
public fun logger(func: () -> Unit): KLogger = logger(KLoggerNameResolver.name(func))
36+
@JsName("kotlinLoggerByRef")
37+
public fun logger(ref: Any): KLogger = logger(KLoggerNameResolver.name(ref))
1438

15-
/**
16-
* This method allow defining the logger in a file in the following way:
17-
* ```
18-
* private val logger = KotlinLogging.logger("io.github.oshai.kotlinlogging.MyLogger")
19-
* ```
20-
*
21-
* In most cases the name represents the package notation of the file that the logger is defined
22-
* in.
23-
*/
39+
@JsName("kotlinLoggerByName")
2440
public fun logger(name: String): KLogger = KLoggerFactory.logger(name)
2541
}

src/commonMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ package io.github.oshai.kotlinlogging.internal
22

33
internal expect object KLoggerNameResolver {
44

5-
internal fun name(func: () -> Unit): String
5+
internal fun name(ref: Any): String
66
}

src/darwinMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ package io.github.oshai.kotlinlogging.internal
22

33
internal actual object KLoggerNameResolver {
44

5-
internal actual fun name(func: () -> Unit): String = func::class.qualifiedName ?: ""
5+
internal actual fun name(ref: Any): String = ref::class.qualifiedName ?: ""
66
}

src/javaMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggerNameResolver.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ import java.lang.reflect.Modifier
66
internal actual object KLoggerNameResolver {
77

88
/** get class name for function by the package of the function */
9-
internal actual fun name(func: () -> Unit): String {
10-
val name = func.javaClass.name
11-
val slicedName =
12-
when {
13-
name.contains("Kt$") -> name.substringBefore("Kt$")
14-
name.contains("$") -> name.substringBefore("$")
15-
else -> name
9+
internal actual fun name(ref: Any): String {
10+
return ref::class.java.name.toCleanClassName()
11+
}
12+
13+
private val classNameEndings = listOf("Kt$", "$")
14+
15+
private fun String.toCleanClassName(): String {
16+
classNameEndings.forEach { ending ->
17+
val indexOfEnding = this.indexOf(ending)
18+
if (indexOfEnding != -1) {
19+
return this.substring(0, indexOfEnding)
1620
}
17-
return slicedName
21+
}
22+
return this
1823
}
1924

2025
/** get class name for java class (that usually represents kotlin class) */

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

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
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 = "kotlinLoggerByRef"
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

5-
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-
}
10+
internal actual fun name(ref: Any): String {
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 = CLASS_LEVEL_INIT_PROPERTIES_REGEX.find(stackTrace[callerLine + 2]) ?: return className
42+
return enclosingFound.groupValues[1]
1743
}
1844
}

0 commit comments

Comments
 (0)