pthivierge

Adding logging to a .NET application using log4net

Blog Post created by pthivierge Employee on May 10, 2016

 

Introduction

Debugging, testing, and understanding what an application does is almost impossible without a good logging system.  When our customers are developing .NET application with PI Developer Technologies we get a lot of questions pointing on a possible bad behavior of our components; further investigations including adding logs in the .NET application often leads to a complete different cause.  This is why I'll share with you how I am implementing a logging system in a .NET application.  You will see this is not difficult and  that a logging system has huge benefits:

  • Helps to understand what happens, and when.
  • Provides an history of actions and errors your application has performed / encountered
  • Speeds up development cycle: having logs also, often, allows to make corrections in the code without having to run the program in debug from Visual Studio, because you can look at log files to determine what is happening, and see when something goes wrong.

 

This post will explain you how to use Apache log4net in your application

 

Software used - Requirements

Visual Studio 2015 is used for this post, log4net is compatible with .Net since .Net v1, so if you are using another version of Visual Studio, that should not be a problem

Internet connection to get the NuGet package - You may also reference the .dll directly, NuGet is easier to use though and removes the need of keeping the .dll in your version control system.

 

Configure your application to use log4net

We'll create a simple application that also contains a library .dll, to see how easy it is to get logs from every parts of your application.

So go ahead and create :

  • a new console application called application.
  • Add a new code library project to the solution and call it library.

 

1 - Add the Nuget package to your project(s)

From the solution node in Visual Studio, right click and select "Manage NuGet Packages for Solution"

 

In the NuGet packages manager:

  1. Select Browse (this will search the Package source nuget.org, on internet)
  2. Enter "log4Net"
  3. Select log4net in the search results
  4. Select all projects in your solution for which you want to use logging
  5. Click Install

 

It is always good to look at the output to see if there is no error, mine looks like this:

 

2 - Create the log4net Configuration file

Add an empty .xml file to your application, I like to call it: log4net.config.xml

log4net needs XML configuration to work and this is what makes it so flexible, you can use either: your application.exe.config file or a separate file to store the configuration.  Personally I prefer a separate file and this is how we will configure it in this post.

log4net allows you to configure one or many "appenders".  An Appender is where the data from the logging statements is being written e.g.: AdoNet, MS SQL Server, Console, EventLog, File, Memory, SMTP, etc.

I will provide you the two basic configurations I am using, but keep in mind that this is not limited to it, log4net has a wide range of possibilities, possibly all you can think of you can log into it!

For this post, I'll use the configuration 1 below.

 

Configuration 1 - For a Console Application

This configuration will show logs in both: the console and a rolling text file:

  • For the console, logs will have a color based on the log level: ALL,WARN,ERROR.

Content of log4net.config.xml:

<log4net debug="false">
  <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
    <file value="Logs\CommandLine.Log" />
    <threshold value="ALL" />
    <appendToFile value="true" />
    <rollingStyle value="Composite" />
    <maximumFileSize value="1MB" />
    <maxSizeRollBackups value="10" />
    <datePattern value="yyyyMMdd" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="*%-10level %-30date %message [%logger] [%thread] %newline" />    </layout>
  </appender>

  <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
    <mapping>
      <level value="ERROR" />
      <foreColor value="Red, highintensity" />
    </mapping>
    <mapping>
      <level value="WARN" />
      <foreColor value="Yellow, highintensity" />
    </mapping>
    <mapping>
      <level value="ALL" />
      <foreColor value="Green, highintensity" />
    </mapping>
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="*%-10level %-30date %message [%logger] [%thread] %newline" />    </layout>
  </appender>

  <root>
    <level value="ALL" />
    <appender-ref ref="RollingFile" />
    <appender-ref ref="ColoredConsoleAppender" />
  </root>
</log4net>

 

 

Configuration 2 - For any application

This configuration simply logs into a text file ( for services and any other application type)

Content of log4net.config.xml:

<log4net>
  <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
    <file value="Logs\Application.Log" />
    <threshold value="ALL" />
    <appendToFile value="true" />
    <rollingStyle value="Composite" />
    <maximumFileSize value="1MB" />
    <maxSizeRollBackups value="10" />
    <datePattern value="yyyyMMdd" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="*%-10level %-30date %message [%logger] [%thread] %newline" />      </layout>
  </appender>

  <root>
    <level value="ALL" />
    <appender-ref ref="RollingFile" />
  </root>
</log4net>

 

Now my content in log4net.config.xml is same as what is shown in Configuration 1

 

One last step is to make sure that the file will be copied when I build the application, so I'll set the file property "Copy to Output Directory" to "Copy if newer":

 

3- Make the application load log4Net configuration

Open AssemblyInfo.cs

Insert the following line somewhere in the file, it does not matter where you insert it:

C#

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]

VB.NET

<Assembly: log4net.Config.XMLConfigurator(ConfigFile:="Log4Net.Config.xml", Watch:=True)>

 

 

The logger variable

Each time you need to log information from a file, you'll need to declare a logger in this file like shown below. Make sure to set the type of your containing class in the typeof operator. (You could hard code the logger type in a string, however, if you rename your class this is best if this is not hard coded.)

Add the following variable in your file, at the class level (or module if VB):

C#

private readonly log4net.ILog _logger = log4net.LogManager.GetLogger(typeof(Program));

 

VB.NET

Private logger As log4net.Ilog=log4net.LogManager.GetLogger(GetType(Module).ToString)

 

Full Example

Here is a more concrete example, you should notice the only place I am catching an exception is in the main, if an issue occurs the exception will "bubble up" until there.  Unless you are handling a particular exception, you should not catch exceptions.

application - Program.cs

using System;
using System.Diagnostics;

namespace application
{
    class Program
    {
        private static readonly log4net.ILog _logger = log4net.LogManager.GetLogger(typeof(Program));
        
        static void Main(string[] args)
        {
            var timer = Stopwatch.StartNew();// starting a timer do show how to log it later
            _logger.Info("application is starting...");
            _logger.Warn("Looks like nothing is really happening in this application");

            try
            {
                // doing something with the library
                var worker=new library.Worker();
                worker.doWork();
            }

            catch (Exception ex)
            {
                _logger.Error("Humm... something went as unexpected", ex);
            }
            finally
            {
                _logger.InfoFormat("application completed in {0}ms",timer.ElapsedMilliseconds);
            }
            
            Console.Write("press a key to terminate");
            Console.ReadKey();
        }
    }
}

 

library - Worker.cs

using System;


namespace library
{
    public class Worker
    {
        private readonly log4net.ILog _logger = log4net.LogManager.GetLogger(typeof(Worker));
        public void doWork()
        {
            _logger.Info("Worker starting...");
            throw new Exception("This is an exception that occurred to show how to log your exceptions with log4Net");
        }
    }
}

 

Result when running the application:

 

Console logs:

The log file in the application\logs directory:

What information are available in the logs

From the log output we could gather a lot of information:

  1. The type of log message, it is up to the developer (you!) to decide what log level to use when logging a message.  In the configuration file (root:level value) you can set which level of logs to output : ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF e.g.  If you set INFO in the configuration file, you will no see debug logs in the log output.
  2. The precise time of all log events
  3. The log message
  4. The object/class that generated the log. This is really helpful to know where to look in the code to find the entity that logged the message.
  5. The thread id, very useful when debugging multi-threaded applications. FYI: I never had any issue with log4net used by multiple threads.
  6. The full stack trace in case of an error, probably the most useful thing.  As log4net takes the Exception object , as you can see on line 25 of Program.cs, it is really easy to do and really useful.

 

Conclusion

I hope this helps you to configure logging in your applications, I use log4net all the time in all my applications and I am recommending it strongly.  It is really mature and highly configurable:  you can configure it to write logs in many different places, change the directory or the log file name from the configuration file, etc. I have attached the solution to the post in case you want to see it, you'll need to restore the NuGet packages for it to work.

 

I am also looking forward for your comments!

 

Talk to you soon

Attachments

Outcomes