Supply fully qualified names for loggers, making those work on Js
OliverO2 opened this issue · 6 comments
Logging frameworks such as oshai/kotlin-logging use invocations like this to automatically assign a logger name:
private val logger = KotlinLogging.logger {}
Behind the scenes, they use KClass.qualifiedName
which does not produce a fully qualified name on Js.
This plugin could solve that, e.g.:
-
A file-level logger in
MyFile.kt
package com.example val logger = Logger(__QUALIFIED_CLASS__) // "com.example.MyFileKt"
-
A class-level logger
package com.example class MyClass { companion object { val logger = Logger(__QUALIFIED_CLASS__) // "com.example.MyClass" } }
In an internal project, I had created an annotation-based compiler plugin to generate logger names (also via a IrElementTransformerVoidWithContext
derivative). The relevant qualified class name (including Kotlin's invisible file-level classes) was retrieved like so:
val qualifiedSurroundingClassName = allScopes.reversed().firstNotNullOfOrNull {
it.scope.scopeOwnerSymbol.owner.qualifiedClassName()
}
if (qualifiedSurroundingClassName == null) {
reportError("Could not find a qualified class name", call)
return super.visitCall(call)
}
What should qualified name return in the top-level case on JS and native where there is no concept of an implicit file class like there is on the JVM?
A different way of phrasing that question is: Should we include MyFileKt
in the qualified name when we don't support __TYPE__
at the same location?
From a plugin user perspective, I would not differentiate between platforms. For example, I have a YAML server-side logging configuration, which is directly applied at the backend, and transmitted to frontends on JVM and Js platforms. Since there are classes in commonMain
which are used across platforms, it is essential to have platform-independent logger names. The above retrieval code does that on the JVM and Js.
Example configuration:
logging:
com.example.service.FrontendServiceKt: DEBUG # this is a Js/JVM frontend class
com.example.service.ClientSessionKt: DEBUG # this is a JVM backend class
It seems that qualifiedSurroundingClassName
is similar to __TYPE__
, except that it does not rely on visitor tracking, but asks the compiler directly at retrieval time. So it always returns a fully qualified class, since a file-level class usually exists (at least the way I'm using the compiler via Gradle).
Oh, I forgot: For the above code to work, it needs to be supplemented with these extension functions:
private fun IrSymbolOwner?.qualifiedClassName(): String? = when (this) {
is IrFile -> "${fqName.asQualificationPrefix()}${name.substringBeforeLast('.')}Kt"
is IrClass -> if (isLocal || isCompanion) null else fqnString(true)
else -> null
}
private fun FqName.asQualificationPrefix(): String = if (isRoot) "" else "$this."
So it was actually me creating a name for that fictious file-level class, regardless of whether such a class is actually generated or not.