Logs and Traces in .Net

I need to add tracing capabilities to my current app, just the ability to set an error level (INFO, WARNING, ERROR) to track the execution of one request in a simple text file, so I started to look for some implementations, I've found two main frameworks:

The first one is an open source project based on the popular log4java, and the second one is a reference block from PAG group (MSDN). I had to discard log4net just because it's Apache License. So I started to dig into the Loggin Application Block.

To install the LogginAppBlock you need first to get:

EIF has a lot of functionality (async logging, WMI Events and so on) but the download page for WSE claims:

...The technology preview of WSE 2.0 is unsupported and is not licensed for production use. For supported bits, use WSE 1.0. ..

So I had to discard this option too. And start to look at the System.Diagnostics Namespace to see which functionality I can reuse,  I found  it has a lot of  useful things:

· TraceLeves to define the detail of your logs

· TraceListeners to specify the log destination

· Configurable Mechanism to define TraceLevels and TraceListeners

 

So I started to write some unit test to validate this features. The configuration file for the unit tests looks like this:

 

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

      <system.diagnostics>

            <switches>

                  <add name="TraceEnabled" value="1" />

                  <add name="TraceLevel" value="4" />

            </switches>

            <trace autoflush="true" indentsize="1">

                  <listeners>

                        <add name="FileTrace" type="System.Diagnostics.TextWriterTraceListener"

                              initializeData="d:\Demo.trace" />              

                  </listeners>

            </trace>

      </system.diagnostics>

</configuration>

 

To write the Asserts, the first attempt was to read the contents of the Demo.trace file to see if the contents are correctly logged. However I always get an IOException:

 

System.IO.IOException : The process cannot access the file " .\Demo.trace" because it is being used by another process.

 

So I need another listener controlated by the Fixture where the Trace Methods can write and the test can read:

 

[TestFixture]

public class TrazedClassFixture

{

      Stream logStream;

      [TestFixtureSetUp]

      public void initTestFixture()

      {

            logStream = new MemoryStream();

            Trace.Listeners.Add(

new TextWriterTraceListener(logStream));

           

      }

      [TestFixtureTearDown]

      public void Reset()

      {

            logStream.Close();

      }

     

[Test]

      public void LogWarning()

      {

            Log.Warning("WarningMessage");

            logStream.Position = 0;

Assert.AreEqual(“WARNING: WarningMessage”,

readStream(logStream));

      }

}

After running the tests from NUnit GUI I have the green bar and the file Demo.trace is created with the log information.

 

Now I need to mock the TraceLevel configuration, so my TraceLogger will accept a TraceSwitch instance in the ctor, to allow testing these features without changing the config.file.

 

TraceSwitch config = new TraceSwitch("TestSwitch", "ConfigurableSwitch");

ILogger logger = new TraceLogger(config, "TEST");

 

So each test can configure the tracelevel.

 

[Test]

public void ConfigOnlyError()

{

      config.Level = TraceLevel.Error;

      logger.Info("msg1");

      logger.Warning("msg2");

      logger.Error("msg3");

      Assert.AreEqual("msg3", actual());

}

 

private string actual()

{

string result = String.Empty;

memory.Position = 0;

using (StreamReader sr = new StreamReader(memory))

{

result = sr.ReadToEnd();

}

return result;

}

 

 

The next step is to create a Log from the ASP.NET Runtime, so I create  a WebApp and call Log.Warning Again. My WebApp consists of one WebMethod

[WebMethod]

public string HelloWorld()

{

      Log.Warning("Hello WS");

      return "Hello World";

}

 

Once the WebMethod is called it throws the next exception

 

 

 System.UnauthorizedAccessException: Access to the path 'C:\WINDOWS\system32\Demo.trace' is denied.

 

Obviously we should define the full path to the log file:

 

<listeners>            

<add name="FileTrace"                           type="System.Diagnostics.TextWriterTraceListener" 

      initializeData="d:\Logs\Demo.trace" />               

</listeners>

After giving permissions to the ASPNET user to the folder d:\Logs everythings works but the absolute path in the configuration file is something we must avoid, but this is another story.

 

At least, the code for my Logger class looks like this.

 

/// <summary>

/// Helper Class to Write Log Files based on .NET Trace Settings

/// </summary>

public  class TraceLogger : ILogger

{

      string _category = String.Empty;

      TraceSwitch traceConfig;

      /// <summary>

      /// Facory to create new Logger Object

      /// </summary>

      /// <param name="name">Used as category of traces</param>

      /// <returns>New Logger Object</returns>

      public static ILogger GetLogger(string name)

      {

            return new TraceLogger(name);

      }

      /// <summary>

      /// Initialize the LogWriter reading settings from .Config

      /// </summary>

      public TraceLogger(string category)

      {

            _category = category;

            traceConfig = new TraceSwitch("TraceLevel", "Controls the detail of the log");

      }

      /// <summary>

      /// Initialize the LogWriter with specified TraceSwitches

      /// </summary>

      /// <param name="config"></param>

      /// /// <param name="category"></param>

      public TraceLogger(TraceSwitch config, string category)

      {

            _category = category;

            traceConfig = config;

            Trace.AutoFlush = true;

      }

     

      public void Error(object message)

      {

                 

            if ( traceConfig.TraceError)

            {

                  writeWithCurrentTime("ERROR: " + message.ToString());

            }

      }

      public void Error(object message, Exception exception)

      {

            Error(message);

            if ( traceConfig.TraceError)

            {

                  writeException(exception);

            }

      }

      public void Warning(object message)

      {

     

            if ( (traceConfig.TraceWarning))

            {

                  writeWithCurrentTime("WARNING: " + message.ToString());

            }

      }

      public void Warning(object message, Exception ex)

      {

            Warning(message);

            if ( (traceConfig.TraceWarning))

            {

                  writeException(ex);

            }

      }

      public void Info(object message)

      {

            if ((traceConfig.TraceInfo))

            {

                  writeWithCurrentTime("INFO: " + message.ToString());

            }

      }

      public void Info(object message, Exception ex)

      {

            Info(message);

            if ((traceConfig.TraceInfo))

            {

                  writeException(ex);

            }

      }

      private void writeWithCurrentTime(string message)

      {

      string now = System.DateTime.Now.ToString("yy-MM-dd hh:mm:ss");

            Trace.WriteLine(now + " " + message, _category);

      }

     

      private void writeException(Exception exception)

      {

            Exception ex = exception;

            Trace.Write("EXCEPTION: ");

            Trace.IndentLevel++;

            while (ex != null)

            {

                  string msg = ex.Message;

                  string stack = ex.StackTrace;

                  Trace.IndentLevel++;

                  Trace.WriteLineIf(msg.Length>0, msg);

                  Trace.WriteLineIf(stack!=null,stack);

                  Trace.IndentLevel--;

                  ex = ex.InnerException;

            }

            Trace.IndentLevel--;

      }

     

}