Showing posts with label Logging. Show all posts
Showing posts with label Logging. Show all posts

Wednesday, 14 December 2011

Overriding TextWriteTraceListener Part2 - Output file to a relative path

In Part1 we discussed how to extend the TextWriterTraceListener class so that we can format its output however we like...

However, you may have noticed that the path to the output file was absolute and static...

This isn't much good if you deploy your application to various environments or machines and don't want to manage the 'initializeData' attribute individiually for each deploy.

Even if you put a relative path in that attribute, you will find that it doesn't render the output file relative to the directory in which your .config file resides...how annoying right!

However, if you were to use Microsoft's TextWriteTraceListener (ie not your new extended class), you will find that relative paths work fine!... WTF!

For a fantastic discussion on the details of what is happening here, i highly recommend you read the following article:

On TextWriterTraceListener, Inheritance, InitializeData, ASP.NET, and Paths

It is from this article that i have found my solution to this problem:
  1. Add a constructor to your extended class which takes a string representing the relative path to your desired output file
    > This should be called automatically by the framework if you're adding your listener via a .config file
  2. Call the base constructor and pass into it the result of a method which takes the relative path and returns a Stream object, which "is-a" (note the inheritance language, lol) FileStream object pointing to your desired output file
What is happening here is that the Framework will call your constructor which takes a string and then your constructor will call the base constructor which takes a Stream object...and viola: you'll have a TextWriterTraceListener which will output to your desired output file, using a relative path.

Now, the magic: the method which builds the FileStream.

NOTE: this is a modified version of the method taken from the
On TextWriterTraceListener, Inheritance, InitializeData, ASP.NET, and Paths article, so credit goes to Nicholas Piasecki for his initial investigations!!!

/// <summary>
/// Takes a file path, checks if it is a relative path, if so, we create
/// a FileStream object pointing relative from the configuration file directory 
/// and not from the current working directory. 
/// If the path is absolute, then we return a FileStream object pointing there.
/// </summary>
/// <param name="filePath" />The file path</param>
/// <returns>FileStream object pointing to the desired file</returns>
private static Stream CreateRelativeStream(string filePath)
{
 string configPath;
 string resultPath;

 resultPath = filePath;

 if (filePath[0] != Path.DirectorySeparatorChar &&
  filePath[0] != Path.AltDirectorySeparatorChar &&
  !Path.IsPathRooted(filePath))
 {
  ConfigurationSection configSection;

  configSection = (ConfigurationSection)ConfigurationManager.GetSection("system.diagnostics");
  if (configSection != null)
  {
   configPath = configSection.ElementInformation.Source;

   if (!string.IsNullOrEmpty(configPath))
   {
    string directoryName;

    directoryName = Path.GetDirectoryName(configPath);

    if (directoryName != null)
    {
     resultPath = Path.Combine(directoryName, filePath);
    }
   }
  }
 }

 return new FileStream(resultPath, FileMode.Append);
}

So now we can use a relative path in our .config file:
<system.diagnostics>
 <trace autoflush="true">
  <listeners>
   <add 
    name="customTraceListener" 
    type="MyApp.Global.MyNewTraceListener, MyApp" 
    initializeData="path/to/log.txt" />
  </listeners>      
 </trace>
</system.diagnostics>

Tuesday, 13 December 2011

Overriding TextWriteTraceListener Part1 - Custom format your trace messages

If you want to customise the messages that are written to a text file, via the TextWriterTraceListener class, it is deceptively simple.

It took me a while to figure out how to do this because most people are either posting about writing a custom trace listener by inheriting from TraceListener class (but this has mostly abstract methods that you need to implement from scratch).

I just wanted to change the format of the messages being written to the trace, eg to put a DateTime value at the start of each line.

  1. Create a new class which inherits from TextWriterTraceListener
  2. Over-ride the WriteLine method, providing your customised formatting and making sure to utilise the existing functionality in the base class
    > NOTE: Make sure you are over-riding the WriteLine() method (not Write or any of the other similar methods) which takes just a string message parameter, otherwise you will probably find that the application will compile and run fine but no traces will be written to your file!
  3. Provide a constructor which takes a path.
    > NOTE: Make sure that this constructor calls the base constructor otherwise you will probably find that the application will compile and run fine but no traces will be written to your file!
  4. Add your new derived listener class to the collection of Listeners and tell it which file to output the messages to - this can be done programmatically, say in Application_Startup event or in a .config file - which is my preference as this allows you to change the location of the output file without re-compiling.
    > NOTE: Make sure that you provide the name of assembly (in which your derived listener resides) to the the 'type' attribute listener entry you are adding to the listeners collection
    eg type="MyApp.Global.MyNewTraceListener, AssemblyName"
The code for the derived listener class:

public class ASMSTraceListener : TextWriterTraceListener
{        
 //public ASMSTraceListener() : base() { }

 public ASMSTraceListener(string path) : base(path)         
 {

 }

 public override void WriteLine(string message)
 {
  base.Write(DateTime.Now.ToString() + " ");
  base.WriteLine(message);
 }
}


The section for your .config file:

<system.diagnostics>
 <trace autoflush="true">
  <listeners>
   <add 
    name="customTraceListener" 
    type="MyApp.Global.MyNewTraceListener, MyApp" 
    initializeData="c:\\path\to\log.txt" />
  </listeners>      
 </trace>
</system.diagnostics>

Resources used for this article:

Tuesday, 20 September 2011

Determining Path in which the Application is executing

System.Reflection.Assembly.GetExecutingAssembly().CodeBase
VS
System.Reflection.Assembly.GetExecutingAssembly().Location

System.Reflection.Assembly.GetExecutingAssembly().CodeBase will give you something like this:

"file:///D:/MyWorkspace/MyVS2010Project/Source/AppName/bin/AppName.Project.DLL"

System.Reflection.Assembly.GetExecutingAssembly().Location will give you something like this:

"C:\\Users\\username\\AppData\\Local\\Temp\\Temporary ASP.NET Files\\appname\\11ac36b5\\e67e91b4\\assembly\\dl3\\6b43be0f\\20cfa62a_4877cc01\\AppName.Project.DLL"

Taken from Suzanne Cook's .NET CLR Notes:
The CodeBase is a URL to the place where the file was found, while the Location is the path from where it was actually loaded. For example, if the assembly was downloaded from the internet, its CodeBase may start with "http://", but its Location may start with "C:\".
It’s also good to know that the CodeBase is not guaranteed to be set for assemblies in the GAC. Location will always be set for assemblies loaded from disk, however.
System.IO.Path.GetDirectoryName:
Returns the directory information for the specified path string.
 
For example, passing the path "C:\Directory\SubDirectory\test.txt" into the GetDirectoryName method will return "C:\Directory\SubDirectory"

So the preferred method to obtain the path of the executing assembly is:

String strPath = System.IO.Path.GetDirectoryName(
 System.Reflection.Assembly.GetExecutingAssembly().Location);

However, you may not want the physical path of where the Assembly is located.

You may just want to get the physical path of the Application Root folder so that you can dynamically create a new folder and put something in there, eg, a 'Logs' folder and put your applications log in there.

In which case, you'll want to use something like:

System.Web.HttpContext.Current.Request.ApplicationPath

which will give you something like:
"D:\\MyWorkspace\\MyVS2010Project\\Source\\AppName"

Then you can bang that string straight into a directory creation call and create your "Logs" folder like this:


string result = Path.Combine(System.Web.HttpContext.Current.Request.PhysicalApplicationPath, "Logs");
if (!System.IO.Directory.Exists(result))
{
    try
    {
        System.IO.Directory.CreateDirectory(result);
    }
    catch (Exception ex)
    {
            // probably a permissions issue
            // could return the exception message to user interface
            // otherwise, cannot do much
            return "";
        }
    }
}



WinForm

System.Windows.Forms.Application.StartupPath