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>

No comments:

Post a Comment