NLog: reglas y filtros
En Confirmit, utilizamos la biblioteca NLog para iniciar sesión en nuestras aplicaciones .NET. Aunque existe documentación para esta biblioteca, fue difícil para mí entender cómo funciona todo. En este artículo, intentaré explicar cómo se aplican las reglas y los filtros en NLog. Empecemos
Cómo configurar NLog
Y comenzaremos con un pequeño recordatorio de lo que podemos hacer con la configuración NLog. En el caso más simple, esta configuración es un archivo XML (por ejemplo, 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>
Puede descargar este archivo con una línea de código:
LogManager.Configuration = new XmlLoggingConfiguration("NLog.config");
¿Qué podemos hacer con eso? Podemos configurar varios receptores de mensajes (objetivo) para la regla:
<rules> <logger name="*" minlevel="Warn" writeTo="target1,target2,target3" /> </rules>
Podemos determinar para qué niveles de registro se aplica esta regla:
<rules> <logger name="*" minlevel="Warn" writeTo="target1" /> <logger name="*" levels="Debug,Warn,Info" writeTo="target2" /> </rules>
Podemos establecer filtros para cada regla:
<rules> <logger name="*" minlevel="Info" writeTo="target1"> <filters defaultAction='Log'> <when condition="contains('${message}','Common')" action="Ignore" /> </filters> </logger> </rules>
Y finalmente, podemos definir reglas anidadas:
<rules> <logger name="*" minlevel="Info" writeTo="target1"> <logger name="*" minlevel="Warn" writeTo="target2" /> </logger> </rules>
Es hora de descubrir cómo funciona todo.
Crear una configuración de registrador
Cuando solicita una instancia de registrador,
var commonLogger = LogManager.GetLogger("Common");
NLog toma uno existente del caché o crea uno nuevo (ver aquí ). En el último caso, también se crea una configuración para el registrador con el nombre dado. Veamos el proceso de crearlo.
En resumen, la configuración del registrador es una cadena separada de receptores y los filtros correspondientes para cada nivel de registro ( Trace
, Debug
, Info
, Warn
, Error
, Fatal
) (ver aquí ). Ahora te mostraré cómo se construyen estas cadenas.
El método principal responsable de crear estas cadenas es GetTargetsByLevelForLogger de la clase LogFactory . Así es como funciona. Todas las reglas especificadas en la configuración NLog se seleccionan a su vez. Primero, verifica si el nombre de la regla coincide con el nombre del registrador. Los nombres de las reglas pueden contener comodines, como los que usamos para los objetos del sistema de archivos:
*
- una secuencia arbitraria de caracteres?
- cualquier personaje individual
Por lo tanto, el nombre de la regla ' *
' coincide con cualquier nombre de registrador, y ' Common*
' coincide con todos los registradores cuyos nombres comienzan con ' Common
'.
Si el nombre de la regla no coincide con el nombre del registrador, esta regla se descarta con todas las reglas incrustadas en ella. De lo contrario, el método GetTargetsByLevelForLogger
obtiene todos los niveles de registro para los que está habilitada esta regla. Para cada nivel, NLog agrega todos los receptores de mensajes especificados en la regla a las cadenas de receptores correspondientes junto con los filtros para esta regla.
Hay otra característica importante en la construcción de cadenas receptoras. Si la regla actual se marca como final
y su nombre coincide con el nombre del registrador, entonces NLog completa la construcción de cadenas para todos los niveles de registro incluidos para esta regla. Esto significa que ni las reglas anidadas ni las reglas posteriores agregan nada a estas cadenas receptoras. Su creación está completamente completada y no cambiarán. Se deduce que no tiene sentido escribir algo como esto:
<rules> <logger name="*" minlevel="Info" writeTo="target1" final="true"> <logger name="*" minlevel="Warn" writeTo="target2" /> </logger> </rules>
Ningún mensaje llegará a target2
. Pero es posible escribir algo como esto:
<rules> <logger name="*" minlevel="Warn" writeTo="target1" final="true"> <logger name="*" minlevel="Info" writeTo="target2" /> </logger> </rules>
Como la regla externa no está habilitada para el nivel de Info
, la cadena de receptores para este nivel no finalizará en la regla externa. Por lo tanto, todos los mensajes con el nivel de Info
caerán en target2
.
Después de que todos los receptores de esta regla se agregan a las cadenas correspondientes, el método procesa recursivamente todas las reglas anidadas de la regla actual de acuerdo con el mismo algoritmo. Esto sucede independientemente de los niveles de registro habilitados para la regla principal.
En total, la configuración para el registrador está lista. Contiene cadenas de receptores con filtros para cada posible nivel de registro:

Es hora de ver cómo se usa esta configuración.
Usar la configuración del registrador
Comencemos con cosas simples. La clase Logger
tiene un método IsEnabled
y propiedades IsXXXEnabled
asociadas ( IsDebugEnabled
, IsInfoEnabled
, ...). ¿Cómo funcionan ellos? De hecho, simplemente verifican si las cadenas de receptor para un nivel dado de registro contienen al menos un enlace (ver aquí ). Esto significa que los filtros nunca afectan los valores de estas propiedades.
A continuación, permítame explicarle lo que sucede cuando intenta asegurar un mensaje. Como habrás adivinado, el registrador toma una cadena de receptores para el nivel de registro de este mensaje. Luego comienza a procesar los eslabones de esta cadena uno tras otro. Para cada enlace, el registrador decide si escribir el mensaje al receptor especificado en el enlace, y si continúa procesando la cadena después de eso. Estas decisiones se toman con filtros. Déjame mostrarte cómo funcionan los filtros en NLog.
Así es como se configuran los filtros:
<rules> <logger name="*" minlevel="Info" writeTo="target1"> <filters defaultAction='Log'> <when condition="contains('${message}','Common')" action="Ignore" /> </filters> </logger> </rules>
Por lo general, un filtro contiene alguna condición booleana. Aquí puede decidir si el filtro devuelve true
o false
para cada mensaje. Pero esto no es así. El resultado de su trabajo es un valor de tipo FilterResult
. Si la condición del filtro devuelve true
, el resultado del filtro se convierte en el valor especificado en el atributo de action
(en nuestro ejemplo, esto es Ignore
). Si la condición devuelve false
, el resultado del filtro será Neutral
. Esto significa que el filtro no quiere decidir qué hacer con el mensaje.
Puede ver cómo se procesa la cadena del receptor aquí . Para cada receptor, se GetFilterResult
el resultado de los filtros correspondientes en el método GetFilterResult
. Es igual al resultado del primer filtro que regresó no Neutral
. Esto significa que si algún filtro devuelve un valor que no sea Neutral
, no se ejecutarán todos los filtros posteriores.
Pero, ¿qué sucede si todos los filtros devuelven Neutral
? En este caso, se usará el valor predeterminado. Este valor se establece utilizando el atributo defaultAction
del elemento de filters
para la regla. ¿Cuál crees que es el valor predeterminado para defaultAction
? Tienes razón si crees que esto es Neutral
. Es decir, como resultado, toda la cadena de filtros puede devolver Neutral
. En este caso, NLog se comporta igual que recibir Log
. El mensaje se escribirá en el receptor (ver aquí ).
Como habrás adivinado, si un filtro devuelve Ignore
o Ignore
IgnoreFinal
, el mensaje no se escribirá en el receptor. Si el resultado del filtro es Log
o LogFinal
, se LogFinal
el mensaje. Pero, ¿cuál es la diferencia entre Ignore
e IgnoreFinal
y entre Log
o LogFinal
? Es simple En el caso de IgnoreFinal
y LogFinal
NLog deja de procesar la cadena del receptor y no escribe nada en los receptores contenidos en los enlaces posteriores.
Conclusión
El análisis del código NLog me ayudó a comprender cómo funcionan las reglas y los filtros. Espero que este artículo te sea útil. Buena suerte