Manera idiomática de iniciar sesión en Kotlin

164

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?

mchlstckl
fuente
1
No publico esto como respuesta porque está muy lejos de la forma de Java, pero he considerado escribir una función de extensión en Any para iniciar sesión. Por supuesto, debe almacenar en caché los registradores, pero creo que esta sería una buena manera de hacerlo.
mhlz
1
@mhlz ¿No se resolvería estáticamente esa función de extensión? Como en, ¿no se aplicaría a todos los objetos, solo a los de tipo Any(por lo que necesita un yeso)?
Jire
1
@mhlz una función de extensión no tiene sentido porque no tendrá un estado para mantener un registrador. Podría ser una extensión para devolver un registrador, pero ¿por qué tener eso en cada clase conocida en el sistema? Poner extensiones en Any tiende a convertirse en un ruido descuidado en el IDE más tarde. @Jire, la extensión se aplicará a todos los descendientes de Any, aún devolverá la correcta this.javaClasspara cada uno. Pero no lo recomiendo como solución.
Jayson Minard

Respuestas:

250

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.Loggingpero 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.stackoverflow.kotlin.test.MyClassfoo 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.javaobtiene la instancia de tipo Class<MyClass>para el registrador, mientras this.javaClassque obtendría la instancia de tipo Class<MyClass.Companion>.

Por instancia de una clase (común)

Pero, realmente no hay 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.stackoverflow.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 Lazydelegado, 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 resultado de llamar foo()a estas dos clases sería:

26 de diciembre de 2015 11:30:55 am org.stackoverflow.kotlin.test.Something foo INFO: Hello from Something

26 de diciembre de 2015 11:30:55 am org.stackoverflow.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 Anycon 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 Anyu 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 am 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 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.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, 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.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hola de MixedBagOfTricks

Nota: El unwrapCompanionClass()método garantiza que no generemos un registrador con el nombre del objeto complementario, sino más bien la clase adjunta. 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.

Jayson Minard
fuente
Algunos marcos de inyección de dependencia usan delegados como se ve en otra respuesta aquí. Se ven como `val log: Logger by injectLogger ()` y permiten que el sistema de registro sea inyectado y desconocido para el código que usa. (Mi marco de inyección que muestra esto está en github.com/kohesive/injekt )
Jayson Minard
10
Gracias por la extensa respuesta. Muy informativo. Particularmente me gusta la implementación de Delegados de Propiedad (común, más elegante) .
mchlstckl
66
Creo que hubo un cambio en la sintaxis de Kotlin. y el Separar debe ser ofClass.enclosingClass.kotlin.objectInstance?.javaClassen lugar deofClass.enclosingClass.kotlin.companionObject?.java
oshai
1
ah, no importa, como se indica aquí kotlinlang.org/docs/reference/reflection.html el tarro reflector se envía por separado del stdlib, para gradle necesitamos esto:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran
1
El código para crear los 'Delegados de propiedades' y las 'Funciones de extensión' parece ser el mismo, excepto para el tipo de retorno. El ejemplo de código para el Delegado de propiedades ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) parece crear una función de extensión tal que "".logger()ahora es una cosa, ¿se supone que esto se comporta de esta manera?
Mike Rylander
32

Echa un vistazo a la biblioteca de registro de kotlin .
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 de blog comparándolo con AnkoLogger: Iniciar 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

oshai
fuente
¿Puedo sugerirle que edite su respuesta para mostrar el resultado de las logger.info()llamadas, como lo hizo Jayson en su respuesta aceptada?
Paulo Merson
7

Como un buen ejemplo de implementación de registro, me gustaría mencionar a Anko, que utiliza una interfaz especial AnkoLoggerque 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 agregando 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 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 solo con invocarlas 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 deMyLogger implementación, verifica la configuración de registro y luego llama a uno de los dos controladores, el que tiene Throwableargumento 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 throwableparámetro opcional .

Las funciones que se pasan como handlery throwableHandlerpueden ser diferentes para diferentes métodos de registro, por ejemplo, pueden escribir el registro en un archivo o cargarlo en algún lugar.isLoggingEnabledy LoggingLevelsse omiten por brevedad, pero usarlos 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")
}
tecla de acceso directo
fuente
Esta respuesta es específica de Android, y la pregunta no menciona ni tiene una etiqueta de Android.
Jayson Minard
@JaysonMinard, ¿por qué? Este enfoque es de propósito general ya que, por ejemplo, tener una etiqueta de registro única para cada clase también es útil en proyectos que no son de Android.
tecla
1
No está claro que diga "implementar algo similar a lo que hizo Anko" y en cambio parece más como "usar Anko" ... que luego requiere una biblioteca de Android llamada Anko. Que tiene una interfaz que tiene funciones de extensión que llaman android.util.Logpara hacer el registro. ¿Cuál fue tu intención? usar Anko? De construir algo similar mientras se usa Anko como ejemplo (es mejor si solo pone el código sugerido en línea y lo arregla para no Android en lugar de decir "portar esto a no Android, aquí está el enlace". En su lugar, agregue un código de muestra llamando a Anko)
Jayson Minard
1
@ JaysonMinard, gracias por sus comentarios, he reescrito la publicación para que ahora explique el enfoque en lugar de hacer referencia a Anko.
tecla
6

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 un poco de azúcar sintáctica.

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). Como log4j2, a diferencia de slf4j, admite el registro con Supplier's, 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 .

El uso es básicamente como se describe anteriormente, pero el módulo admite el acceso al registrador basado en la interfaz, una loggerfunción de extensión activada Anypara su uso donde thisse define y una función de registrador con nombre para su uso donde no thisse define (como las funciones de nivel superior).

Raman
fuente
1
Si tengo razón, puede evitar escribir el nombre de la clase en la primera solución que proporcionó cambiando la firma del método a T.logger ()
IPat
1
@IPat sí, la primera solución intencionalmente no hace eso para permanecer cerca de la "forma de Java". La segunda parte de la respuesta cubre el caso de extensión T.logger(): consulte la parte inferior del ejemplo de código.
Raman
5

Anko

Puedes usar la Ankobiblioteca 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 ( proyecto Github - kotlin-logging ) le permite escribir el código de registro de la siguiente manera:

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 StaticLogentonces 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 ha utilizado la Timberverificación de la biblioteca de registro de Jake Wharton 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

piotrek1543
fuente
4

¿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() }
}
Jire
fuente
1
Esta respuesta necesita más explicaciones, si la persona que pregunta no comprende los objetos complementarios, probablemente no ha llegado a los delegados y, por lo tanto, no sabrá lo que está haciendo. Además, hay muy pocos ahorros en el código con este modelo. Y dudo que el almacenamiento en caché en el objeto complementario sea realmente una ganancia de rendimiento que no sea en un sistema restringido con una CPU pequeña como Android.
Jayson Minard
1
Lo que muestra este código anterior es la creación de una clase que actúa como Delegado (ver kotlinlang.org/docs/reference/delegated-properties.html ) que es la primera clase LoggerDelegate Y luego está creando una función de nivel superior que está haciendo es más fácil crear una instancia del delegado (no mucho más fácil, pero sí un poco). Y esa función debe cambiarse para ser inline. Luego, utiliza el delegado para proporcionar un registrador cuando lo desee. Pero proporciona uno para el acompañante Foo.Companiony no para la clase, Foopor lo que tal vez no sea el previsto.
Jayson Minard
@JaysonMinard Estoy de acuerdo, pero dejaré la respuesta para futuros espectadores que quieran una "solución rápida" o un ejemplo de cómo aplicar esto a sus propios proyectos. No entiendo por qué la logger()función debería ser inlinesi no hay lambdas presentes. IntelliJ sugiere que la alineación en este caso es innecesaria: i.imgur.com/YQH3NB1.png
Jire
1
Incorporé su respuesta a la mía y la simplifiqué eliminando la clase de delegado personalizada y en su lugar utilicé un contenedor Lazy. Con un truco para que sepa en qué clase está.
Jayson Minard
1

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

voddan
fuente
El nivel superior también se conoce como nivel de paquete.
Caelum
Una variable de nivel superior es como decir "usar variables globales" y creo que solo sería aplicable si tuviera otras funciones de nivel superior que necesitaran usar un registrador. Sin embargo, en ese punto, podría ser mejor pasar un registrador a cualquier función de utilidad que desee iniciar sesión.
Jayson Minard
1
@ JaysonMinard Creo que pasar el registrador como parámetro sería un antipatrón, porque su registro nunca debería afectar su API, externa o interna
voddan
Ok, luego volviendo a mi punto, para el registro a nivel de clase, coloque el registrador en la clase, no en una función de nivel superior.
Jayson Minard
1
@voddan al menos proporciona un ejemplo completo de qué tipo de registrador está creando. val log = what?!? ... creando un registrador por nombre? Ignorando el hecho de que la pregunta mostraba que quería crear un registrador para una clase específicaLoggerFactory.getLogger(Foo.class);
Jayson Minard el
1

¿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.

Graham
fuente
1

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.
}
Miguel
fuente
1

cree un objeto complementario y marque los campos apropiados con la anotación @JvmStatic

agente de limpieza
fuente
1

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")
    }
}
Niel de Wet
fuente
0

Para eso son los objetos complementarios, en general: reemplazar cosas estáticas.

Jacob Zimmerman
fuente
Un objeto complementario no es estático, es un singleton que puede contener miembros que pueden volverse estáticos si usa una JvmStaticanotación. Y en el futuro puede haber más de uno permitido. Además, esta respuesta no es muy útil sin más información o una muestra.
Jayson Minard
No dije que fuera una estática. Dije que era para reemplazar la estática. ¿Y por qué habría más de uno permitido? Eso no tiene sentido. Por último, tenía prisa y pensé que apuntar en la dirección correcta sería lo suficientemente útil.
Jacob Zimmerman
1
Un objeto complementario no es para reemplazar las estadísticas, sino que también puede hacer que sus elementos sean estáticos. Kotlin apoyó más que un compañero durante un tiempo y les permite tener otros nombres. Una vez que comienzas a nombrarlos, actúan menos como estáticos. Y se deja abierto en el futuro tener más de un compañero nombrado. Por ejemplo, uno podría ser Factoryy otroHelpers
Jayson Minard
0

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 { }
Liu Dong
fuente
0
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()
    }
}
tritot
fuente
0

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 {

}
Leandro
fuente
0

Simplemente puede construir su propia "biblioteca" de utilidades. No necesita una biblioteca grande para esta tarea, lo que hará que su proyecto sea más pesado y complejo.

Por ejemplo, puede usar Kotlin Reflection para obtener el nombre, tipo y valor de cualquier propiedad de clase.

En primer lugar, asegúrese de tener la metadependencia establecida en su build.gradle:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}

Luego, simplemente puede copiar y pegar este código en su proyecto:

import kotlin.reflect.full.declaredMemberProperties

class LogUtil {
    companion object {
        /**
         * Receives an [instance] of a class.
         * @return the name and value of any member property.
         */
        fun classToString(instance: Any): String {
            val sb = StringBuilder()

            val clazz = instance.javaClass.kotlin
            clazz.declaredMemberProperties.forEach {
                sb.append("${it.name}: (${it.returnType}) ${it.get(instance)}, ")
            }

            return marshalObj(sb)
        }

        private fun marshalObj(sb: StringBuilder): String {
            sb.insert(0, "{ ")
            sb.setLength(sb.length - 2)
            sb.append(" }")

            return sb.toString()
        }
    }
}

Ejemplo de uso:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
rocammo
fuente