NLog: rules and filters

Original author: Ivan Iakimov
  • Transfer

NLog: rules and filters


At Confirmit, we use the NLog library for logging in our .NET applications. Although documentation exists for this library, it was difficult for me to understand how it all works. In this article, I will try to explain how rules and filters are applied in NLog. Let's start.


How to configure NLog


And we'll start with a little reminder of what we can do with the NLog configuration. In the simplest case, this configuration is an XML file (for example, NLog.config):



You can download this file with one line of code:


LogManager.Configuration = new XmlLoggingConfiguration("NLog.config");

What can we do with it? We can set several message receivers (target) to the rule:



We can determine for what levels of logging this rule is applied:



We can set filters for each rule:



And finally, we can define nested rules:



It's time to find out how it all works.


Creating a logger configuration


When you request a logger instance,


var commonLogger = LogManager.GetLogger("Common");

NLog either takes an existing one from the cache or creates a new one (see here ). In the latter case, a configuration is also created for the logger with the given name. Let's look at the process of creating it.


Briefly, the logger configuration is a separate chain of receivers and appropriate filters for each login level ( Trace, Debug, Info, Warn, Error, Fatal) (see. Herein ). Now I will show you how these chains are built.


The main method responsible for creating these chains is the GetTargetsByLevelForLogger class LogFactory. This is how it works. All rules specified in the NLog configuration are selected in turn. First, it checks to see if the rule name matches the logger name. Rule names can contain wildcards, such as those we use for file system objects:


  • * - an arbitrary sequence of characters
  • ? - any single character

Thus, the rule name ' *' matches any logger name, and ' Common*' matches all loggers whose names begin with ' Common'.


If the rule name does not match the logger name, then this rule is discarded with all the rules embedded in it. Otherwise, the method GetTargetsByLevelForLoggerreceives all logging levels for which this rule is enabled. For each such level, NLog adds all message receivers specified in the rule to the corresponding receiver chains along with filters for this rule.


There is another important feature in constructing receiver chains. If the current rule is marked as finalits name matches the name of the logger, then NLog completes the construction of chains for all logging levels included for this rule. This means that neither the nested rules nor subsequent rules add anything to these receiver chains. Their creation is fully completed and they will not change. It follows that it does not make sense to write something like this:



No messages will fall into target2. But it is possible to write something like this:



Since an external rule is not included for a level Info, the chain of receivers for this level will not end on the external rule. Therefore, all messages with a level Infowill fall into target2.


After all the receivers from this rule are added to the corresponding chains, the method recursively processes all the nested rules of the current rule according to the same algorithm. This happens regardless of the logging levels enabled for the parent rule.


In total, the configuration for the logger is ready. It contains chains of receivers with filters for each possible level of logging:


Receiver Chain


It's time to see how this configuration is used.


Using logger configuration


Let's start with simple things. The class Loggerhas a method IsEnabledand related properties IsXXXEnabled( IsDebugEnabled, IsInfoEnabled, ...). How do they work? In fact, they simply check whether the receiver chains for a given level of logging contain at least one link (see here ). This means that filters never affect the values ​​of these properties.


Next, let me explain what happens when you try to secure a message. As you might have guessed, the logger takes a chain of receivers for the logging level of this message. Then he begins to process the links of this chain one after another. For each link, the logger decides whether to write the message to the receiver specified in the link, and whether to continue processing the chain after that. These decisions are made using filters. Let me show you how filters work in NLog.


Here's how the filters are configured:



Usually a filter contains some Boolean condition. Here you can decide what filter returns trueor falsefor each message. But this is not so. The result of their work is the type value FilterResult. If the filter condition returns true, then the result of the filter becomes the value specified in the attribute action(in our example, this Ignore). If the condition returns false, then the result of the filter will be Neutral. This means that the filter does not want to decide what to do with the message.


You can see how the receiver chain is processed here . For each receiver, the result of the corresponding filters in the method is calculated GetFilterResult. It is equal to the result of the first filter that returned no Neutral. This means that if some filter returns a value other than Neutral, all subsequent filters are not executed.


But what happens if all the filters are returned Neutral? In this case, the default value will be used. This value is set using the attribute of defaultActionthe element filtersfor the rule. What do you think is the default value for defaultAction? You are right if you think this is Neutral. That is, the entire filter chain can return Neutralas a result. In this case, NLog behaves the same as receiving Log. The message will be written to the receiver (see here ).


As you might have guessed, if the filter returns Ignoreor IgnoreFinal, the message will not be written to the receiver. If the result of the filter is - Logor LogFinal, the message will be recorded. But what is the difference between Ignoreand IgnoreFinaland between Logor LogFinal? It's simple. If IgnoreFinaland LogFinalNLog stops processing chain of the receiver and does not write anything in the receivers contained in the following links.


Conclusion


Analyzing NLog code helped me understand how rules and filters work. I hope this article will be useful to you. Good luck


Also popular now: