Logging with PostSharp and NLog
For my first article I will cover the inanely boring aspect of logging and show
you how to build an easily configurable and somewhat invisible logging component
that is virtually set and forget for any application. By the end off this article
you will have Trace level logging enabled for every method in you project, and only
one existing class needs to be modified. Sounds good? Yeah it is.
This article will be using Microsoft’s .Net framework with its C# language. In addition,
we will be using the third party logging tool NLog and the AOP/IL Weaver libraries
from Post Sharp.
First and foremost we will deal with logging. Logging is an important part of software
development but often neglected as it is a non functional deliverable and often
only really used in later stages of the application lifecycle when bugs are to be
investigated. Personally I prefer to set up logging early on in the project, if
possible in the first iteration with other non functional deliverables. Having a
logging component ready to go can make this process a lot easier, hence the reason
for writing this article. One of the major problems with logging is that is creeps
into all aspects and layers of the applications code. This phenomenon makes it a
“cross cutting concern” and it means it is not so easily separated from the application,
it instead roots itself into the application, making it very hard to make wholesale
changes to the way you interact with your logging framework.
For this reason I will be using an AOP-like framework called PostSharp. PostSharp
give you the ability to weave coding logic into the application at build time, meaning
cleaner code that deals with the business problem at hand and is not littered with
logging statements.
So let’s get started. Firstly we will download the NLog assemblies to be used in
the application. At the time of writing this article the latest version is 1.0 and
can be downloaded via the NLog site.
Next download the Postsharp assemblies. At the time of writing this article the
latest version is RC 1.0 and can be downloaded via the PostSharp site.
Create a new class library project in Visual Studio (or whatever IDE you are using).
I have called mine ArtemisWest.Logging. Include references to NLog, Postshapr.Laos
& PostSharp.Public. These should have been GAC’ed on installing them. If not, locate
the folder where these DLL’s where downloaded to.
Add a C# file to the project and call the class TraceLoggingAttribute. As you may
guess we will be write an attribute class that will be used to decorate other assemblies
(or classes or methods). The code for the class is below:
using System;
using NLog;
using PostSharp.Laos;
namespace ArtemisWest.Logging
{
/// <summary>
/// AOP based logging attribute to enable trace logging on entry and exit of any
sub component of the
/// component marked with the attribute.
/// For example marking an assembly with the this attribute will allow trace debugging
on all methods
/// of all classes with in that assembly.
/// </summary>
[Serializable]
public sealed class TraceLoggingAttribute
:
OnMethodBoundaryAspect
{
#region Private Variables
/// <summary>
/// The NLog static logging componet that will beused to write the logging messages
/// </summary>
private static Logger logger
= LogManager.GetCurrentClassLogger();
/// <summary>
/// The name of the assembly we are logging on
/// </summary>
private readonly string assembly;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="TraceLoggingAttribute"/>
class.
/// </summary>
/// <param name="assembly">The assembly.</param>
public TraceLoggingAttribute(
string
assembly)
{
this.assembly = assembly;
}
#endregion
#region Read Only Properties
/// <summary>
/// Gets the assembly.
/// </summary>
/// <value>The assembly.</value>
public string Assembly { get {
return
assembly; } }
#endregion
#region Overriden OnMethodBoundaryAspect Events
/// <summary>
/// The code to be woven into the start of the method. This will be run before any
of the other visible code in the method
/// This will specifically tell us when the method has begun along with
/// Assembly, Class and Method name,
/// Method Parameters
/// and the Reference to this specific instance of the method.
/// </summary>
/// <param name="eventArgs"></param>
/// <inheritdoc/>
public override void OnEntry(
MethodExecutionEventArgs
eventArgs)
{
Guid reference =
Guid.NewGuid();
logger.Trace(
string.Format("\r\nEntering Assembly: {0}\r\n\t{1}.{2}.
\r\n\tMethod reference: {3}",
this.Assembly,
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name,
reference.ToString()),
eventArgs.GetArguments());
eventArgs.MethodExecutionTag = reference;
}
/// <summary>
/// The code to be woven into the end of the method. This will be run before exiting
the method.
/// This will specifically tell us when the method has completed along with
/// Assembly, Class and Method name,
/// Method Parameters,
/// Return Value
/// and the Reference to this specific instance of the method.
/// </summary>
/// <param name="eventArgs"></param>
/// <inheritdoc/>
public override void OnExit(
MethodExecutionEventArgs
eventArgs)
{
string returnValue = (eventArgs.ReturnValue != null ?
eventArgs.ReturnValue.ToString() :
"null/nothing");
logger.Trace(
string.Format("\r\nExiting Assembly: {0}\r\n\t{2}.{3}.
\r\n\tReturning value {1}\r\n\tMethod reference: {4}",
this.Assembly,
returnValue,
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name,
eventArgs.MethodExecutionTag.ToString()),
eventArgs.GetArguments());
}
/// <summary>
/// Ensure any unhandled exceptions are logged also.
/// </summary>
/// <param name="eventArgs"></param>
/// <inheritdoc/>
public override void OnException(
MethodExecutionEventArgs
eventArgs)
{
base.OnException(eventArgs);
logger.ErrorException(
string.Format("Assembly: {0}\r\nException
at {1}.{2}. Method reference: {3}",
this.Assembly,
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name,
eventArgs.MethodExecutionTag.ToString()),
eventArgs.Exception);
}
#endregion }
}
We now have a vanilla trace logging attribute that can be applied to an assembly
to log the entry, exit and exceptions for any method with in that assembly.
Key aspects of this class are:
- The using statements. We need to reference PostSharp and NLog so we can implement
their functionality.
- The Serializable Attribute on the class. This is required by PostSharp.
- The inheritance of OnMethodBoundaryAspect. This is so we can override the OnEntry
and OnExit events so we can weave the logging into the methods.
- The assignment of the logger from the NLog LogManger
- The overridden events. We have chosen to put logging into these events, however
other cross cutting concerns like caching or security could also be added is a similar
fashion.
Now we need to add this attribute to an assembly. Let’s assume we are writing an
ASP.Net application. In my mind the best place to put the attribute in this instance
would be in the AssemblyInfo.cs class as this will be the easiest (and most intuitive)
place to find the attribute later on.
using ArtemisWest.Logging;
[assembly: TraceLogging("ArtemisWest.Web")]
Before this will work we need to add to this project a reference to ArtemisWest.Logging,
Postshapr.Laos & PostSharp.Public.
Logging is now enabled for the whole assembly.
Unfortunately the logging framework has no idea what to do when we ask it to log
for us. We need to add some config setting to application. As we are writing an
ASP.Net application we will need to modify the web.config. In the configuration
we need to add a section reference:
<configuration>
<configSections>
<section name="nhibernateSettings"
type="C4.Programme.Library.CommonOpenSessionInViewSection, C4.Programme.Library.Common" />
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
<!—Config continues below… -->
Then we need to define what the logger is to do:
<nlog xmlns=" http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwExceptions="true" internalLogFile=" Nlog.txt">
<targets>
<target name="file" xsi:type="File"
layout="${longdate} ${level} ${message} ${exception:format=tostring}"
fileName="${basedir}/${shortdate}.txt"/>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="file" />
</rules>
</nlog>
</configuration>
This specific configuration will log to a file with the current date as the file
name, in the base directory of the application and will log anything above the trace
level (ie everything). This will also log and internal NLog exceptions to the Nlog.txt
file (normally NLog exceptions are ignored).
What have we achieved?
- We have a reusable logging attribute that can be used on any.net assembly to enable
assembly wide trace logging.
- We have kept our original code that contains our business logic clean and free of
unwanted logging calls. We have only modified the web.config file and ONE class
file!
- We have a fully configurable logging framework that can log what we want, to where
we want it and how we what logged!
Drawbacks
- Build time is slower. As the IL weave happens at build time, the build time will
be increased. This may be a further incentive to keep your projects to a sane and
manageable size ;)
- The Logging appears to be “magic”. As you can not see the code that is doing to
logging (due to its AOP like nature), it should be clearly communicated to the dev
team what is happening behind the scenes
These code snippets should give you a relative cut and paste logging framework to
get you started. The code can be replicated to provide, for instance Information
debugging statements, by following the same guideline and creating a InformationLoggingAttribute
with the logger calling the Info method instead.
For more information about these two very handy open source projects check out the
web sites.
http://www.nlog-project.org/
http://www.postsharp.org/
NOTE:
Personally I prefer a more pluggable & loosely coupled approach, however for ease
of understanding and writing of this article, the NLog components are directly referenced.
This could be further abstracted out by using IoC to inject the Logging facade in
to the Logging attribute class and again anywhere in the application using something
like the Windsor Container or Spring.net