Geeks With Blogs

News
View Szymon Kobalczyk's profile on LinkedIn

Szymon Kobalczyk's Blog A Developer's Notebook
Last weekend (yeah, I know it was Easter) I was writing a quick and dirty log viewer for log4net and came across what I believed would be a huge problem but turned out as quite trivial with .NET. As I blogged a while ago, log4net can be configured to broadcast events over UDP, so your application can be monitored in real-time from another process or machine, for example with the Chainsaw log4j viewer (yes, this is all Java). Basically I wanted to write my own little .NET replacement for Chainsaw.

In this configuration, each event is formatted as following XML fragment:

<log4j:event logger="MyLogger" timestamp="1144757984572" level="INFO" thread="1028">
  <log4j:message>Hello world!log4j:message>
  <log4j:throwable>log4j:throwable>
  <log4j:locationInfo class="?" method="?" file="?" line="?"/>
  <log4j:properties>
    <log4j:data name="log4net:UserName" value="MyMachine\user"/>
    <log4j:data name="log4jid" value="4"/>
    <log4j:data name="log4jmachinename" value="MyMachine"/>
    <log4j:data name="log4net:HostName" value="MyMachine"/>
    <log4j:data name="log4japp" value="MyProgram.exe"/>
  log4j:properties>
log4j:event>

So what we get from the UDP is a series of such XML fragments. And I thought this is bad as this doesn't look as well-formed XML document, because it doesn't have any root element (the events are appended dynamically so there is nowhere to put the closing tag).

Maybe my misconception was due that I worked mostly with XmlDocument class. As it turns out, the XmlTextReader is perfectly suited to handle such XML fragments. It has a special constructor that takes the XML fragment as a String or Stream, a fragment tape and a ParserContext. As the fragment type you can only use the Element, Attribute and Document values of the XmlNodeType enum.  The XmlParserContext holds information required to start parsing the XML fragment as if it was in the middle of a proper XML document. So for example, you can configure it with namespaces that normally would occur on the root element of the document.

To give you an example of this, below is my code to parse the log4j formatted events:

static readonly DateTime s1970 = new DateTime(1970, 1, 1);
public LogEventCollection Decode(Stream input)
{
    NameTable nt = new NameTable();
    XmlNamespaceManager nsmanager = new XmlNamespaceManager(nt);
    nsmanager.AddNamespace("log4j", "http://jakarta.apache.org/log4j/");
    Encoding encoding = Encoding.GetEncoding(Properties.Settings.Default.InputEncoding);
    XmlParserContext context =
        new XmlParserContext(nt, nsmanager, "elem", XmlSpace.None, encoding);
    XmlTextReader reader = new XmlTextReader(input, XmlNodeType.Element, context);
    LogEventCollection events = new LogEventCollection();
    reader.Read();
    while (reader.EOF == false)
    {
        if (reader.MoveToContent() == XmlNodeType.Element &&  reader.Name == "log4j:event")
        {
            LogEvent logEvent = new LogEvent();
            logEvent.Logger = reader.GetAttribute("logger");
            logEvent.Level = reader.GetAttribute("level");
            
            int thread;
            if (Int32.TryParse(reader.GetAttribute("thread"), out thread))
                logEvent.Thread = thread;
            long timeStamp;
            if (long.TryParse(reader.GetAttribute("timestamp"), out timeStamp))
                logEvent.TimeStamp = s1970.AddMilliseconds(timeStamp).ToLocalTime();
            int eventDepth = reader.Depth;
            reader.Read();
            while (reader.Depth > eventDepth)
            {
                if (reader.MoveToContent() == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case "log4j:message":
                            logEvent.Message = reader.ReadString();
                            break;
                        case "log4j:throwable":
                            reader.ReadString();
                            break;
                        case "log4j:locationInfo":
                            break;
                        case "log4j:properties":
                            reader.Read();
                            while (reader.MoveToContent() == XmlNodeType.Element 
&& reader.Name == "log4j:data") { string name = reader.GetAttribute("name"); string value = reader.GetAttribute("value"); logEvent.Properties[name] = value; reader.Read(); } break; } } reader.Read(); } events.Add(logEvent); } else { reader.Read(); } } return events; }
If you need to learn more there is a MSDN topic on this, and Daniel Cazzulino presents another solution to this problem. Posted on Thursday, April 20, 2006 7:05 PM Development | Back to top


Comments on this post: Reading XML Fragments with the XmlTextReader

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Szymon Kobalczyk | Powered by: GeeksWithBlogs.net