Complex applications need more than simple Console.WriteLine() statements sprinkled about the code to record application state for debugging and diagnostics purposes. That’s a job for logging packages. In this post, I’ll explain how to integrate logging into a complex Visual Studio solution based on a multi-layered application architecture. I’ll show you how to use the Visual Studio package manager to download and install the NLog package and how to configure NLog to write logs to an asynchronous log file.

The reference architecture I’ll be using is shown in figure 1.

Figure 1 – Multi-Layered Application Architecture w/Logging

Referring to figure 1 – I will install the NLog logging package in the Infrastructure layer and make logging services available via two classes: ApplicationBase, which can be extended as required, and StaticLogger, which can be used on an ad hoc basis anywhere within the application.

Before I get started, here’s a video that explains everything:

Install NLog NuGet Package in the InfrastructureLayer Project

Launch Visual Studio and open the EmployeeTraining solution you created in Part I of this series. Select the InfrastructureLayer project and from the main menu select Tools -> NuGet Package Manager -> Package Manager Console. This will open the Package Manager Console as is shown in figure 2.

Figure 2 – Package Manager Console

At the Default project dropdown select the InfrastructureLayer project. At the prompt type Find-Package to list packages available via your configured package source. Figure 3 shows the packages available on my system.

Figure 3 – Find-Package Command Showing Available NuGet Packages

The package you want to install is NLog. Be sure you’ve selected the InfrastructureLayer project and at the prompt type Install-Package NLog. If NLog installs successfully your Package Manager Console will look something like figure 4.

Figure 4 – Install-Package NLog Results

You may close the Package Manager Console. Inspect the InfrastructureLayer project to ensure a packages.config file has been added and that the NLog library reference appears in the project References as is shown in figure 5.

Figure 5 -InfrastructureLayer Project after Installing NLog Package

Install NLog.config File Template

It’s a royal pain in the butt to hand-jam an XML configuration file, so I recommend you install the NLog.config file template. From the main menu select Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution… Click the Browse tab and in the Search text box type NLog. Your Visual Studio window will look something like figure 6.

Figure 6 – NLog.config File Template Package is Second from the Top

Select NLog.config from the list of packages and to the right check the box next to the InfrastructureLayer project as is shown in figure 7.

Figure 7 – Installing NLog.config File Template

Click the Install button to install the package then click OK on the confirmation pop-up window. This will add two files to the project: NLog.config and NLog.xsd. Inspect the InfrastructureLayer project to ensure those two files are there. Now it’s time to configure NLog.

Configure NLog

Before NLog can write logs anywhere you’ll need to add at least one Target and one Rule to the NLog.config file. I will also show you how to use a Variable.  In the <targets> section add the following code:

<target xsi:type="AsyncWrapper" name="asyncFile">
    <target xsi:type="File" name="logfile" fileName="${var:logDirectory}"/>
 </target>

This target specifies a file target named “logfile”. The filename is specified by a variable named “logDirectory” which is defined in the variables section like so:

<variable name="logDirectory" value="${basedir}/logs/${shortdate}"/>

Note that the logDirectory variable itself uses two build-in NLog variables “basedir” and “shortdate”. To access a variable’s value use ${ }.

Note that there are actually two targets defined. One is the logfile and the other is an AsyncWrapper named “asyncFile”. For more information on the purpose of the AsyncWrapper target consult the NLog wiki. Now we need a rule.

In the <rules> section add the following code:

<logger name="*" minlevel="Trace" writeTo="asyncFile" />

This rule will cause all loggers to write Trace level messages and above to the asyncFile target defined in the <targets> section. When you’ve completed your edits, save and close the NLog.config file.

 

 

Implement Logging

It’s time now to actually implement logging. I like to make logging available throughout an application in two ways:

  1. Create a base class that makes logging available to derived classes
  2. Create a static class that can be used on an ad hoc basis in cases where the base class cannot be extended or where doing so would be an awkward construct.

I will name the base class “ApplicationBase” and the static class “StaticLogger”. I will locate both classes in the InfrastructureLayer project in a folder named “Common”.  Let’s start with ApplicationBase. here’s the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;

namespace InfrastructureLayer.Common
{
   public class ApplicationBase
    {
      protected Logger Log { get; private set; }

      protected ApplicationBase(Type declaringType)
        {
            Log = LogManager.GetLogger(declaringType.FullName);
        }
    }
}

Note the highlighted code. The namespace is InfrastructureLayer.Common because this class resided in the Common folder within the InfrastructureLayer project. I’ve defined a protected property named Log which is of type Logger. The constructor takes a Type argument, which is used to name the logger.

Let’s now define the StaticLogger class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;

namespace InfrastructureLayer.Common
{
    public static class StaticLogger
    {
        public static void LogInfo(Type declaringType, string text)
        {
            LogManager.GetLogger(declaringType.FullName).Info(text);
        }

        public static void LogWarn(Type declaringType, string text)
        {
            LogManager.GetLogger(declaringType.FullName).Warn(text);
        }

        public static void LogDebug(Type declaringType, string text)
        {
            LogManager.GetLogger(declaringType.FullName).Debug(text);
        }

        public static void LogTrace(Type declaringType, string text)
        {
            LogManager.GetLogger(declaringType.FullName).Trace(text);
        }

        public static void LogError(Type declaringType, string text)
        {
            LogManager.GetLogger(declaringType.FullName).Error(text);
        }

        public static void LogFatal(Type declaringType, string text)
        {
            LogManager.GetLogger(declaringType.FullName).Fatal(text);
        }
    }
}

Note the names of the static methods in StaticLogger correspond to the log level methods on a Logger. OK, let’s take a look at each of the classes in action.

EmployeeVO Class

To demonstrate the use of both the ApplicationBase and the StaticLogger classes, I’ll create two versions of a class named EmployeeVO. The first version will extend ApplicationBase. The second version will use the StaticLogger class. Create a new folder in the InfrastructureLayer project and name it VO. Add the EmployeeVO class to the VO folder.

Here’s EmployeeVO version1:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using InfrastructureLayer.Common;

namespace InfrastructureLayer.VO
{
    public class EmployeeVO : ApplicationBase
    {
        public EmployeeVO() : base(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
        {
            Log.Debug("Using ApplicationBase.Log Property");
        }
    }
}

This version of EmployeeVO extends the ApplicationBase class. The EmployeeVO constructor passed the DeclaringType to the base constructor through constructor chaining. The ApplicationBase.Log property is used in the constructor body to call the Logger.Debug() method.

To test this version of the EmployeeVO class go to the PresentationLayer project and open the Form1.cs file. Right click to view the code and and edit it to look like the following code snippet:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using InfrastructureLayer.VO;

namespace EmployeeTraining
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            EmployeeVO vo = new EmployeeVO();
        }
    }
}

You only need to add two lines of code which are highlighted above. All you’re doing is creating an instant of EmployeeVO because the logging takes place in that class’s constructor. When you made the changes save the file and run the solution. The log file will be written to the “…/debug/logs/” directory of the PresentationLayer project folder if you named your PresentationLayer project as suggested at the beginning of this post, otherwise, the logs directory will be located in the working directory of the executable project.

Here’s what the log output looks like after running the solution:

2017-07-09 08:23:30.7420|DEBUG|InfrastructureLayer.VO.EmployeeVO|Using ApplicationBase.Log Property

Your dates will be difference, of course.

Here’s EmployeeVO version 2 using the StaticLogger class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using InfrastructureLayer.Common;

namespace InfrastructureLayer.VO
{
    public class EmployeeVO
    {
        public EmployeeVO() 
        {
            StaticLogger.LogDebug(this.GetType(), "This class has a serious problem!");
        }
    }
}

There’s no need to change the code in Form1.cs. Once you’ve made these changes to the EmployeeVO class run the solution again. Here’s the log output from this example:

2017-07-09 08:27:10.8038|DEBUG|InfrastructureLayer.VO.EmployeeVO|Using StaticLogger.LogDebug() method

 

Summary

Consolidating logging to the InfrastructureLayer project saves you from having to install NLog in every project in complex Visual Studio solutions. You can create as many logger rules and targets as required.

I hope you found this post helpful. Happy coding!