library - Manera idiomática de iniciar sesión en Kotlin
kotlin anko toast (15)
KISS: Para equipos Java que migran a Kotlin
Si no le importa proporcionar el nombre de la clase en cada instancia del registrador (al igual que Java), puede mantenerlo simple definiéndolo como una función de nivel superior en algún lugar de su proyecto:
import org.slf4j.LoggerFactory
inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java)
Esto utiliza un parámetro de tipo reificado de Kotlin.
Ahora, puede usar esto de la siguiente manera:
class SomeClass {
// or within a companion object for one-instance-per-class
val log = logger<SomeClass>()
...
}
Este enfoque es súper simple y cercano al equivalente de Java, pero solo agrega algo de azúcar sintáctico.
Siguiente paso: extensiones o delegados
Personalmente prefiero ir un paso más allá y usar el enfoque de extensiones o delegados.
Esto se resume muy bien en la respuesta de @ JaysonMinard, pero aquí está el TL; DR para el enfoque "Delegado" con la API log4j2 (
ACTUALIZACIÓN
: ya no es necesario escribir este código manualmente, ya que se ha lanzado como un módulo oficial del proyecto log4j2, ver más abajo).
Dado que log4j2, a diferencia de slf4j, admite el registro con el
Supplier
, también he agregado un delegado para simplificar el uso de estos métodos.
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.util.Supplier
import kotlin.reflect.companionObject
/**
* An adapter to allow cleaner syntax when calling a logger with a Kotlin lambda. Otherwise calling the
* method with a lambda logs the lambda itself, and not its evaluation. We specify the Lambda SAM type as a log4j2 `Supplier`
* to avoid this. Since we are using the log4j2 api here, this does not evaluate the lambda if the level
* is not enabled.
*/
class FunctionalLogger(val log: Logger): Logger by log {
inline fun debug(crossinline supplier: () -> String) {
log.debug(Supplier { supplier.invoke() })
}
inline fun debug(t: Throwable, crossinline supplier: () -> String) {
log.debug(Supplier { supplier.invoke() }, t)
}
inline fun info(crossinline supplier: () -> String) {
log.info(Supplier { supplier.invoke() })
}
inline fun info(t: Throwable, crossinline supplier: () -> String) {
log.info(Supplier { supplier.invoke() }, t)
}
inline fun warn(crossinline supplier: () -> String) {
log.warn(Supplier { supplier.invoke() })
}
inline fun warn(t: Throwable, crossinline supplier: () -> String) {
log.warn(Supplier { supplier.invoke() }, t)
}
inline fun error(crossinline supplier: () -> String) {
log.error(Supplier { supplier.invoke() })
}
inline fun error(t: Throwable, crossinline supplier: () -> String) {
log.error(Supplier { supplier.invoke() }, t)
}
}
/**
* A delegate-based lazy logger instantiation. Use: `val log by logger()`.
*/
@Suppress("unused")
inline fun <reified T : Any> T.logger(): Lazy<FunctionalLogger> =
lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }
// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) {
ofClass.enclosingClass
} else {
ofClass
}
}
Log4j2 Kotlin Logging API
La mayor parte de la sección anterior se ha adaptado directamente para producir el módulo API de registro de Kotlin , que ahora es una parte oficial de Log4j2 (descargo de responsabilidad: soy el autor principal). Puede descargar esto directamente desde Apache , o mediante Maven Central .
Usage
es básicamente como se describe anteriormente, pero el módulo admite el acceso al registrador basado en la interfaz, una función de extensión del
logger
en
Any
para su uso donde está definido y una función de registrador con nombre para su uso donde no está definido (como las funciones de nivel superior )
Kotlin no tiene la misma noción de campos estáticos que la utilizada en Java. En Java, la forma generalmente aceptada de hacer registros es:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
La pregunta es ¿cuál es la forma idiomática de realizar el inicio de sesión en Kotlin?
Anko
Puedes usar la biblioteca
Anko
para hacerlo.
Tendría un código como el siguiente:
class MyActivity : Activity(), AnkoLogger {
private fun someMethod() {
info("This is my first app and it''s awesome")
debug(1234)
warn("Warning")
}
}
registro de kotlin
La biblioteca kotlin-logging ( kotlin-logging ) le permite escribir el código de registro como se muestra a continuación:
class FooWithLogging {
companion object: KLogging()
fun bar() {
logger.info{"Item $item"}
}
}
StaticLog
o también puede usar este pequeño escrito en la biblioteca de Kotlin llamado
StaticLog
entonces su código se vería así:
Log.info("This is an info message")
Log.debug("This is a debug message")
Log.warn("This is a warning message","WithACustomTag")
Log.error("This is an error message with an additional Exception for output", "AndACustomTag", exception )
Log.logLevel = LogLevel.WARN
Log.info("This message will not be shown")/
La segunda solución podría ser mejor si desea definir un formato de salida para el método de registro como:
Log.newFormat {
line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence)
}
o use filtros, por ejemplo:
Log.filterTag = "filterTag"
Log.info("This log will be filtered out", "otherTag")
Log.info("This log has the right tag", "filterTag")
timberkt
Si ya usó la biblioteca de registro de
Timber
Jake Wharton, consulte
timberkt
.
Esta biblioteca se basa en Timber con una API que es más fácil de usar desde Kotlin. En lugar de utilizar parámetros de formato, pasa una lambda que solo se evalúa si se registra el mensaje.
Ejemplo de código:
// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())
// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }
Verifique también: Iniciar sesión en Kotlin y Android: AnkoLogger vs kotlin-logging
Espero que ayude
¿Qué pasa con una función de extensión en clase en su lugar? De esa manera terminas con:
public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java)
class SomeClass {
val LOG = SomeClass::class.logger()
}
Nota: no he probado esto en absoluto, por lo que podría no ser del todo correcto.
¿Te gustaría algo como esto?
class LoggerDelegate {
private var logger: Logger? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
if (logger == null) logger = Logger.getLogger(thisRef!!.javaClass.name)
return logger!!
}
}
fun logger() = LoggerDelegate()
class Foo { // (by the way, everything in Kotlin is public by default)
companion object { val logger by logger() }
}
Como un buen ejemplo de implementación de registro, me gustaría mencionar a
Anko
que utiliza una interfaz especial
AnkoLogger
que debe implementar una clase que necesita registro.
Dentro de la interfaz hay un código que genera una etiqueta de registro para la clase.
Luego, el registro se realiza a través de funciones de extensión que se pueden llamar dentro de la implementación interactiva sin prefijos o incluso la creación de la instancia del registrador.
No creo que esto sea idiomático , pero parece un buen enfoque, ya que requiere un código mínimo, solo agrega la interfaz a una declaración de clase y obtienes un registro con diferentes etiquetas para diferentes clases.
El siguiente código es básicamente AnkoLogger , simplificado y reescrito para uso AnkoLogger de Android.
Primero, hay una interfaz que se comporta como una interfaz de marcador:
interface MyLogger {
val tag: String get() = javaClass.simpleName
}
Permite que su implementación use las funciones de extensiones para
MyLogger
dentro de su código simplemente llamándolos en
this
.
Y también contiene una etiqueta de registro.
A continuación, hay un punto de entrada general para diferentes métodos de registro:
private inline fun log(logger: MyLogger,
message: Any?,
throwable: Throwable?,
level: Int,
handler: (String, String) -> Unit,
throwableHandler: (String, String, Throwable) -> Unit
) {
val tag = logger.tag
if (isLoggingEnabled(tag, level)) {
val messageString = message?.toString() ?: "null"
if (throwable != null)
throwableHandler(tag, messageString, throwable)
else
handler(tag, messageString)
}
}
Se llamará por métodos de registro.
Obtiene una etiqueta de la implementación de
MyLogger
, comprueba la configuración de registro y luego llama a uno de los dos controladores, el que tiene argumento
Throwable
y el que no.
Luego puede definir tantos métodos de registro como desee, de esta manera:
fun MyLogger.info(message: Any?, throwable: Throwable? = null) =
log(this, message, throwable, LoggingLevels.INFO,
{ tag, message -> println("INFO: $tag # $message") },
{ tag, message, thr ->
println("INFO: $tag # $message # $throwable");
thr.printStackTrace()
})
Estos se definen una vez para registrar solo un mensaje y registrar un
Throwable
, esto se hace con un parámetro opcional.
Las funciones que se pasan como
handler
y
throwableHandler
pueden ser diferentes para diferentes métodos de registro, por ejemplo, pueden escribir el registro en un archivo o cargarlo en algún lugar.
isLoggingEnabled
y
LoggingLevels
se omiten por brevedad, pero su uso proporciona aún más flexibilidad.
Permite el siguiente uso:
class MyClass : MyLogger {
fun myFun() {
info("Info message")
}
}
Hay un pequeño inconveniente: se necesitará un objeto registrador para iniciar sesión en las funciones de nivel de paquete:
private object MyPackageLog : MyLogger
fun myFun() {
MyPackageLog.info("Info message")
}
Echa un vistazo a la
kotlin-logging
.
Permite iniciar sesión así:
private val logger = KotlinLogging.logger {}
class Foo {
logger.info{"wohoooo $wohoooo"}
}
O asi:
class FooWithLogging {
companion object: KLogging()
fun bar() {
logger.info{"wohoooo $wohoooo"}
}
}
También escribí una publicación en el blog comparándolo con
AnkoLogger
:
AnkoLogger
sesión en Kotlin y Android: AnkoLogger vs kotlin-logging
Descargo de responsabilidad: soy el encargado de mantener esa biblioteca.
Editar: kotlin-logging ahora tiene soporte multiplataforma: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support
Ejemplo de Slf4j, lo mismo para otros. Esto incluso funciona para crear un registrador de nivel de paquete
/**
* Get logger by current class name.
*/
fun getLogger(c: () -> Unit): Logger =
LoggerFactory.getLogger(c.javaClass.enclosingClass)
Uso:
val logger = getLogger { }
En la mayoría de los códigos Kotlin maduros, encontrará uno de estos patrones a continuación. El enfoque que usa Delegados de propiedades aprovecha el poder de Kotlin para producir el código más pequeño.
Nota: el código aquí es para
java.util.Logging
pero la misma teoría se aplica a cualquier biblioteca de registro
Estática (común, equivalente a su código Java en la pregunta)
Si no puede confiar en el rendimiento de esa búsqueda hash dentro del sistema de registro, puede obtener un comportamiento similar a su código Java mediante el uso de un objeto complementario que puede contener una instancia y sentirse como estático para usted.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
creando salida:
26 de diciembre de 2015 11:28:32
org..kotlin.test.MyClass
foo INFO: Hola desde MyClass
Más información sobre los objetos complementarios aquí:
Objetos complementarios
... También tenga en cuenta que en el ejemplo anterior
MyClass::class.java
obtiene la instancia de tipo
Class<MyClass>
para el registrador, mientras que
this.javaClass
obtendría la instancia de tipo
Class<MyClass.Companion>
.
Por instancia de una clase (común)
Pero, realmente no hay ninguna razón para evitar llamar y obtener un registrador a nivel de instancia. La forma idiomática de Java que mencionó está desactualizada y se basa en el miedo al rendimiento, mientras que el registrador por clase ya está almacenado en caché por casi cualquier sistema de registro razonable en el planeta. Simplemente cree un miembro para contener el objeto registrador.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
creando salida:
26 de diciembre de 2015 11:28:44 org..kotlin.test.MyClass foo INFO: Hola desde MyClass
Puede realizar pruebas de rendimiento tanto por variaciones de instancia como de clase y ver si hay una diferencia realista para la mayoría de las aplicaciones.
Delegados de propiedad (común, más elegante)
Otro enfoque, sugerido por @Jire en otra respuesta, es crear un delegado de propiedades, que luego puede usar para hacer la lógica de manera uniforme en cualquier otra clase que desee.
Hay una manera más simple de hacer esto ya que Kotlin ya proporciona un delegado
Lazy
, simplemente podemos envolverlo en una función.
Un truco aquí es que si queremos saber el tipo de clase que actualmente usa el delegado, lo convertimos en una función de extensión en cualquier clase:
fun <R : Any> R.logger(): Lazy<Logger> {
return lazy { Logger.getLogger(unwrapCompanionClass(this.javaClass).name) }
}
// see code for unwrapCompanionClass() below in "Putting it all Together section"
Este código también se asegura de que si lo usa en un Objeto complementario, el nombre del registrador será el mismo que si lo usara en la clase misma. Ahora puedes simplemente:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
para cada instancia de clase, o si desea que sea más estático con una instancia por clase:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Y su salida de llamar a
foo()
en ambas clases sería:
26 de diciembre de 2015 11:30:55 am org..kotlin.test.Something foo INFO: Hello from Something
26 de diciembre de 2015 11:30:55 AM org..kotlin.test.SomethingElse foo INFO: Hola de SomethingElse
Funciones de extensión (poco común en este caso debido a la "contaminación" de cualquier espacio de nombres)
Kotlin tiene algunos trucos ocultos que le permiten hacer que parte de este código sea aún más pequeño.
Puede crear funciones de extensión en clases y, por lo tanto, darles funcionalidad adicional.
Una sugerencia en los comentarios anteriores fue extender
Any
con una función de registrador.
Esto puede crear ruido cada vez que alguien usa la finalización de código en su IDE en cualquier clase.
Pero existe un beneficio secreto al extender
Any
o alguna otra interfaz de marcador: puede implicar que está extendiendo su propia clase y, por lo tanto, detectar la clase en la que se encuentra.
¿Eh?
Para ser menos confuso, aquí está el código:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Ahora dentro de una clase (u objeto complementario), simplemente puedo llamar a esta extensión en mi propia clase:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Producción de salida:
26 de diciembre de 2015 11:29:12 org..kotlin.test.SomethingDifferent foo INFO: Hola de SomethingDifferent
Básicamente, el código se ve como una llamada a la extensión
Something.logger()
.
El problema es que lo siguiente también podría ser cierto creando "contaminación" en otras clases:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Funciones de extensión en la interfaz de marcador (no estoy seguro de qué tan común, pero modelo común para "rasgos")
Para hacer que el uso de extensiones sea más limpio y reducir la "contaminación", puede usar una interfaz de marcador para extender:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
O incluso haga que el método forme parte de la interfaz con una implementación predeterminada:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Y use cualquiera de estas variaciones en su clase:
class MarkedClass: Loggable {
val LOG = logger()
}
Producción de salida:
26 de diciembre de 2015 11:41:01 org..kotlin.test.MarkedClass foo INFO: Hola de MarkedClass
Si desea forzar la creación de un campo uniforme para contener el registrador, mientras usa esta interfaz, podría requerir fácilmente que el implementador tenga un campo como
LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Ahora el implementador de la interfaz debe verse así:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Por supuesto, una clase base abstracta puede hacer lo mismo, teniendo la opción de que tanto la interfaz como una clase abstracta que implementen esa interfaz permitan flexibilidad y uniformidad:
abstract class WithLogging: Loggable {
override val LOG: Logger = logger()
}
// using the logging from the base class
class MyClass1: WithLogging() {
// ... already has logging!
}
// providing own logging compatible with marker interface
class MyClass2: ImportantBaseClass(), Loggable {
// ... has logging that we can understand, but doesn''t change my hierarchy
override val LOG: Logger = logger()
}
// providing logging from the base class via a companion object so our class hierarchy is not affected
class MyClass3: ImportantBaseClass() {
companion object : WithLogging() {
// we have the LOG property now!
}
}
Poniendo todo junto (una pequeña biblioteca auxiliar)
Aquí hay una pequeña biblioteca auxiliar para hacer que cualquiera de las opciones anteriores sea fácil de usar. Es común en Kotlin extender las API para que sean más de su agrado. Ya sea en extensión o funciones de nivel superior. Aquí hay una combinación para darle opciones sobre cómo crear registradores, y una muestra que muestra todas las variaciones:
// Return logger for Java class, if companion object fix the name
fun <T: Any> logger(forClass: Class<T>): Logger {
return Logger.getLogger(unwrapCompanionClass(forClass).name)
}
// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
return ofClass.enclosingClass?.takeIf {
ofClass.enclosingClass.kotlin.companionObject?.java == ofClass
} ?: ofClass
}
// unwrap companion class to enclosing class given a Kotlin Class
fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> {
return unwrapCompanionClass(ofClass.java).kotlin
}
// Return logger for Kotlin class
fun <T: Any> logger(forClass: KClass<T>): Logger {
return logger(forClass.java)
}
// return logger from extended class (or the enclosing class)
fun <T: Any> T.logger(): Logger {
return logger(this.javaClass)
}
// return a lazy logger property delegate for enclosing class
fun <R : Any> R.lazyLogger(): Lazy<Logger> {
return lazy { logger(this.javaClass) }
}
// return a logger property delegate for enclosing class
fun <R : Any> R.injectLogger(): Lazy<Logger> {
return lazyOf(logger(this.javaClass))
}
// marker interface and related extension (remove extension for Any.logger() in favour of this)
interface Loggable {}
fun Loggable.logger(): Logger = logger(this.javaClass)
// abstract base class to provide logging, intended for companion objects more than classes but works for either
abstract class WithLogging: Loggable {
val LOG = logger()
}
Elija cualquiera de los que desea conservar, y aquí están todas las opciones en uso:
class MixedBagOfTricks {
companion object {
val LOG1 by lazyLogger() // lazy delegate, 1 instance per class
val LOG2 by injectLogger() // immediate, 1 instance per class
val LOG3 = logger() // immediate, 1 instance per class
val LOG4 = logger(this.javaClass) // immediate, 1 instance per class
}
val LOG5 by lazyLogger() // lazy delegate, 1 per instance of class
val LOG6 by injectLogger() // immediate, 1 per instance of class
val LOG7 = logger() // immediate, 1 per instance of class
val LOG8 = logger(this.javaClass) // immediate, 1 instance per class
}
val LOG9 = logger(MixedBagOfTricks::class) // top level variable in package
// or alternative for marker interface in class
class MixedBagOfTricks : Loggable {
val LOG10 = logger()
}
// or alternative for marker interface in companion object of class
class MixedBagOfTricks {
companion object : Loggable {
val LOG11 = logger()
}
}
// or alternative for abstract base class for companion object of class
class MixedBagOfTricks {
companion object: WithLogging() {} // instance 12
fun foo() {
LOG.info("Hello from MixedBagOfTricks")
}
}
// or alternative for abstract base class for our actual class
class MixedBagOfTricks : WithLogging() { // instance 13
fun foo() {
LOG.info("Hello from MixedBagOfTricks")
}
}
Las 13 instancias de los registradores creados en este ejemplo producirán el mismo nombre de registrador y salida:
26 de diciembre de 2015 11:39:00 AM org..kotlin.test.MixedBagOfTricks foo INFO: Hola desde MixedBagOfTricks
Nota:
El método
unwrapCompanionClass()
asegura que no generemos un registrador con el nombre del objeto complementario, sino más bien la clase que lo encierra.
Esta es la forma recomendada actual para encontrar la clase que contiene el objeto complementario.
Eliminar "
$ Companion
" del nombre usando
removeSuffix()
no funciona ya que los objetos complementarios pueden recibir nombres personalizados.
Esto sigue siendo WIP (casi terminado), así que me gustaría compartirlo: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14
El objetivo principal de esta biblioteca es aplicar un cierto estilo de registro en un proyecto. Al hacer que genere el código de Kotlin, estoy tratando de abordar algunos de los problemas mencionados en esta pregunta. Con respecto a la pregunta original, lo que suelo hacer es simplemente:
private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {
}
No he oído hablar de ningún idioma al respecto. Cuanto más simple, mejor, así que usaría una propiedad de nivel superior
val logger = Logger.getLogger("package_name")
Esta práctica sirve bien en Python, y por muy diferentes que parezcan Kotlin y Python, creo que son bastante similares en su "espíritu" (hablando de expresiones idiomáticas).
Para eso son los objetos complementarios, en general: reemplazar cosas estáticas.
Primero, puede agregar funciones de extensión para la creación del registrador.
inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java)
fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass)
Entonces podrá crear un registrador utilizando el siguiente código.
private val logger1 = getLogger<SomeClass>()
private val logger2 = getLogger()
En segundo lugar, puede definir una interfaz que proporcione un registrador y su implementación mixin.
interface LoggerAware {
val logger: Logger
}
class LoggerAwareMixin(containerClass: Class<*>) : LoggerAware {
override val logger: Logger = LoggerFactory.getLogger(containerClass)
}
inline fun <reified T : Any> loggerAware() = LoggerAwareMixin(T::class.java)
Esta interfaz se puede usar de la siguiente manera.
class SomeClass : LoggerAware by loggerAware<SomeClass>() {
// Now you can use a logger here.
}
Ya hay muchas respuestas excelentes aquí, pero todas se refieren a agregar un registrador a una clase, pero ¿cómo haría eso para iniciar sesión en las Funciones de nivel superior?
Este enfoque es genérico y lo suficientemente simple como para funcionar bien en ambas clases, objetos complementarios y funciones de nivel superior:
package nieldw.test
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.junit.jupiter.api.Test
fun logger(lambda: () -> Unit): Lazy<Logger> = lazy { LogManager.getLogger(getClassName(lambda.javaClass)) }
private fun <T : Any> getClassName(clazz: Class<T>): String = clazz.name.replace(Regex("""/$.*$"""), "")
val topLog by logger { }
class TopLevelLoggingTest {
val classLog by logger { }
@Test
fun `What is the javaClass?`() {
topLog.info("THIS IS IT")
classLog.info("THIS IS IT")
}
}
cree un objeto complementario y marque los campos apropiados con la anotación @JvmStatic
fun <R : Any> R.logger(): Lazy<Logger> = lazy {
LoggerFactory.getLogger((if (javaClass.kotlin.isCompanion) javaClass.enclosingClass else javaClass).name)
}
class Foo {
val logger by logger()
}
class Foo {
companion object {
val logger by logger()
}
}