web-dev-qa-db-fra.com

Mode d'enregistrement idiomatique à Kotlin

Kotlin n'a pas la même notion de champs statiques que celle utilisée en Java. En Java, la méthode de journalisation généralement acceptée est la suivante:

public class Foo {
    private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}

Question Quel est le moyen idiomatique d’effectuer la journalisation à Kotlin?

122
mchlstckl

Jetez un coup d’œil à la bibliothèque kotlin-logging .
Cela permet de se connecter comme ça:

private val logger = KotlinLogging.logger {}

class Foo {
  logger.info{"wohoooo $wohoooo"}
}

Ou comme ça: 

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

J'ai également écrit un billet de blog le comparant à AnkoLogger: Se connecter à Kotlin & Android: AnkoLogger vs kotlin-logging

Disclaimer: Je suis le mainteneur de cette bibliothèque.

Edit: kotlin-logging prend désormais en charge plusieurs plateformes: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

25
oshai

Comme bon exemple d’implémentation de la journalisation, je voudrais mentionner Anko qui utilise une interface spéciale AnkoLogger qu'une classe qui a besoin de journalisation doit implémenter. A l'intérieur de l'interface, un code génère une balise de journalisation pour la classe. La journalisation est ensuite effectuée via des fonctions d'extension qui peuvent être appelées dans l'implémentation interace sans préfixes ni même création d'instance de consignateur.

Je ne pense pas qu'il s'agisse de idiomatic, mais cela semble une bonne approche car elle nécessite un minimum de code. Il suffit d'ajouter l'interface à une déclaration de classe et de se connecter avec différentes balises pour différentes classes.


Le code ci-dessous est fondamentalement AnkoLogger , simplifié et réécrit pour un usage Android-agnostique.

Premièrement, il y a une interface qui se comporte comme une interface de marqueur:

interface MyLogger {
    val tag: String get() = javaClass.simpleName
}

Il permet à son implémentation d’utiliser les fonctions d’extension pour MyLogger dans leur code, en les appelant simplement sur this. Et il contient également une balise de journalisation.

Ensuite, il existe un point d’entrée général pour différentes méthodes de journalisation:

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

Il sera appelé par les méthodes de journalisation. Il obtient une balise de l'implémentation MyLogger, vérifie les paramètres de journalisation, puis appelle l'un des deux gestionnaires, l'un avec l'argument Throwable et l'autre sans.

Ensuite, vous pouvez définir autant de méthodes de journalisation que vous le souhaitez, de cette manière:

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

Celles-ci sont définies une fois pour la journalisation d'un message et la consignation d'une Throwable également. Cette opération est effectuée avec le paramètre optionnel throwable.

Les fonctions passées en tant que handler et throwableHandler peuvent être différentes pour différentes méthodes de journalisation. Par exemple, elles peuvent écrire le journal dans un fichier ou le télécharger quelque part. isLoggingEnabled et LoggingLevels sont omis pour des raisons de brièveté, mais leur utilisation offre encore plus de flexibilité.


Il permet l'utilisation suivante: 

class MyClass : MyLogger {
    fun myFun() {
        info("Info message")
    }
}

Il y a un petit inconvénient: un objet de journalisation sera nécessaire pour la journalisation des fonctions au niveau du paquet:

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}
7
hotkey

KISS: pour les équipes Java migrant vers Kotlin

Si cela ne vous dérange pas de fournir le nom de la classe à chaque instanciation de l'enregistreur (tout comme Java), vous pouvez le garder simple en le définissant comme une fonction de niveau supérieur quelque part dans votre projet:

import org.slf4j.LoggerFactory

inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.Java)

Ceci utilise un Kotlin paramètre de type réifié }.

Maintenant, vous pouvez utiliser ceci comme suit:

class SomeClass {
  // or within a companion object for one-instance-per-class
  val log = logger<SomeClass>()
  ...
}

Cette approche est très simple et se rapproche de l’équivalent Java, mais n’ajoute que du sucre syntaxique. 

Prochaine étape: extensions ou délégués

Personnellement, je préfère aller plus loin et utiliser l’approche des extensions ou des délégués. Ceci est bien résumé dans la réponse de @ JaysonMinard, mais voici le TL; DR pour l'approche "Délégué" avec l'API log4j2 (UPDATE: plus besoin d'écrire ce code manuellement, car il a été publié. en tant que module officiel du projet log4j2, voir ci-dessous). Depuis log4j2, contrairement à slf4j, prend en charge la journalisation avec Supplier, j'ai également ajouté un délégué pour simplifier l'utilisation de ces méthodes.

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 API de journalisation Kotlin

La majeure partie de la section précédente a été directement adaptée pour produire le module Kotlin Logging API , qui est maintenant une partie officielle de Log4j2 (disclaimer: je suis l'auteur principal). Vous pouvez télécharger ceci directement à partir d'Apache ou via Maven Central .

Usage est fondamentalement conforme à la description ci-dessus, mais le module prend en charge à la fois l'accès au consignateur basé sur une interface, une fonction d'extension logger sur Any pour une utilisation où this est défini et une fonction de journalisation nommée à utiliser sans this ( telles que les fonctions de haut niveau).

5
Raman

Anko

Vous pouvez utiliser Anko library pour le faire. Vous auriez un code comme ci-dessous:

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

enregistrement de kotlin

kotlin-logging ( projet Github - kotlin-logging ) vous permet d'écrire le code de journalisation comme ci-dessous:

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

StaticLog

ou vous pouvez aussi utiliser ce petit écrit dans la bibliothèque Kotlin appelé StaticLog alors votre code ressemblerait à ceci:

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 deuxième solution pourrait être préférable si vous souhaitez définir un format de sortie pour la méthode de journalisation, tel que:

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

ou utilisez des filtres, par exemple:

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

bois d'oeuvre

Si vous avez déjà utilisé la bibliothèque de journalisation Timber de Jake Wharton, vérifiez timberkt .

Cette bibliothèque s'appuie sur Timber avec une API plus facile à utiliser depuis Kotlin. Au lieu d'utiliser des paramètres de formatage, vous transmettez un lambda qui n'est évalué que si le message est enregistré.

Exemple de code:

// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())

// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }

Vérifiez également: Enregistrement dans Kotlin et Android: AnkoLogger vs kotlin-logging

J'espère que ça va aider

4
piotrek1543

Est-ce que quelque chose comme ce travail pour vous?

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

Qu'en est-il d'une fonction d'extension sur Class à la place? De cette façon, vous vous retrouvez avec:

public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.Java)

class SomeClass {
    val LOG = SomeClass::class.logger()
}

Note - Je n'ai pas testé cela du tout, donc ça pourrait ne pas être tout à fait correct.

1
Graham

Je n'ai entendu parler d'aucun langage à cet égard. Le plus simple sera le mieux, alors j'utiliserais une propriété de premier niveau

val logger = Logger.getLogger("package_name")

Cette pratique est utile en Python, et aussi différents que Kotlin et Python puissent paraître, je pense qu’ils sont assez similaires dans leur "esprit" (parlant d’idiomes).

1
voddan

créer un objet compagnon et marquer les champs appropriés avec l'annotation @JvmStatic

1
cleaning agent

Tout d'abord, vous pouvez ajouter des fonctions d'extension pour la création d'un enregistreur.

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

Ensuite, vous pourrez créer un enregistreur en utilisant le code suivant.

private val logger1 = getLogger<SomeClass>()
private val logger2 = getLogger()

Deuxièmement, vous pouvez définir une interface fournissant un enregistreur et son implémentation 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)

Cette interface peut être utilisée de la manière suivante.

class SomeClass : LoggerAware by loggerAware<SomeClass>() {
  // Now you can use a logger here.
}
1
Michael

Il y a déjà beaucoup de bonnes réponses ici, mais toutes concernent l'ajout d'un enregistreur à une classe, mais comment feriez-vous pour vous connecter à des fonctions de niveau supérieur?

Cette approche est générique et suffisamment simple pour fonctionner correctement dans les deux classes, les objets compagnons et les fonctions de niveau supérieur:

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

Exemple Slf4j, idem pour les autres. Cela fonctionne même pour créer un enregistreur au niveau du paquet

/**  
  * Get logger by current class name.  
  */ 

fun getLogger(c: () -> Unit): Logger = 
        LoggerFactory.getLogger(c.javaClass.enclosingClass)

Usage:

val logger = getLogger { }
0
Liu Dong

C’est à cela que servent les objets compagnons, en général: remplacer des objets statiques.

0
Jacob Zimmerman
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()
    }
}
0
tritot