Manière idiomatique de se connecter à Kotlin


164

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

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

La question est quelle est la manière idiomatique d'effectuer la journalisation dans Kotlin?


1
Ne pas publier cela comme réponse car c'est loin de la méthode Java, mais j'ai envisagé d'écrire une fonction d'extension sur Any pour la journalisation. Vous devez bien sûr mettre en cache les enregistreurs, mais je pense que ce serait une bonne façon de le faire.
mhlz

1
@mhlz Cette fonction d'extension ne serait-elle pas résolue statiquement? Comme dans, il ne serait pas appliqué à tous les objets, uniquement à ceux de type Any(nécessitant donc un cast)?
Jire

1
@mhlz une fonction d'extension n'a pas de sens car elle n'aura pas d'état pour garder un enregistreur. Cela pourrait être une extension pour renvoyer un enregistreur, mais pourquoi l'avoir sur chaque classe connue du système? Mettre des extensions sur Any a tendance à devenir un bruit bâclé dans l'EDI plus tard. @Jire l'extension s'appliquera à tous les descendants de Any, retournera toujours le bon this.javaClasspour chacun. Mais je ne le recommande pas comme solution.
Jayson Minard

Réponses:


251

Dans la majorité du code Kotlin mature, vous trouverez l'un de ces modèles ci-dessous. L'approche utilisant les délégués de propriété tire parti de la puissance de Kotlin pour produire le plus petit code.

Remarque: le code ici est pour java.util.Loggingmais la même théorie s'applique à toute bibliothèque de journalisation

Statique (commun, équivalent de votre code Java dans la question)

Si vous ne pouvez pas faire confiance aux performances de cette recherche de hachage dans le système de journalisation, vous pouvez obtenir un comportement similaire à votre code Java en utilisant un objet compagnon qui peut contenir une instance et vous sembler statique.

class MyClass {
    companion object {
        val LOG = Logger.getLogger(MyClass::class.java.name) 
    }

    fun foo() {
        LOG.warning("Hello from MyClass")
    }
}  

création de sortie:

26 décembre 2015 11:28:32 org.stackoverflow.kotlin.test.MyClassfoo INFO: Bonjour de MyClass

Plus d'informations sur les objets compagnons ici: Objets compagnons ... Notez également que dans l'exemple ci-dessus MyClass::class.javaobtient l'instance de type Class<MyClass>pour le logger, alors que this.javaClasscela obtiendrait l'instance de type Class<MyClass.Companion>.

Par instance d'une classe (commun)

Mais il n'y a vraiment aucune raison d'éviter d'appeler et d'obtenir un enregistreur au niveau de l'instance. La méthode Java idiomatique que vous avez mentionnée est obsolète et basée sur la peur des performances, alors que l'enregistreur par classe est déjà mis en cache par presque tous les systèmes de journalisation raisonnables sur la planète. Créez simplement un membre pour contenir l'objet enregistreur.

class MyClass {
  val LOG = Logger.getLogger(this.javaClass.name)

  fun foo() {
        LOG.warning("Hello from MyClass")
  }
} 

création de sortie:

26 décembre 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Bonjour de MyClass

Vous pouvez tester les performances des variations par instance et par classe et voir s'il existe une différence réaliste pour la plupart des applications.

Délégués de propriété (communs, les plus élégants)

Une autre approche, suggérée par @Jire dans une autre réponse, consiste à créer un délégué de propriété, que vous pouvez ensuite utiliser pour appliquer la logique uniformément dans toute autre classe de votre choix. Il existe un moyen plus simple de le faire puisque Kotlin fournit Lazydéjà un délégué, nous pouvons simplement l'envelopper dans une fonction. Une astuce ici est que si nous voulons connaître le type de la classe utilisant actuellement le délégué, nous en faisons une fonction d'extension sur n'importe quelle classe:

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"

Ce code garantit également que si vous l'utilisez dans un objet compagnon, le nom de l'enregistreur sera le même que si vous l'aviez utilisé sur la classe elle-même. Maintenant, vous pouvez simplement:

class Something {
    val LOG by logger()

    fun foo() {
        LOG.info("Hello from Something")
    }
}

pour chaque instance de classe, ou si vous voulez qu'elle soit plus statique avec une instance par classe:

class SomethingElse {
    companion object {
        val LOG by logger()

    }

    fun foo() {
        LOG.info("Hello from SomethingElse")
    }
}

Et votre résultat de l'appel foo()à ces deux classes serait:

26 décembre 2015 11:30:55 AM org.stackoverflow.kotlin.test Quelque chose foo INFO: Bonjour de Something

26 décembre 2015 11:30:55 AM org.stackoverflow.kotlin.test.SomethingElse foo INFO: Bonjour de SomethingElse

Fonctions d'extension (rare dans ce cas en raison de la "pollution" de n'importe quel espace de noms)

Kotlin a quelques astuces cachées qui vous permettent de rendre une partie de ce code encore plus petite. Vous pouvez créer des fonctions d'extension sur les classes et ainsi leur donner des fonctionnalités supplémentaires. Une suggestion dans les commentaires ci-dessus était d'étendre Anyavec une fonction d'enregistrement. Cela peut créer du bruit à chaque fois que quelqu'un utilise la complétion de code dans son IDE dans n'importe quelle classe. Mais il y a un avantage secret à étendre Anyou à une autre interface de marqueur: vous pouvez impliquer que vous étendez votre propre classe et donc détectez la classe dans laquelle vous vous trouvez. Hein? Pour être moins déroutant, voici le code:

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

Maintenant, dans une classe (ou un objet compagnon), je peux simplement appeler cette extension sur ma propre classe:

class SomethingDifferent {
    val LOG = logger()

    fun foo() {
        LOG.info("Hello from SomethingDifferent")
    }
}

Production de sortie:

26 décembre 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Bonjour de SomethingDifferent

Fondamentalement, le code est considéré comme un appel à l'extension Something.logger(). Le problème est que ce qui suit pourrait également être vrai créant de la «pollution» sur d'autres classes:

val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()

Fonctions d'extension sur l'interface des marqueurs ( je ne sais pas à quel point il est courant, mais modèle commun pour les «traits»)

Pour rendre l'utilisation des extensions plus propre et réduire la "pollution", vous pouvez utiliser une interface de marqueur pour étendre:

interface Loggable {} 

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

Ou même intégrez la méthode à l'interface avec une implémentation par défaut:

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

Et utilisez l'une de ces variantes dans votre classe:

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

Production de sortie:

26 décembre 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: Bonjour de MarkedClass

Si vous vouliez forcer la création d'un champ uniforme pour contenir l'enregistreur, alors en utilisant cette interface, vous pourriez facilement demander à l'implémenteur d'avoir un champ tel que LOG:

interface Loggable {
    val LOG: Logger  // abstract required field

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

Maintenant, l'implémenteur de l'interface doit ressembler à ceci:

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

Bien sûr, une classe de base abstraite peut faire la même chose, ayant l'option à la fois de l'interface et d'une classe abstraite implémentant cette interface permet la flexibilité et l'uniformité:

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

Mettre tout ensemble (une petite bibliothèque d'aide)

Voici une petite bibliothèque d'aide pour rendre l'une des options ci-dessus facile à utiliser. Il est courant dans Kotlin d'étendre les API pour les rendre plus à votre goût. Soit dans les fonctions d'extension ou de niveau supérieur. Voici un mélange pour vous donner des options sur la façon de créer des enregistreurs, et un exemple montrant toutes les variations:

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

Choisissez celle que vous souhaitez conserver et voici toutes les options utilisées:

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

Les 13 instances des enregistreurs créés dans cet exemple produiront le même nom d'enregistreur et produiront:

26 décembre 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Bonjour de MixedBagOfTricks

Remarque: La unwrapCompanionClass()méthode garantit que nous ne générons pas un enregistreur nommé d'après l'objet compagnon mais plutôt la classe englobante. Il s'agit de la méthode actuellement recommandée pour rechercher la classe contenant l'objet compagnon. Supprimer " $ Companion " du nom à l'aide de removeSuffix()ne fonctionne pas car les objets compagnons peuvent recevoir des noms personnalisés.


Certains frameworks d'injection de dépendances utilisent des délégués comme vous le voyez dans une autre réponse ici. Ils ressemblent à `val log: Logger by injectLogger ()` et permettent au système de journalisation d'être injecté et inconnu du code utilisateur. (Mon cadre d'injection le montrant est sur github.com/kohesive/injekt )
Jayson Minard

10
Merci pour la réponse détaillée. Très instructif. J'aime particulièrement l' implémentation des Délégués de Propriété (commune, la plus élégante) .
mchlstckl

6
Je pense qu'il y a eu un changement dans la syntaxe de kotlin. et le déballage devrait être ofClass.enclosingClass.kotlin.objectInstance?.javaClassau lieu deofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
ah, peu importe, comme indiqué ici kotlinlang.org/docs/reference/reflection.html le pot de réflexion est expédié séparément du stdlib, pour gradle nous avons besoin de ceci:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
Le code de création des «Délégués de propriété» et des «Fonctions d'extension» semble être le même, sauf pour le type de retour. L'exemple de code pour le délégué de propriété ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) semble créer une fonction d'extension telle que "".logger()c'est maintenant une chose, est-ce supposé se comporter de cette façon?
Mike Rylander

32

Jetez un œil à la bibliothèque de journalisation kotlin .
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 article de blog le comparant à AnkoLogger: Journalisation dans Kotlin et Android: AnkoLogger vs kotlin-logging

Clause de non-responsabilité: je suis le mainteneur de cette bibliothèque.

Edit: kotlin-logging a maintenant un support multiplateforme: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


Puis-je vous suggérer de modifier votre réponse pour afficher la sortie des logger.info()appels, comme Jayson l'a fait dans sa réponse acceptée.
Paulo Merson le

7

Comme bon exemple d'implémentation de journalisation, j'aimerais mentionner Anko qui utilise une interface spéciale AnkoLoggerqu'une classe qui a besoin de journalisation devrait implémenter. Dans l'interface, il y a du code qui 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 d'interface sans préfixes ni même la création d'instance de journalisation.

Je ne pense pas que ce soit idiomatique , mais cela semble une bonne approche car cela nécessite un minimum de code, il suffit d'ajouter l'interface à une déclaration de classe, et vous obtenez une journalisation avec différentes balises pour différentes classes.


Le code ci-dessous est essentiellement AnkoLogger , simplifié et réécrit pour une utilisation indépendante d'Android.

Tout d'abord, 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'extensions pour l' MyLoggerintérieur de leur code en les appelant simplement 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 des méthodes de journalisation. Il obtient une balise de l' MyLoggerimplémentation, vérifie les paramètres de journalisation, puis appelle l'un des deux gestionnaires, celui avec Throwableargument et celui 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 journalisation d'un Throwableégalement, cela se fait avec un throwableparamètre facultatif .

Les fonctions transmises en tant que handleret throwableHandlerpeuvent ê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. isLoggingEnabledet LoggingLevelssont omis par souci de concision, 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 logger sera nécessaire pour se connecter aux fonctions au niveau du package:

private object MyPackageLog : MyLogger

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

Cette réponse est spécifique à Android et la question ne mentionnait ni ne comportait de balise Android.
Jayson Minard

@JaysonMinard pourquoi est-ce? Cette approche est générale car, par exemple, avoir une balise de journalisation unique pour chaque classe est également utile dans les projets non Android.
raccourci clavier du

1
Il n'est pas clair que vous disiez "implémentez quelque chose de similaire à ce qu'Anko a fait" et ressemble plutôt à "utilisez Anko" ... qui nécessite alors une bibliothèque Android appelée Anko. Qui a une interface qui a des fonctions d'extension qui appellent android.util.Logà faire la journalisation. Quelle était votre intention? utiliser Anko? De construire quelque chose de similaire en utilisant Anko comme exemple (il est préférable de simplement mettre le code suggéré en ligne et de le corriger pour un non-Android au lieu de dire "portez ceci sur un non-Android, voici le lien". Au lieu de cela, vous ajoutez un exemple de code appelant Anko)
Jayson Minard

1
@JaysonMinard, merci pour vos commentaires, j'ai réécrit le post afin qu'il explique maintenant l'approche plutôt que de faire référence à Anko.
raccourci clavier

6

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 rester simple en définissant ceci comme une fonction de premier niveau quelque part dans votre projet:

import org.slf4j.LoggerFactory

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

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

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 super simple et proche de l'équivalent java, mais ajoute juste du sucre syntaxique.

Étape suivante: 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 "Delegate" 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). Puisque log4j2, contrairement à slf4j, prend en charge la journalisation avec Supplier's, 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
  }
}

API de journalisation Kotlin Log4j2

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

L'utilisation est essentiellement celle décrite ci-dessus, mais le module prend en charge à la fois l'accès au journal basé sur l'interface, une loggerfonction d'extension Anyà utiliser là où thisest défini et une fonction de journalisation nommée à utiliser lorsque aucun thisn'est défini (comme les fonctions de niveau supérieur).


1
Si j'ai raison, vous pouvez éviter de taper le nom de la classe dans la première solution que vous avez fournie en changeant la signature de la méthode en T.logger ()
IPat

1
@IPat yup, la première solution ne fait volontairement pas ça pour rester proche de la "java way". La deuxième partie de la réponse couvre le cas de l'extension T.logger()- voir le bas de l'exemple de code.
Raman

5

Anko

Vous pouvez utiliser la Ankobibliothèque 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")
    }
}

journalisation kotlin

La bibliothèque kotlin-logging ( projet Github - kotlin-logging ) vous permet d'écrire du code de journalisation comme ci-dessous:

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

StaticLog

ou vous pouvez également utiliser cette petite bibliothèque écrite dans Kotlin appelée StaticLogalors votre code ressemblerait à:

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 meilleure si vous souhaitez définir un format de sortie pour la méthode de journalisation comme:

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

Timberkt

Si vous avez déjà utilisé la Timbervérification de la bibliothèque de journalisation de Jake Wharton timberkt.

Cette bibliothèque s'appuie sur Timber avec une API plus facile à utiliser depuis Kotlin. Au lieu d'utiliser des paramètres de mise en forme, 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: Journalisation dans Kotlin et Android: AnkoLogger vs kotlin-logging

J'espère que cela aidera


4

Quelque chose comme ça fonctionnerait 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() }
}

1
Cette réponse nécessite plus d'explications, si la personne qui pose la question ne comprend pas les objets compagnons, elle n'a probablement pas communiqué avec les délégués et ne saura donc pas ce que cela fait. De plus, ce modèle permet de réaliser très peu d'économies de code. Et je doute que la mise en cache dans l'objet compagnon soit vraiment un gain de performances autre que dans un système restreint avec un petit processeur tel qu'Android.
Jayson Minard

1
Ce que ce code ci-dessus montre est la création d'une classe qui agit comme un délégué (voir kotlinlang.org/docs/reference/delegated-properties.html ) qui est la première classe LoggerDelegate Et puis il crée une fonction de haut niveau qui fait il est plus facile de créer une instance du délégué (pas beaucoup plus facile, mais un peu). Et cette fonction devrait être modifiée pour être inline. Ensuite, il utilise le délégué pour fournir un enregistreur chaque fois que vous le souhaitez. Mais il en fournit un pour le compagnon Foo.Companionet non pour la classe, Foodonc ce n'est peut-être pas comme prévu.
Jayson Minard

@JaysonMinard Je suis d'accord mais je laisserai la réponse aux futurs téléspectateurs qui veulent une "solution rapide" ou un exemple de la façon d'appliquer cela à leurs propres projets. Je ne comprends pas pourquoi la logger()fonction devrait être inlinesi aucun lambdas n'est présent. IntelliJ suggère que l'inlining dans ce cas est inutile: i.imgur.com/YQH3NB1.png
Jire

1
J'ai incorporé votre réponse dans la mienne et je l'ai simplifiée en supprimant la classe de délégué personnalisé et en utilisant un wrapper à la Lazyplace. Avec une astuce pour lui faire savoir dans quelle classe il appartient.
Jayson Minard

1

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

val logger = Logger.getLogger("package_name")

Cette pratique sert bien en Python, et aussi différents que Kotlin et Python puissent paraître, je crois qu'ils sont assez similaires dans leur «esprit» (en parlant d'idiomes).


Le niveau supérieur est également appelé niveau du package.
Caelum

Une variable de niveau supérieur est comme dire "utiliser des variables globales" et je pense qu'elle ne serait applicable que si vous aviez d'autres fonctions de niveau supérieur qui nécessitaient d'utiliser un enregistreur. À ce stade, cependant, il peut être préférable de passer un enregistreur à toute fonction utilitaire qui souhaite se connecter.
Jayson Minard

1
@JaysonMinard Je pense que passer logger en tant que paramètre serait un anti-modèle, car votre journalisation ne devrait jamais affecter votre API, externe ou interne
voddan

Ok, puis revenons à mon point, pour la journalisation au niveau de la classe, placez l'enregistreur dans la classe, pas dans une fonction de niveau supérieur.
Jayson Minard

1
@voddan fournit au moins un exemple complet du type d'enregistreur que vous créez. val log = what?!? ... créer un enregistreur par son nom? Ignorant le fait que la question montrait qu'il voulait créer un enregistreur pour une classe spécifiqueLoggerFactory.getLogger(Foo.class);
Jayson Minard

1

Qu'en est-il plutôt d'une fonction d'extension sur Class? De cette façon, vous vous retrouvez avec:

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

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

Remarque - Je n'ai pas du tout testé cela, donc ce n'est peut-être pas tout à fait correct.


1

Tout d'abord, vous pouvez ajouter des fonctions d'extension pour la création de l'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 qui fournit 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

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 cela pour vous connecter dans les fonctions de niveau supérieur?

Cette approche est générique et suffisamment simple pour bien fonctionner dans les deux classes, objets compagnons et 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

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


Un objet compagnon n'est pas un statique, c'est un singleton qui peut contenir des membres qui peuvent devenir statiques si vous utilisez une JvmStaticannotation. Et à l'avenir, il se peut qu'il y en ait plus d'un autorisé. De plus, cette réponse n'est pas très utile sans plus d'informations ou un échantillon.
Jayson Minard

Je n'ai pas dit que c'était statique. J'ai dit que c'était pour remplacer la statique. Et pourquoi y aurait-il plus d'un permis? Cela n'a pas de sens. Enfin, j'étais pressé et je pensais que pointer dans la bonne direction serait assez utile.
Jacob Zimmerman

1
Un objet compagnon n'est pas pour remplacer la statique, mais il peut également en rendre des éléments statiques. Kotlin a soutenu plus que sur compagnon pendant un certain temps, et leur permet d'avoir d'autres noms. Une fois que vous commencez à les nommer, ils agissent moins comme des statiques. Et il est laissé ouvert à l'avenir d'avoir plus d'un compagnon nommé. Par exemple, l'un pourrait être Factoryet l'autreHelpers
Jayson Minard

0

Exemple Slf4j, idem pour les autres. Cela fonctionne même pour créer un enregistreur de niveau de package

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

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

Usage:

val logger = getLogger { }

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

0

C'est toujours WIP (presque terminé) donc j'aimerais le partager: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

L'objectif principal de cette bibliothèque est d'appliquer un certain style de journal à travers un projet. En le faisant générer du code Kotlin, j'essaie de résoudre certains des problèmes mentionnés dans cette question. En ce qui concerne la question initiale, ce que j'ai généralement tendance à faire est simplement de:

private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {

}

0

Vous pouvez simplement créer votre propre "bibliothèque" d'utilitaires. Vous n'avez pas besoin d'une grande bibliothèque pour cette tâche qui rendra votre projet plus lourd et plus complexe.

Par exemple, vous pouvez utiliser Kotlin Reflection pour obtenir le nom, le type et la valeur de n'importe quelle propriété de classe.

Tout d'abord, assurez-vous que la méta-dépendance est installée dans votre build.gradle:

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

Ensuite, vous pouvez simplement copier et coller ce code dans votre projet:

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

Exemple d'utilisation:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.