in

ASP.NET Weblogs

J e r o e n ' s   w e b l o g

Appending XML files and confusing disposables

There are several ways to create XML files using the .NET FCL, but none of the standard methods allows simple extension of an existing file, such as a logfile, without needing to put the entire file into memory first (because that's what would happend if I use the DOM to add nodes somewhere near the end of a tree).

Now XmlTextWriter is the ideal candidate for logging since from a user's perspective it's basically a standard filestream writer that has some standard formatting built-in. But unfortunately, everytime I Flush(), it wants the things I've written to be well-formed. Actually this is an advantage but not for what I'm trying to do here: if I'm extending a file, I want to rewrite the closing tag without having written the opening tag (since that's at the top of the file and was potentially created weeks ago).

So here's a quick solution I came up with. Basically what I do is derive from this class and then use its protected XmlTextWriter to write logging stuff. An implementation of this same idea (it's not a copy/paste because I don't have that code handy) has been running for a couple of weeks and it works well:

internal class Xmlog : IDisposable
{
  private bool first;
  private bool disposed = false;
  protected XmlTextWriter xw;
  public Xmlog(string logfile)
  {
    if(!File.Exists(logfile))
    {
      xw = new XmlTextWriter(logfile, System.Text.Encoding.UTF8);
      xw.WriteStartDocument(true);
      xw.WriteStartElement("log");
      first = true;
    }
    else
    {
      FileStream fs = File.OpenWrite(logfile);
      fs.Seek(-6, SeekOrigin.End); // length of  is 6 characters
      xw = new XmlTextWriter(fs, System.Text.Encoding.UTF8);
      first = false;
    }
    xw.WriteStartElement("session");
    xw.WriteAttributeString("datetime", DateTime.Now.ToString());
  }
  public void Dispose()
  {
    if(!disposed)
    {
      xw.WriteEndElement();
      if(first)
        xw.WriteEndElement();
      else
      {
        xw.Flush(); // flush here or XmlTextWriter will suspect something :)
        byte[] endtag = System.Text.Encoding.UTF8.GetBytes("");
        xw.BaseStream.Write(endtag, 0, endtag.Length);
      }
      xw.Close();
      disposed = true;
    }
  }
}

When I was coding up this class I stumbled upon something that keeps confusing me: IDisposable. In the MSDN documentation is an example where its use is illustrated in combination with a finalizer, demonstrating that you can clean whatever you want when the Dispose()-method is explicitly called, but when you're in the finalizer, you need to only clean unmanaged resources because during finalization the existence of those managed resources is not guaranteed anymore. But then I wonder: in the example above, if someone forgets to call the Dispose()-method, I can't use the finalizer to ensure he ends up with a valid XML file. At least, that's what I make of the way it's explained.

I ended up looking through the SLAR to see if there's any further mention of it there, but it basically contains the same information as in the MSDN documentation (except for some interesting background on how the interface came to be along with an annotation by Jeffrey Richter where he predicts my confusion :))

Comments

 

oleg@tkachenko.com (Oleg Tkachenko) said:

There is a much simpler solution - just have log file as XML fragment, not XML document. Then you can append entries to the end with no any hacks.
See http://www.tkachenko.com/blog/archives/000053.html
June 28, 2004 5:52 AM
 

Jeroen van den Bos said:

Thanks for the link! Ofcourse it's true that you can write fragments, but the point is that I want to write a valid XML file :) I agree your approach is less hackish though, which might be better in most circumstances.
June 28, 2004 5:58 AM
 

TrackBack said:

June 30, 2004 1:19 AM
 

Solutions for basic logging « # cat blogPosts said:

September 24, 2006 11:12 PM

Leave a Comment

(required)  
(optional)
(required)  
Add