Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
117 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Using XML Technologies For Enhancing Log Files


Enhancing XSLT using JavaScript

In the preceding section you have seen the power of some relatively simple XSLT for prettifying a log file. However, it is possible to provide even more possibilities by using some basic JavaScript.

The problem arises when you have a run-time log consisting of several thousand events and you are trying to find specific information about one or more unit(s), or are particularly interested in a specific type of message. In its current form, this technology would require you, the analyst, to examine all of the entries at the same time and read only those that concerned you. This works okay for relatively small log files, but quickly gets both difficult and tedious – not to mention error prone.

XSLT provides a useful set of constructs for conditional processing of elements, and it also provides the ability to declare parameters. Combining these two features we can filter the LogEvents from the log file and solve the aforementioned problem with browsing huge documents. Using parameters to describe which event types (e.g. "Debug" and "Event" mentioned previously), which namespace groups and allowing filtering of the specific files that a message originated from.

<!-- ******************************************************************************* -->
<!-- The "nameSpace" parameter is used to filter the messages depending on the       -->
<!-- general location that the message originated from within the source code        -->
<!--                                                                                 -->
<!-- The following special values are defined:                                       -->
<!-- *****************************************                                       -->
<!-- 'any'          : This will effectively disable namespace filtering              -->
<!--                                                                                 -->
<!-- The following are the currently used namespace names:                           -->
<!-- *****************************************************                           -->
<!-- default        : Originating in the root (unnamed) C++ namespace                -->
<!-- Engines        : The files that make up the core/underlying engine code         -->
<!-- Game           : The files that make up the actual game                         -->
<!-- UI             : The files that make up the user interface (mostly controls)    -->
<!-- Vars           : The data storage components of the code                        -->
<!-- ******************************************************************************* -->
  <xsl:param name="nameSpace" select="'any'"/>

The above listing is from the next revision of the XSLT document – the bulk of it is a comment describing the acceptable values for the actual parameter declaration. The value assigned to this particular parameter can be used later on in the document to provide conditional selection of elements. The nameSpace parameter is used in the following context for conditional selection:

<xsl:if test="$nameSpace='any' or $nameSpace=NameSpace">

</xsl:if>

If the contents of the LogEvent/NameSpace field matches that specified by the nameSpace parameter or there is the special 'any' value set, the contents of the xsl:if block is considered. The contents of the xsl:if block are exactly the same as the XSLT used in the previous section. With a number of parameters and suitable conditionals, the XSLT can get very difficult to follow – it is worth bearing this in mind when designing a similar system.

There is one minor problem with xsl:params that I have just introduced – they are written into the actual XSLT file. As it stands, you can use them to filter the output, but if you want to change the way that the filtering works, you have to manually edit the parameters and refresh the rendered output and as such it's not the easiest or most convenient method. However, help is at hand – using, as previously mentioned, JavaScript we can control both the rendering of the XML (via XSLT) and dynamically select the values for the parameters. All without actually changing the text stored in the XSLT!

In order to facilitate the use of JavaScript it is necessary to change the general structure of how the log file is constructed. Up until now we've used two files – the source data in the form of an .xml file and a transform stored as an .xsl file. Neither of these formats directly allows the use of embedded JavaScript code, so we'll need to wrap everything up in an HTML file. HTML has a script tag that can be used to embed JavaScript code, and it's via this entry point that we can start to add dynamic filtering of the generated log file.

The following listing shows the basic structure of the new wrapping document:

<html>
  <head>
    <TITLE>
      Log File Viewer
    </TITLE>
    <script language="javascript">

    </script>
  </head>
  <body bgcolor="#FFFFFF" vlink="#000000" alink="#000000"
      link="#000000" onload="javascript:onViewLog( ); javascript:generateLink( );">
    <font size="3" face="Arial" color="#2060AA">
      <b>
        <u>
          Configure the current log-file view:
        </u>
      </b>
    </font>

    …

    <div id="eventTypeLabel"></div>
    <hr>
    <div id="logview"></div>

  </body>
</html>

The script block exists in the head of the HTML, and the standard body of the HTML describes the various custom filters (more on this later). The final part is the div tags that are recipients for JavaScript generated content. JavaScript can be easily invoked via standard HTML hyperlinks (also note the usage of the "onload" attribute in the opening body tag), and as such provides a relatively simple means by which the viewer can select the configuration they desire. The JavaScript can then use the requested properties to set the xsl:params discussed earlier and load the document into the div targets.

The general design of the JavaScript, and consequently the HTML that interfaces with it, is to allow for multiple filters to operate on the generated XML output. The simplest implementation is to have a single JavaScript method that, when executed via a hyperlink, will generate the XML output in the div target(s). However this doesn't really provide for much customisability. If the JavaScript methods invoked from hyperlinks initially configured the parameters for filtering we could (in theory at least) configure any number of filters.

var g_eventType    = 'all';  //The 'eventType' xsl:param
var g_nameSpace    = 'any';  //The 'nameSpace' xsl:param
var g_specificFile = '';     //The 'specificFile' xsl:param

function setEventType( eventType )
  {

    g_eventType = eventType;
    generateLink( );

  }

function setNameSpace( nameSpace )
  {

    g_nameSpace = nameSpace;
    generateLink( );

  }

function setSpecificFile( fileName )
  {

    g_specificFile = fileName;
    if( g_specificFile == '' )
    {
      currentAdvFilter.innerHTML = "Not filtering entries based on a specific filename.";
    }
    else
    {
      currentAdvFilter.innerHTML = "Show only the entries originating from the file '<b>" +
                                     g_specificFile + "</b>'."
    }
    generateLink( );

  }

The above fragment from the body of the script tag shows this concept of multiple filters. The script globally defines three variables (g_eventType, g_nameSpace and g_specificFile) which can be configured through the corresponding set*( ) method calls.

You can see, in the setSpecificFile, that HTML formatted text is assigned to currentAdvFilter.innerHTML. Whilst not shown in the original outline, currentAdvFilter is the id property of one of the div tags in the body. Using this mechanism, we can effectively inject our own HTML code into the rendered output as the position of the div tag. A more complex example of this is the generateLink( ) method you can also see referenced in the above fragment. In order to aid the user, when a filter property is changed the display (via a div tag) updates accordingly so that they can see what the current combination is:

function generateLink( )
  {

    var linkText = "<br>";

    linkText = linkText + "<a href=\"javascript:onViewLog( );\">";

    linkText = linkText + "<font size=\"2\" face=\"Arial\" color=\"#2060AA\">";
       
    linkText = linkText + "<i>";
             
    linkText = linkText + "Click here to generate a report showing events of type '<b>" +
                 g_eventType + "</b>' ";

    if( g_specificFile == '' )
    {
      linkText = linkText + "from <b>all files</b> in the code namespace '<b>" +
                   g_nameSpace + "</b>'.";
    }
    else
    {
      linkText = linkText + "from <b>'";
      linkText = linkText + g_specificFile;
      linkText = linkText + "'</b> in the code namespace '<b>" + g_nameSpace + "</b>'.";
    }

    linkText = linkText + "</i>";

    linkText = linkText + "</font>";

    linkText = linkText + "</a>";

    linkText = linkText + "<br>";

    linkText = linkText + "<br>";

    //Commit the compiled string to the display
    eventTypeLabel.innerHTML = linkText;

  }

The above method takes all three of the parameters, in their current states, and injects an HTML hyperlink into the main display. The key to this all is that the actual text summary of what the filter will do is also the hyperlink you click on in order to process (or re-process) the XML. For example:

Click here to generate a report showing events of type 'Warning' from all files in the code namespace 'Engines'.

Because the actual "back end" of the JavaScript/XML is extremely simple (basically just setting up variables, and outputting their current values) you can interface by whatever means you see fit. It would be perfectly reasonable to draw up a set of icons/images for the various event types so that the user gets a more graphical/friendly display. However, the more powerful possibility is to use the HTML form tag (and its associated elements).

Using a form you can have complex GUI-style widgets for the user to work with – provided you can arrange the necessary JavaScript to store the results, you can achieve some very clever filtering combinations. I experimented with these, but in the end chose not to use them in order to keep simple design of this demonstration. The actual rendering (and the control you have over this) can to vary substantially between browsers and operating systems – and trying to create an aesthetically pleasing filter for the log files became more problematic than it was worth. This does limit the filtering possibilities, but you can salvage some of it – for example, I still managed to incorporate specific file searching. These issues can be worked around using various JavaScript detection routines and CSS tweaks, but again it was decided to keep this demonstration as simple as possible; however you are encouraged to explore other such solutions on your own.

In the above image, taken from Internet Explorer 6, you can see the HTML header defined by the example code for this article. I have gone to quite some lengths to get an aesthetically pleasing set of controls. The above display always appears at the top of the rendered page, and the horizontal rule visible at the bottom of the image provides a border between the generated output (below) and the controls (above). The colours used as backgrounds for the first row ("Types") of the table are exactly the same as those used in the background of the corresponding entries if, and when, displayed at the bottom of the page. Not only does this make it easier for the user to see which type that may want, it also acts as a simple key should they not be able to identify any entries in the resultant table.

So far I have described the basic process of interfacing the HTML (via hyperlinks) with the JavaScript defined in a script tags body, but I've missed out the absolutely crucial part – displaying the filtered XML!

As previously explained, the XSLT can be customized using the xsl:param notation (along with suitable conditionals) – but in raw XSLT these values were fixed in the actual file. Using JavaScript it is possible to instantiate the various XML parser/rendering components for a given browser and to configure them accordingly – including setting any internal xsl:param fields. Put the pieces together and you have the connecting piece. If we use the HTML and JavaScript to generate a set of controls, with the actual filters stored as variables, we can then use JavaScript to load the XML and XSLT and replace whatever is stored in the corresponding xsl:params with whatever the user has selected via the HTML interface.

Unfortunately, there is a complication (just to make things fun!) – the actual code for manipulating HTML is browser dependent (which, in all honesty, can be a complete minefield at times). It can be categorised as either being Internet Explorer or Mozilla – one of the two covers pretty much all browser/platform combinations that are popular at the moment. The amount of code required for either platform is relatively small and can easily be switched at run-time, so it isn't a huge problem.

function onViewLog( )
  {

    var moz = (typeof document.implementation != 'undefined') &&
              (typeof document.implementation.createDocument != 'undefined');

    if (moz)
    {
      onViewLog_Mozilla();
    }
    else
    {
      onViewLog_IE();
    }

  }

The above fragment demonstrates the way that a Mozilla/MSIE split can be achieved at runtime. A generic entry point, onViewLog( ), is provided that then delegates the actual hard work to the respective platform-specific function. If you use an exotic platform/browser combination it is possible that you might either have to customize this code or add an additional case to the branched execution.

For both platforms, the subsequent code effectively boils down to a simple four-step scenario:

1. Load in the raw XML data
2. Load in the XSLT transform script
3. Overwrite the xsl:params with runtime selected values
4. Output the XML (as processed by the XSLT) to the div target.

The following fragment does this for Internet Explorer based browsers:

function onViewLog_IE()
  {

    xmlDoc = new ActiveXObject( "MSXML2.DOMDocument.3.0" );
    xslDoc = new ActiveXObject( "MSXML2.FreeThreadedDOMDocument.3.0" );
    var xslTemplate = new ActiveXObject( "MSXML2.XSLTemplate.3.0" );

    //1. Load in the raw XML data:
    xmlDoc.async = "false";
    xmlDoc.load( 'XMLOutputWithComplexXSLT.xml' );

    //2. Load in the XSLT transform script:
    xslDoc.async = "false";
    xslDoc.load( 'ComplexXSLT.xsl' );

    xslTemplate.stylesheet = xslDoc;
    xslProcessor = xslTemplate.createProcessor( );
    xslProcessor.input = xmlDoc;

    //3. Overwrite the xsl:params with runtime selected values:
    xslProcessor.addParameter( "eventType",         g_eventType );
    xslProcessor.addParameter( "nameSpace",         g_nameSpace );
    xslProcessor.addParameter( "specificFile",      g_specificFile );

    //4. Output the XML (as processed by the XSLT) to the div target
    xslProcessor.transform();
    logview.innerHTML = xslProcessor.output;
       
  }

The above is a relatively simple single-function implementation of all four steps. The code for the Mozilla based browsers is a little more involved, as it uses a callback mechanism to pass on execution to different methods:

function onViewLog_Mozilla()
  {

    //1. Load in the raw XML data:
    xmlDoc = document.implementation.createDocument("", "", null);
    xmlDoc.load( 'XMLOutputWithComplexXSLT.xml' );
    xmlDoc.onload = readXML;
  
  }
       
function readXML()
  {

    xslDoc = document.implementation.createDocument("", "test", null);
    xslProcessor = new XSLTProcessor();

    //2. Load in the XSLT transform script:
    xslDoc.addEventListener("load", xslLoad, false);
    xslDoc.load( 'ComplexXSLT.xsl' );
  
  }

function xslLoad()
  {

    xslProcessor.importStylesheet( xslDoc );

    //3. Overwrite the xsl:params with runtime selected values:
    xslProcessor.setParameter( "", "eventType",     g_eventType );
    xslProcessor.setParameter( "", "nameSpace",     g_nameSpace );
    xslProcessor.setParameter( "", "specificFile",  g_specificFile );

    //4. Output the XML (as processed by the XSLT) to the div target
    xmlOutput = xslProcessor.transformToDocument( xmlDoc );
    var xmlSerializer = new XMLSerializer();
    logview.innerHTML = xmlSerializer.serializeToString( xmlOutput );

  }

The code itself is essentially the same as the preceding MSIE code, but with different syntax. Note that in both of the methods the JavaScript has to explicitly state which .xml and which .xslt file to use – it just so happens that they match up to those values specified in the physical files. If you wanted to, it would be possible to determine which style sheet you wanted to use at runtime – this alone opens up as many combinations as you can think of (or spend time writing!) and is an exceptionally powerful concept.

That is about all there is to discuss along the lines of the general concepts and technology. I have explained the basic concept behind the individual pieces used – so you should be able to add and/or customize the example code provided with this article in order to fit your own team (or personal) requirements. I strongly urge you to examine all of the included files – I have cherry-picked the interesting fragments of code/script for the actual text of this article, but it is by no means been an exhaustive explanation of every single line of code in this solution.

You can click here (Both: [19], MSIE Only [20], Mozilla Only [21]) to see the final, interactive, filtering log file output. There are three versions of the HTML document provided. Ideally [19] will work in all cases, but there appears to be some obscure circumstances where WindowsXP Service Pack 2's new security measures will stop it working (due to the presence of "unknown" Mozilla code). You can also view the XSLT file here [22] and the raw XML here [23].

The final output, when viewed, should look something like this:





Conclusion


Contents
  Introduction
  Basic requirements and background
  Prettifying XML using XSLT
  Enhancing XSLT using JavaScript
  Conclusion

  Printable version
  Discuss this article