In one of my previous posts where I made an HTMLLayout for Log4Net Wim ten Brink commented that I should use XML with xsl instead.

AS I always listen to my followers. I did so swiftly. Here is the XslLayout class I created, Yes I know that there is an XMLLayout class available but that isn’t as good as the one I have.

```vbnet Imports log4net.Layout Imports System.Text

Namespace Logging.FileLog Public Class XslLayout Inherits LayoutSkeleton

    Public Overrides Sub ActivateOptions()

    End Sub

    Public Overrides Sub Format(ByVal writer As System.IO.TextWriter, ByVal loggingEvent As log4net.Core.LoggingEvent)
        If loggingEvent Is Nothing Then
            Throw New ArgumentNullException("loggingEvent")
        End If
        writer.Write("<log>")
        writer.Write("<datetime>" & DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & "</datetime>")
        writer.Write("<thread>" & loggingEvent.ThreadName & "</thread>")
        writer.Write("<level>" & loggingEvent.Level.DisplayName & "</level>")
        If loggingEvent.ExceptionObject IsNot Nothing Then
            writer.Write("<class>" & loggingEvent.ExceptionObject.Source & "</class>")
        Else
            writer.Write("<class>?</class>")
        End If
        writer.Write("<message>")
        loggingEvent.WriteRenderedMessage(writer)
        writer.Write("</message>")
        writer.Write("</log>")
        writer.WriteLine()
    End Sub

    Public Overrides Property Header As String
        Get
            Dim _Header As New StringBuilder
            _Header.Append("<?xml version=""1.0"" encoding=""utf-8""?>")
            _Header.Append("<?xml-stylesheet href=""log.xsl"" type=""text/xsl""?>")
            _Header.Append("<logs>")
            _Header.Append("<header>Started logging at " & DateTime.Now & "</header>")
            Return _Header.ToString
        End Get
        Set(ByVal value As String)
            MyBase.Header = value
        End Set
    End Property

    Public Overrides Property Footer As String
        Get
            Dim _Footer As New StringBuilder
            _Footer.Append("<footer>Stopped logging at " & DateTime.Now & "</footer>")
            _Footer.Append("</logs>")
            Return _Footer.ToString
        End Get
        Set(ByVal value As String)
            MyBase.Footer = value
        End Set
    End Property
End Class

End Namespace``` and this is the xsl file to go with it.

```xml <?xml version=“1.0”?>

<xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform">

<xsl:template match=“/”> <html lang=“en”> <head> <meta charset=“utf-8” /> <title>LogFile</title> <style type=“text/css”> table, th, td{ border: 1px solid black;} table {border-collapse:collapse;width:100%;} th{background-color:gray; color:white;} .warn{color:red;} .info{color:green;} .alternatecolor{background-color:lightCyan;} </style> </head> <body> <p> <xsl:value-of select=“logs/header”/> </p> <table> <tr> <th>Date time</th> <th>Thread</th> <th>Level</th> <th>Class</th> <th>Message</th> </tr> <xsl:for-each select=“logs/log”> <tr> <xsl:if test=“position() mod 2 = 1”> <xsl:attribute name=“class”> <xsl:value-of select=“translate(level, ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ ‘, ‘abcdefghijklmnopqrstuvwxyz’)“/> </xsl:attribute> </xsl:if> <xsl:if test=“position() mod 2 = 0”> <xsl:attribute name=“class”> <xsl:value-of select=“translate(level, ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ ‘, ‘abcdefghijklmnopqrstuvwxyz’)“/> alternatecolor </xsl:attribute> </xsl:if> <td> <xsl:value-of select=“datetime”/> </td> <td> <xsl:value-of select=“level”/> </td> <td> <xsl:value-of select=“thread”/> </td> <td> <xsl:value-of select=“class”/> </td> <td> <xsl:value-of select=“message”/> </td> </tr> </xsl:for-each> </table> <p> <xsl:value-of select=“logs/footer”/> </p> <hr /> </body> </html> </xsl:template>

</xsl:stylesheet>``` That was fun.

And here the result.

The XML logfile.

xml &lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;?xml-stylesheet href="log.xsl" type="text/xsl"?&gt;&lt;logs&gt;&lt;header&gt;Started logging at 9/02/2011 14:27:11&lt;/header&gt;&lt;log&gt;&lt;datetime&gt;2011-02-09 14:27:16&lt;/datetime&gt;&lt;thread&gt;10&lt;/thread&gt;&lt;level&gt;INFO&lt;/level&gt;&lt;class&gt;?&lt;/class&gt;&lt;message&gt;Using NHibernateConfiguration. Database: Server: &lt;/message&gt;&lt;/log&gt; &lt;log&gt;&lt;datetime&gt;2011-02-09 14:27:18&lt;/datetime&gt;&lt;thread&gt;10&lt;/thread&gt;&lt;level&gt;DEBUG&lt;/level&gt;&lt;class&gt;?&lt;/class&gt;&lt;message&gt;Constructor - Log initialized&lt;/message&gt;&lt;/log&gt; &lt;log&gt;&lt;datetime&gt;2011-02-09 14:27:19&lt;/datetime&gt;&lt;thread&gt;10&lt;/thread&gt;&lt;level&gt;WARN&lt;/level&gt;&lt;class&gt;?&lt;/class&gt;&lt;message&gt;Is this warning on?&lt;/message&gt;&lt;/log&gt; &lt;log&gt;&lt;datetime&gt;2011-02-09 14:27:19&lt;/datetime&gt;&lt;thread&gt;10&lt;/thread&gt;&lt;level&gt;DEBUG&lt;/level&gt;&lt;class&gt;?&lt;/class&gt;&lt;message&gt;Is this debug on?&lt;/message&gt;&lt;/log&gt; &lt;log&gt;&lt;datetime&gt;2011-02-09 14:27:19&lt;/datetime&gt;&lt;thread&gt;10&lt;/thread&gt;&lt;level&gt;INFO&lt;/level&gt;&lt;class&gt;?&lt;/class&gt;&lt;message&gt;Is this info on?&lt;/message&gt;&lt;/log&gt; &lt;footer&gt;Stopped logging at 9/02/2011 14:27:22&lt;/footer&gt;&lt;/logs&gt; And the result in the browser.

That looks exactly the same as previous and the resulting logfile is machinereadable to boot.

Warning: Your appender should have the AppendToFile attribute set to False else this will generate invalid XML. That is also the biggest drawback of this method. You could of course just create invalid XML and then write a parser but that was not really the point of the exercise.

Warnign 2: You also want to write the xsl to where the log file has been written else the result will be less satisfying.