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?
kotlin
kotlin-logging
mchlstckl
la source
la source
Any
(nécessitant donc un cast)?this.javaClass
pour chacun. Mais je ne le recommande pas comme solution.Réponses:
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.Logging
mais la même théorie s'applique à toute bibliothèque de journalisationStatique (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.
création de sortie:
Plus d'informations sur les objets compagnons ici: Objets compagnons ... Notez également que dans l'exemple ci-dessus
MyClass::class.java
obtient l'instance de typeClass<MyClass>
pour le logger, alors quethis.javaClass
cela obtiendrait l'instance de typeClass<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.
création de sortie:
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
Lazy
dé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: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:
pour chaque instance de classe, ou si vous voulez qu'elle soit plus statique avec une instance par classe:
Et votre résultat de l'appel
foo()
à ces deux classes serait: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
Any
avec 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 à étendreAny
ou à 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:Maintenant, dans une classe (ou un objet compagnon), je peux simplement appeler cette extension sur ma propre classe:
Production de sortie:
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: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:
Ou même intégrez la méthode à l'interface avec une implémentation par défaut:
Et utilisez l'une de ces variantes dans votre classe:
Production de sortie:
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
:Maintenant, l'implémenteur de l'interface doit ressembler à ceci:
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é:
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:
Choisissez celle que vous souhaitez conserver et voici toutes les options utilisées:
Les 13 instances des enregistreurs créés dans cet exemple produiront le même nom d'enregistreur et produiront:
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 deremoveSuffix()
ne fonctionne pas car les objets compagnons peuvent recevoir des noms personnalisés.la source
ofClass.enclosingClass.kotlin.objectInstance?.javaClass
au lieu deofClass.enclosingClass.kotlin.companionObject?.java
compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
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?Jetez un œil à la bibliothèque de journalisation kotlin .
Cela permet de se connecter comme ça:
Ou comme ça:
J'ai également écrit un article de blog le comparant à
AnkoLogger
: Journalisation dans Kotlin et Android: AnkoLogger vs kotlin-loggingClause 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
la source
logger.info()
appels, comme Jayson l'a fait dans sa réponse acceptée.Comme bon exemple d'implémentation de journalisation, j'aimerais mentionner Anko qui utilise une interface spéciale
AnkoLogger
qu'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:
Il permet à son implémentation d'utiliser les fonctions d'extensions pour l'
MyLogger
intérieur de leur code en les appelant simplementthis
. 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:
Il sera appelé par des méthodes de journalisation. Il obtient une balise de l'
MyLogger
implémentation, vérifie les paramètres de journalisation, puis appelle l'un des deux gestionnaires, celui avecThrowable
argument et celui sans.Ensuite, vous pouvez définir autant de méthodes de journalisation que vous le souhaitez, de cette manière:
Celles-ci sont définies une fois pour la journalisation d'un message et la journalisation d'un
Throwable
également, cela se fait avec unthrowable
paramètre facultatif .Les fonctions transmises en tant que
handler
etthrowableHandler
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
etLoggingLevels
sont omis par souci de concision, mais leur utilisation offre encore plus de flexibilité.Il permet l'utilisation suivante:
Il y a un petit inconvénient: un objet logger sera nécessaire pour se connecter aux fonctions au niveau du package:
la source
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)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:
Cela utilise un paramètre de type réifié Kotlin .
Maintenant, vous pouvez utiliser ceci comme suit:
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.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
logger
fonction d'extensionAny
à utiliser là oùthis
est défini et une fonction de journalisation nommée à utiliser lorsque aucunthis
n'est défini (comme les fonctions de niveau supérieur).la source
T.logger()
- voir le bas de l'exemple de code.Anko
Vous pouvez utiliser la
Anko
bibliothèque pour le faire. Vous auriez un code comme ci-dessous:journalisation kotlin
La bibliothèque kotlin-logging ( projet Github - kotlin-logging ) vous permet d'écrire du code de journalisation comme ci-dessous:
StaticLog
ou vous pouvez également utiliser cette petite bibliothèque écrite dans Kotlin appelée
StaticLog
alors votre code ressemblerait à:La deuxième solution pourrait être meilleure si vous souhaitez définir un format de sortie pour la méthode de journalisation comme:
ou utilisez des filtres, par exemple:
Timberkt
Si vous avez déjà utilisé la
Timber
vérification de la bibliothèque de journalisation de Jake Whartontimberkt
.Exemple de code:
Vérifiez également: Journalisation dans Kotlin et Android: AnkoLogger vs kotlin-logging
J'espère que cela aidera
la source
Quelque chose comme ça fonctionnerait pour vous?
la source
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 êtreinline
. Ensuite, il utilise le délégué pour fournir un enregistreur chaque fois que vous le souhaitez. Mais il en fournit un pour le compagnonFoo.Companion
et non pour la classe,Foo
donc ce n'est peut-être pas comme prévu.logger()
fonction devrait êtreinline
si aucun lambdas n'est présent. IntelliJ suggère que l'inlining dans ce cas est inutile: i.imgur.com/YQH3NB1.pngLazy
place. Avec une astuce pour lui faire savoir dans quelle classe il appartient.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
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).
la source
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);
Qu'en est-il plutôt d'une fonction d'extension sur Class? De cette façon, vous vous retrouvez avec:
Remarque - Je n'ai pas du tout testé cela, donc ce n'est peut-être pas tout à fait correct.
la source
Tout d'abord, vous pouvez ajouter des fonctions d'extension pour la création de l'enregistreur.
Ensuite, vous pourrez créer un enregistreur en utilisant le code suivant.
Deuxièmement, vous pouvez définir une interface qui fournit un enregistreur et son implémentation mixin.
Cette interface peut être utilisée de la manière suivante.
la source
créer un objet compagnon et marquer les champs appropriés avec l'annotation @JvmStatic
la source
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:
la source
C'est à cela que servent les objets compagnons, en général: remplacer des objets statiques.
la source
JvmStatic
annotation. 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.Factory
et l'autreHelpers
Exemple Slf4j, idem pour les autres. Cela fonctionne même pour créer un enregistreur de niveau de package
Usage:
la source
la source
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:
la source
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:
Ensuite, vous pouvez simplement copier et coller ce code dans votre projet:
Exemple d'utilisation:
la source