NLog: règles et filtres
Chez Confirmit, nous utilisons la bibliothèque NLog pour nous connecter à nos applications .NET. Bien qu'il existe de la documentation pour cette bibliothèque, il m'a été difficile de comprendre comment tout cela fonctionne. Dans cet article, je vais essayer d'expliquer comment les règles et les filtres sont appliqués dans NLog. Commençons.
Comment configurer NLog
Et nous commencerons par un petit rappel de ce que nous pouvons faire avec la configuration NLog. Dans le cas le plus simple, cette configuration est un fichier XML (par exemple, NLog.config):
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets> <target name="target1" xsi:type="ColoredConsole" layout="Access Log|${level:uppercase=true}|${logger}|${message}"> <highlight-row condition="true" foregroundColor="red"/> </target> <target name="target2" xsi:type="ColoredConsole" layout="Common Log|${level:uppercase=true}|${logger}|${message}"> <highlight-row condition="true" foregroundColor="green"/> </target> <target name="target3" xsi:type="ColoredConsole" layout="Yellow Log|${level:uppercase=true}|${logger}|${message}"> <highlight-row condition="true" foregroundColor="yellow"/> </target> </targets> <rules> <logger name="*" minlevel="Warn" writeTo="target1,target2,target3" /> </rules> </nlog>
Vous pouvez télécharger ce fichier avec une ligne de code:
LogManager.Configuration = new XmlLoggingConfiguration("NLog.config");
Que pouvons-nous en faire? Nous pouvons définir plusieurs destinataires de messages (cible) selon la règle:
<rules> <logger name="*" minlevel="Warn" writeTo="target1,target2,target3" /> </rules>
Nous pouvons déterminer pour quels niveaux de journalisation cette règle est appliquée:
<rules> <logger name="*" minlevel="Warn" writeTo="target1" /> <logger name="*" levels="Debug,Warn,Info" writeTo="target2" /> </rules>
Nous pouvons définir des filtres pour chaque règle:
<rules> <logger name="*" minlevel="Info" writeTo="target1"> <filters defaultAction='Log'> <when condition="contains('${message}','Common')" action="Ignore" /> </filters> </logger> </rules>
Et enfin, nous pouvons définir des règles imbriquées:
<rules> <logger name="*" minlevel="Info" writeTo="target1"> <logger name="*" minlevel="Warn" writeTo="target2" /> </logger> </rules>
Il est temps de découvrir comment tout cela fonctionne.
Création d'une configuration d'enregistreur
Lorsque vous demandez une instance d'enregistreur,
var commonLogger = LogManager.GetLogger("Common");
NLog en prend un existant dans le cache ou en crée un nouveau (voir ici ). Dans ce dernier cas, une configuration est également créée pour l'enregistreur avec le nom donné. Regardons le processus de création.
En bref, la configuration de l'enregistreur est une chaîne distincte de récepteurs et de filtres correspondants pour chaque niveau d'enregistrement ( Trace
, Debug
, Info
, Warn
, Error
, Fatal
) (voir ici ). Je vais maintenant vous montrer comment ces chaînes sont construites.
La principale méthode responsable de la création de ces chaînes est GetTargetsByLevelForLogger de la classe LogFactory . Voilà comment ça marche. Toutes les règles spécifiées dans la configuration NLog sont sélectionnées tour à tour. Tout d'abord, il vérifie si le nom de la règle correspond au nom de l'enregistreur. Les noms de règles peuvent contenir des caractères génériques, tels que ceux que nous utilisons pour les objets du système de fichiers:
*
- une séquence arbitraire de caractères?
- n'importe quel caractère
Ainsi, le nom de règle « *
» correspond à n'importe quel nom d'enregistreur et « Common*
» correspond à tous les enregistreurs dont les noms commencent par « Common
».
Si le nom de la règle ne correspond pas au nom de l'enregistreur, cette règle est supprimée avec toutes les règles intégrées. Sinon, la méthode GetTargetsByLevelForLogger
obtient tous les niveaux de journalisation pour lesquels cette règle est activée. Pour chacun de ces niveaux, NLog ajoute tous les récepteurs de messages spécifiés dans la règle aux chaînes de récepteurs correspondantes ainsi que des filtres pour cette règle.
Il existe une autre caractéristique importante dans la construction de chaînes réceptrices. Si la règle actuelle est marquée comme final
et que son nom correspond au nom de l'enregistreur, alors NLog termine la construction des chaînes pour tous les niveaux de journalisation inclus pour cette règle. Cela signifie que ni les règles imbriquées ni les règles ultérieures n'ajoutent quoi que ce soit à ces chaînes réceptrices. Leur création est entièrement terminée et ils ne changeront pas. Il s'ensuit que cela n'a pas de sens d'écrire quelque chose comme ceci:
<rules> <logger name="*" minlevel="Info" writeTo="target1" final="true"> <logger name="*" minlevel="Warn" writeTo="target2" /> </logger> </rules>
Aucun message n'atteindra target2
. Mais il est possible d'écrire quelque chose comme ceci:
<rules> <logger name="*" minlevel="Warn" writeTo="target1" final="true"> <logger name="*" minlevel="Info" writeTo="target2" /> </logger> </rules>
Étant donné que la règle externe n'est pas activée pour le niveau Info
, la chaîne de récepteurs pour ce niveau ne se terminera pas sur la règle externe. Par conséquent, tous les messages avec le niveau Info
tomberont dans target2
.
Une fois tous les récepteurs de cette règle ajoutés aux chaînes correspondantes, la méthode traite récursivement toutes les règles imbriquées de la règle actuelle selon le même algorithme. Cela se produit quels que soient les niveaux de journalisation activés pour la règle parent.
Au total, la configuration de l'enregistreur est prête. Il contient des chaînes de récepteurs avec des filtres pour chaque niveau d'enregistrement possible:

Il est temps de voir comment cette configuration est utilisée.
Utilisation de la configuration de l'enregistreur
Commençons par des choses simples. La classe Logger
possède une méthode IsEnabled
et les propriétés IsXXXEnabled
associées ( IsDebugEnabled
, IsInfoEnabled
, ...). Comment fonctionnent-ils? En fait, ils vérifient simplement si les chaînes réceptrices pour un niveau de journalisation donné contiennent au moins un lien (voir ici ). Cela signifie que les filtres n'affectent jamais les valeurs de ces propriétés.
Ensuite, laissez-moi vous expliquer ce qui se passe lorsque vous essayez de sécuriser un message. Comme vous l'avez peut-être deviné, l'enregistreur prend une chaîne de récepteurs pour le niveau d'enregistrement de ce message. Puis il commence à traiter les maillons de cette chaîne l'un après l'autre. Pour chaque lien, l'enregistreur décide s'il faut écrire le message au destinataire spécifié dans le lien et s'il doit continuer à traiter la chaîne par la suite. Ces décisions sont prises à l'aide de filtres. Permettez-moi de vous montrer comment fonctionnent les filtres dans NLog.
Voici comment les filtres sont configurés:
<rules> <logger name="*" minlevel="Info" writeTo="target1"> <filters defaultAction='Log'> <when condition="contains('${message}','Common')" action="Ignore" /> </filters> </logger> </rules>
Habituellement, un filtre contient une condition booléenne. Ici, vous pouvez décider si le filtre renvoie true
ou false
pour chaque message. Mais ce n'est pas le cas. Le résultat de leur travail est une valeur de type FilterResult
. Si la condition de filtre renvoie true
, le résultat du filtre devient la valeur spécifiée dans l'attribut action
(dans notre exemple, il s'agit de Ignore
). Si la condition retourne false
, le résultat du filtre sera Neutral
. Cela signifie que le filtre ne veut pas décider quoi faire avec le message.
Vous pouvez voir comment la chaîne de réception est traitée ici . Pour chaque récepteur, le résultat des filtres correspondants dans la méthode GetFilterResult
est GetFilterResult
. Il est égal au résultat du premier filtre qui est retourné non Neutral
. Cela signifie que si certains filtres renvoient une valeur autre que Neutral
, tous les filtres suivants ne sont pas exécutés.
Mais que se passe-t-il si tous les filtres renvoient Neutral
? Dans ce cas, la valeur par défaut sera utilisée. Cette valeur est définie à l'aide de l'attribut defaultAction
de l'élément filters
pour la règle. defaultAction
vous, quelle est la valeur par défaut de defaultAction
? Vous avez raison si vous pensez que c'est Neutral
. C'est-à-dire que la chaîne de filtres entière peut retourner Neutral
en conséquence. Dans ce cas, NLog se comporte de la même manière que la réception du Log
. Le message sera écrit au destinataire (voir ici ).
Comme vous l'avez peut-être deviné, si un filtre renvoie Ignore
ou IgnoreFinal
, le message ne sera pas écrit sur le récepteur. Si le résultat du filtre est Log
ou LogFinal
, le message sera enregistré. Mais quelle est la différence entre Ignore
et IgnoreFinal
et entre Log
ou LogFinal
? C'est simple. Dans le cas d' IgnoreFinal
et LogFinal
NLog arrête de traiter la chaîne de réception et n'écrit rien sur les récepteurs contenus dans les liens suivants.
Conclusion
L'analyse du code NLog m'a aidé à comprendre le fonctionnement des règles et des filtres. J'espère que cet article vous sera utile. Bonne chance