Forma idiomática de iniciar session en Kotlin

Kotlin no tiene la misma noción de campos estáticos que se usan en Java. En Java, la forma generalmente aceptada de hacer el logging 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 la registración en Kotlin?

    En la mayoría del código de Kotlin maduro, encontrará uno de estos patrones a continuación. El enfoque que usa Property Delegates 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 logging

    Estático (común, equivalente a su código Java en la pregunta)

    Si no puede confiar en el performance de esa búsqueda hash dentro del sistema de logging, puede get un comportamiento similar a su código Java mediante el uso de un object complementario que puede contener una instancia y sentirse como un elemento 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 a.m. org.stackoverflow.kotlin.test.MyClass foo INFO: Hello from MyClass

    Más información sobre los objects complementarios aquí: https://kotlinlang.org/docs/reference/classes.html#companion-objects … También tenga en count 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 class (común)

    Pero, realmente no hay ninguna razón para evitar llamar y get un registrador en el nivel de instancia. La forma idiomática de Java que mencionaste está desactualizada y se basa en el miedo al performance, mientras que el registrador por class ya está en la memory caching de casi cualquier sistema de logging razonable en el planeta. Solo crea un miembro para mantener el object logger.

     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 a.m. org.stackoverflow.kotlin.test.MyClass foo INFO: Hello from MyClass

    Puede realizar testings de performance por instancia y por variaciones de class y ver si hay una diferencia realist para la mayoría de las aplicaciones.

    Delegados de propiedad (comunes, más elegantes)

    Otro enfoque, sugerido por @Jire en otra respuesta, es crear un delegado de propiedad, que luego puede usar para hacer la lógica de manera uniforme en cualquier otra class que desee. Hay una forma más simple de hacerlo, ya que Kotlin ya proporciona un delegado Lazy , podemos simplemente envolverlo en una function. Un truco aquí es que si queremos saber el tipo de la class que actualmente usa el delegado, la convertimos en una function de extensión en cualquier class:

     public 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 asegura que si lo usa en un object acompañante, el nombre del registrador será el mismo que si lo hubiera usado en la class misma. Ahora puedes simplemente:

     class Something { val LOG by logger() fun foo() { LOG.info("Hello from Something") } } 

    para la instancia por class, o si desea que sea más estática con una instancia por class:

     class SomethingElse { companion object { val LOG by logger() } fun foo() { LOG.info("Hello from SomethingElse") } } 

    Y su salida de llamar a foo() en ambas classs sería:

    26 de diciembre de 2015 11:30:55 a.m. org.stackoverflow.kotlin.test.Something foo INFORMACIÓN: Hola de algo

    26 de diciembre de 2015 11:30:55 a.m. org.stackoverflow.kotlin.test.SomethingElse foo INFORMACIÓN: 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 te permiten hacer que parte de este código sea aún más pequeño. Puede crear funciones de extensión en classs y, por lo tanto, darles funcionalidad adicional. Una sugerencia en los comentarios anteriores fue extender Any con una function de registrador. Esto puede generar ruido cada vez que alguien utiliza la finalización de código en su IDE en cualquier class. Pero hay un beneficio secreto de extender Any o alguna otra interfaz de marcador: puede implicar que está ampliando su propia class y, por lo tanto, detecta la class en la que se encuentra. ¿Huh? Para ser less confuso, aquí está el código:

     // extend any class with the ability to get a logger public fun <T: Any> T.logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } 

    Ahora dentro de una class (u object complementario) puedo simplemente llamar a esta extensión en mi propia class:

     class SomethingDifferent { val LOG = logger() fun foo() { LOG.info("Hello from SomethingDifferent") } } 

    Producción de salida:

    26-dic-2015 11:29:12 a.m. org.stackoverflow.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 classs:

     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 el model común para "rasgos")

    Para hacer el uso de las extensiones más limpias y networkingucir la "contaminación", puede usar una interfaz de marcador para ampliar:

     interface Loggable {} public 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 pnetworkingeterminada:

     interface Loggable { public fun logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } } 

    Y use cualquiera de estas variaciones en su class:

     public class MarkedClass: Loggable { val LOG = logger() } 

    Producción de salida:

    26 de diciembre 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hola de MarkedClass

    Si desea forzar la creación de un campo uniforme para contener el registrador, al usar esta interfaz puede requerir fácilmente que el implementador tenga un campo como LOG :

     interface Loggable { val LOG: Logger // abstract requinetworking field public fun logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } } 

    Ahora el implementador de la interfaz debe verse así:

     public class MarkedClass: Loggable { override val LOG: Logger = logger() } 

    Por supuesto, una class base abstracta puede hacer lo mismo, tener la opción tanto de la interfaz como de una class abstracta que implemente esa interfaz permite flexibilidad y uniformidad:

     abstract class WithLogging: Loggable { override val LOG: Logger = logger() } // using the logging from the base class public class MyClass1: WithLogging() { // ... already has logging! } // providing own logging compatible with marker interface public 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 public class MyClass3: ImportantBaseClass() { companion object : WithLogging() { // we have the LOG property now! } } 

    Poniéndolo todo junto (Una pequeña biblioteca de ayuda)

    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 ampliar las API para hacerlas más a su gusto. Ya sea en funciones de extensión o 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 public fun <T: Any> logger(forClass: Class<T>): Logger { return Logger.getLogger(unwrapCompanionClass(forClass).name) } // unwrap companion class to enclosing class given a Java Class public fun <T: Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> { return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) { ofClass.enclosingClass } else { ofClass } } // unwrap companion class to enclosing class given a Kotlin Class public fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> { return unwrapCompanionClass(ofClass.java).kotlin } // Return logger for Kotlin class public fun <T: Any> logger(forClass: KClass<T>): Logger { return logger(forClass.java) } // return logger from extended class (or the enclosing class) public fun <T: Any> T.logger(): Logger { return logger(this.javaClass) } // return a lazy logger property delegate for enclosing class public fun <R : Any> R.lazyLogger(): Lazy<Logger> { return lazy { logger(this.javaClass) } } // return a logger property delegate for enclosing class public 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 {} public fun Loggable.logger(): Logger = logger(this.javaClass) // abstract base class to provide logging, intended for companion objects more than classes but works for either public abstract class WithLogging: Loggable { val LOG = logger() } 

    Elija cualquiera de los que quiera 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 esta muestra producirán el mismo nombre de registrador y generarán:

    26 de diciembre de 2015, 11:39:00 a.m. org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hello from MixedBagOfTricks

    Nota: El método unwrapCompanionClass () garantiza que no generemos un registrador que tenga el nombre del object complementario, sino la class adjunta. Esta es la forma actual recomendada de encontrar la class que contiene el object complementario. Eliminar "$ Companion" del nombre con removeSuffix() no funciona, ya que a los objects complementarios se les pueden removeSuffix() nombres personalizados.

    Eche un vistazo a la biblioteca de logging de kotlin .
    Permite iniciar session de esta manera:

     private val logger = KotlinLogging.logger {} class Foo { logger.info{"wohoooo $wohoooo"} } 

    O así:

     class FooWithLogging { companion object: KLogging() fun bar() { logger.info{"wohoooo $wohoooo"} } } 

    También escribí una publicación de blog comparándola con AnkoLogger : https://medium.com/@OhadShai/logging-in-android-ankologger-vs-kotlin-logging-bb693671442a

    Descargo de responsabilidad: soy el mantenedor de esa biblioteca 😉

    Como un buen ejemplo de implementación de logging, me gustaría mencionar a Anko que utiliza una interfaz especial AnkoLogger que debe implementar una class que necesita logging. Dentro de la interfaz hay un código que genera una label de logging para la class. El logging se realiza a través de funciones de extensión que se pueden llamar dentro de la implementación de interace sin prefijos o incluso creación de instancia de 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 statement de class, y obtienes el logging con diferentes tags para diferentes classs.


    El código siguiente es básicamente AnkoLogger , simplificado y reescrito para uso independiente 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 sobre this . Y también contiene la label de logging.

    A continuación, hay un punto de input general para diferentes methods de logging:

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

    Será llamado por methods de logging. Obtiene una label de la implementación de MyLogger , comtesting la configuration de logging y luego llama a uno de los dos controlleres, el que tiene un argumento Throwable y el que no tiene.

    Luego puede definir tantos methods de logging como quiera, de esta forma:

     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 tanto para registrar solo un post como para iniciar un Throwable , esto se hace con el parámetro throwable opcional.

    Las funciones que se pasan como handler y throwableHandler pueden ser diferentes para diferentes methods de logging, por ejemplo, pueden escribir el logging en un file o cargarlo en alguna parte. isLoggingEnabled y LoggingLevels se han omitido 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 object logger para iniciar session en las funciones de nivel de package:

     private object MyPackageLog : MyLogger fun myFun() { MyPackageLog.info("Info message") } 

    ¿Algo como esto funcionaría para ti?

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

    Anko

    Puedes usar la biblioteca de Anko para hacerlo. Tendría código como a continuación:

     class MyActivity : Activity(), AnkoLogger { private fun someMethod() { info("This is my first app and it's awesome") debug(1234) warn("Warning") } } 

    kotlin-logging

    La biblioteca kotlin-logging ( https://github.com/MicroUtils/kotlin-logging ) le permite escribir el código de logging de la siguiente manera:

     class FooWithLogging { companion object: KLogging() fun bar() { logger.info{"Item $item"} } } 

    StaticLog

    o también puedes usar esta pequeña biblioteca escrita en Kotlin llamada StaticLog entonces tu código se StaticLog 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 formatting de salida para el método de logging como:

     Log.newFormat { line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence) } 

    o use filters, por ejemplo:

     Log.filterTag = "filterTag" Log.info("This log will be filtenetworking out", "otherTag") Log.info("This log has the right tag", "filterTag") 

    timberkt

    Si ya habías utilizado la biblioteca maderera de Jake Wharton, verifica timberkt .

    Esta biblioteca se basa en Timber con una API que es más fácil de usar de Kotlin. En lugar de utilizar parameters de formatting, pasa una lambda que solo se evalúa si el post se registra.

    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 session en Kotlin y Android: AnkoLogger vs kotlin-logging

    Espero que ayude

    KISS: para los equipos de Java que migran a Kotlin

    Si no le importa proporcionar el nombre de class en cada creación de instancias del registrador (al igual que java), puede mantenerlo simple al definir esto como una function de alto nivel en algún lugar de su proyecto:

     import org.slf4j.LoggerFactory inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java) 

    Esto usa un parámetro de tipo reificado 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áctica.

    Siguiente paso: extensiones o delegates

    Personalmente prefiero ir un paso más allá y usar el enfoque de extensiones o delegates. Esto se resume muy bien en la respuesta de @JaysonMinard, pero aquí está el TL; DR para el enfoque "Delegado" con la API log4j2. Dado que log4j2, a diferencia de slf4j, admite el logging con Supplier 's, también he agregado un delegado para simplificar el uso de estos methods.

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

    ¿Qué pasa con una function de extensión en Clase en su lugar? De esa forma 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.

    Primero, puede agregar funciones de extensión para la creación de registradores.

     inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java) fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass) 

    Luego podrá crear un registrador usando 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. } 

    Para eso están los objects complementarios, en general: reemplazando cosas estáticas.

    I have heard of no idiom in this regard. The simpler the better, so I would use a top-level property

     val logger = Logger.getLogger("package_name") 

    This practice serves well in Python, and as different as Kotlin and Python might appear, I believe they are quite similar in there "spirit" (speaking of idioms).

    Slf4j example, same for others. This even works for creating package level logger

     /** * Get logger by current class name. */ fun getLogger(c: () -> Unit): Logger = LoggerFactory.getLogger(c.javaClass.enclosingClass) 

    Usage:

     val logger = getLogger { }