studio library framework commons async kotlin kotlin-logging

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