October 11, 2011

One brick to the wall - common logging in .Net application


Being a .Net developer I often use an opensource log4net library in my projects. It needles to list all features  here, there is so many articles and posts about it, let me omit comparisons and move further.
That was my preferred logging library for many years though in last project I used NLog. That's my new favorite. Lightweight, easy to use, async- and bufferable on your wish (and the project is alive).
But what if I would want to change my preferences in logging and need to change the logging library afterwards? I need more flexible solution than just linking
NLog in my projects. And the solution is in Common.Logging namespace. Do you know? Right, the universal configurable adapter for logging frameworks. It let me use the logger-of-my-choice and not to be bound to specific library's classes.
Ok, then just add a reference on the Common.Logging.dll to the project either with a regular Add reference dialog or via NuGet (yes, it has a nu-package). My backend logger is NLog, so I need to add a NLog adapter as well, Common.Logging.NLog.
Then I need to add the following fragments to my app.config, according to this:
<sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
 </sectionGroup>
...and...
<common>
   <logging>
     <factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog">
        <arg key="configType" value="FILE"/>
        <arg key="configFile" value="~/NLog.config"/>
     </factoryAdapter>
   </logging>
</common>
Now, I can configure Nlog through NLog.config as if I used it as standalone logger, with no adapters and excuses. Everything works like a charm.
But a trouble was on the way, my webapp has got a clone, and those two instances ought to log to one database table. What a mess, I need something to distinguish records of different apps. Something like ApplicationName or AppDomainName written in a standalone column. Reading NLog's documentation I found that best way to do it is using ${event-context} 'layout renderer'.
So if Nlog was a standalone logger all I need to do is to add a keyed value to the Properties dictionary in the LogEventInfo (the object that describes a log entry and declared in NLog.dll).
But unfortunately Common.Logging adapter does not provide any members to set the underlying logger's properties. Ok, let's implement custom NLog adapter, overloading the WriteInternal() method and adding 'Properties' property to the 'adapter' logger. Then we could use it the same way as in NLog, just by adding keyed values and referencing them in the config.
Full source you can find in the attachment.
Also, we need to overload one more thing from Commons library, the NLogLoggerFactoryAdapter. It's responsible for creation logger instances from the configuration. It is as simple as that:
public class NLogLoggerFactoryAdapterEx : NLogLoggerFactoryAdapter
{
    public NLogLoggerFactoryAdapterEx(NameValueCollection properties) 
       : base(properties)
    {
    }

    protected override ILog CreateLogger(string name)
    {
         return new My.Logging.NLogger(NLog.LogManager.GetLogger(name));
    }
}
And no line more. Now then we can use our extended logger slightly modifying both our configuration and code:
<common>
   <logging>
      <factoryAdapter type="My.Logging.NLogLoggerFactoryAdapterEx, My.Logging">
        <arg key="configType" value="FILE"/>
        <arg key="configFile" value="~/NLog.config"/>
      </factoryAdapter>
   </logging>
</common>
  ...and usage...
var logger = (My.Logging.NLogger)LogManager.GetCurrentClassLogger();
logger.Properties["appname"] = Properties.Settings.Default.ApplicationName; // any string 
  ... and in NLog.config...
<parameter name="@appName" layout="${event-context:item=appname}"/>
NB, if you use other Common.Logging adapter for your preferrable logging library you might want to implement Properties of type IDictionary<object,object> in your class.

Full source: NLogLoggerFactoryAdapterEx.cs

No comments:

Post a Comment