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?
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
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.
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é.
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")
}
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.
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
}
}
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).
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")
}
}
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"}
}
}
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")
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
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() }
}
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.
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).
créer un objet compagnon et marquer les champs appropriés avec l'annotation @JvmStatic
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.
}
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")
}
}
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 { }
C’est à cela que servent les objets compagnons, en général: remplacer des objets statiques.
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()
}
}