Integrate Log4Net into an ASP.NET Web API Project

For most of my projects I was comfortable with rolling my own logging and it became easy to reference this library in new projects. I then found myself wanting to have multiple log files and sending data from different parts of the application to each log for diagnostic purposes. There needed to be some serious changes to my logging library and I decided that this was not time well spent. So I started looking around at different logging frameworks and settled on Log4Net. I am not going to go into my decisions on why since this blog is about how I integrated Log4Net into my existing ASP.NET Web API project.

Log4Net

Log4Net was a port of Log4J over to .NET and is open source covered under the Apache Software Foundation. You can navigate to the official site here. Like all good software libraries, they are available to install using Nuget and at the time of writing, version 2.0.8 was the latest. The website is primarily used searching through the documentation. It works with .NET 2.0 and above and .NET Standard 1.3 for all you .NET Core lovers.

Demo Application

For this tutorial I created a simple project. My projects are multi-tiered and typically have many layers including a Common layer that is referenced by every other layer. This is a good place to include logging to make it assessable to everywhere whilst abstracting away the inner workings. This practise actually enabled me to slot in Log4Net into my existing application very easily. The example below has a Common library project and an ASP.NET Web API library project.

There is just a single web method that displays a message on the screen when navigating to empty Url.

Reference Log4Net

The only project that references Log4Net is Demo.Common. Let’s add the Log4Net nuget package using the Package Manager Console.

Custom Logging Class

The next thing we need to do is create our Custom Logging class in our Demo.Common project and reference Log4Net.

using log4net;

namespace Demo.Common
{
    public static class CustomLogging
    {
        public static void Initialize(string ApplicationPath)
        {

        }
    }
}

This is a static class that has an Initialize method with a parameter for the folder path. The path is used to locate the app.config file used to configure Log4Net and to locate the App_Data folder where the log file will be created. Log4Net can be configured within an application but makes it inflexible to change. Using an app.config file allows changes to the level of logging and locations simply by editing a file. Now we have our initialization method we now need to call it.

Application Entrypoint

Every application has an entrypoint or a method that is executed at start-up. In the case of an ASP.NET project this entry point is Global.asax in the Demo.UI.WebService project and specifically the method Application_Start(). This is where we should initialize our logging in order to make use of logging as soon as possible.

protected void Application_Start()
{
    GlobalConfiguration.Configure(WebApiConfig.Register);

    CustomLogging.Initialize(Server.MapPath("~"));
}

Here is our call to the static Initialize method passing in the server path. This would be the location where the App_Data or Web.Config objects are created.

Log4Net Configuration

As I eluded to earlier, the configuration I prefer is using an app.config file. As I am creating an ASP.NET Web API app, the logical place is the Web.config file however; you can actually create a specific config file for Log4Net and reference that one. I prefer this method. Create an empty Log4Net.config file.

Here are the entries I added to my config file. I am not going to explain all the entries however; look at the blue highlighted line. Log4Net allows us to create properties which are populated at runtime and then are used to aid the configuration.

<log4net>
  
  <root>
    <level value="INFO"/>
    <appender-ref ref="LogFileAppender"/>
  </root>
  
  <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
    <file type="log4net.Util.PatternString" value="%property{LogFileName}" />
    <appendToFile value="true" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="3MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="%date{dd MMM yyyy HH:mm:ss} - %-5level - %message%newline%exception" />
    </layout>
  </appender>

  <logger name="LAAC.UI.WebService">
    <level value="INFO" />
  </logger>

</log4net>

In the example above there is a property called LogFileName which contains the full path to the log filename I want Log4Net to create. This prevents us having to hardcode a path which may be different for different applications. I will show you later how we populate that property.

There is one bit of configuration that we do need to add to the Web.config file.

<configSections>
  <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>

You can think of this similar to a C# using namespace directive. It declares a name and the type used to handle the Log4Net entries defined in the Log4Net.config file. Make sure the root element name matches the name in the config section.

Initialize Log4Net

Now we have our configuration we can finish off our Initialize method.

public static class CustomLogging
{
    private static ILog _log = null;
    private static string _logFile = null;

    public static void Initialize(string ApplicationPath)
    {
        _logFile = Path.Combine(ApplicationPath, "App_Data", "Demo.UI.WebService.log");
        GlobalContext.Properties["LogFileName"] = _logFile;

        log4net.Config.XmlConfigurator.Configure(new FileInfo(Path.Combine(ApplicationPath, "Log4Net.config")));

        _log = LogManager.GetLogger("Demo.UI");
    }

    public static string LogFile
    {
        get { return _logFile; }
    }

}

Let’s go over each line.

  • Create _logFile variable holding the full path to the log file.
  • Add this value to the LogFileName Log4Net property which is referenced in the Log4Net.config file as mentioned above.
  • Configure an XML configurator using the path to our Log4Net.config file.
  • Get a logger object which matches our logger entry in the Log4Net.config file.
  • I also create a public getter LogFile to retrieve the log file path which I will explain why later on.

Define the Message Logging Methods

At this stage we have a fully configured system. What we now need is a method we can use to generate a message to log. Since we have different message severities, let’s create an ENUM for each one.

public enum TracingLevel
{
    ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}

Now let’s create a method called LogMessage which takes the severity and a message. We then call the appropriate Log4Net log method on the logger we created earlier.

public static void LogMessage(TracingLevel Level, string Message)
{
    switch (Level)
    {
        case TracingLevel.DEBUG:
            _log.Debug(Message);
            break;

        case TracingLevel.INFO:
            _log.Info(Message);
            break;

        case TracingLevel.WARN:
            _log.Warn(Message);
            break;

        case TracingLevel.ERROR:
            _log.Error(Message);
            break;

        case TracingLevel.FATAL:
            _log.Fatal(Message);
            break;
    }
}

Testing

That is all the setup we need at this stage. We can now try it out. I added a call to my test action.

public HttpResponseMessage Test()
{
    string _mess = "Web service is working";

    CustomLogging.LogMessage(TracingLevel.INFO, _mess);

    var resp = new HttpResponseMessage(HttpStatusCode.OK);
    resp.Content = new StringContent(_mess, System.Text.Encoding.UTF8, "text/plain");
    return resp;
}

I started the web service and navigated to the url. Then went to the folder where my app started. I opened the App_Data folder and low and behold the log file has been created.

Let’s look at the contents.

There you have it. The format of the entry can be practically anything you want. If you look in the log4Net.config file you will see the entry

If you check the Log4Net documentation it goes into great detail on the different layout patterns supported.

I have only skimmed the surface on Log4Net, so look out for future articles as I use more of its features.