Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Formatting an Exception in HTML for email to SysAdmin in c#

Tags:

c#

Our error handler emails the SysAdmin exceptions. Right now they look bad as plain text.

Is there a way to take an Exception and format it into nice looking html so the SysAdmin can read it more easily?

like image 658
Ian Vink Avatar asked Dec 06 '12 19:12

Ian Vink


2 Answers

<pre>
... htmlencoded output from Exception.ToString() goes here ...
</pre>
like image 156
Joe Avatar answered Sep 28 '22 07:09

Joe


I would serialize the exception into an XML element and then I would format it with a custom XSLT.

There is an interesting approach about how to serialize an Exception which you can read about here: Serializing Exceptions to XML. To summarize it, if you try to decorate a custom class inheriting from System.Exception with the [Serializable] attribute and then use the XmlSerializer class on it, you will get a runtime exception because of the Exception.Data property, which is implementing System.Collections.IDictionary. So, you may easily use the new System.Xml.Linq API (new as of .NET 3.5).

Here is a simple program which generates an exception and formats it as HTML.

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
using ConsoleApplication2.Properties;

class Program
{
    public static void Main(string[] args)
    {
        try
        {
            //throw an DivideByZeroException
            var a=0;
            var b=1/a;
        }
        catch (Exception ex)
        {
            //using the ExceptionXElement class
            var xmlException = new ExceptionXElement(ex);
            XslCompiledTransform myXslTrans = new XslCompiledTransform();

            //Resources.formatter is the xsl file added as a Resource to the project (ConsoleApplication2.Properties.Resources.formatter)
            //So, here we load the xsl
            myXslTrans.Load(XmlReader.Create(new StringReader(Resources.formatter)));

            //initialize a TextWriter, in this case a StringWriter and set it to write to a StringBuilder
            StringBuilder stringBuilder = new StringBuilder();
            XmlTextWriter myWriter = new XmlTextWriter(new StringWriter(stringBuilder));

            //apply the XSL transformations to the xmlException and output them to the XmlWriter
            myXslTrans.Transform(xmlException.CreateReader(), null, myWriter);

            //outputting to the console the HTML exception (you can send it as the message body of an email)
            Console.WriteLine(stringBuilder);
        }
    }
}

Here is the Formatter.xsl

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" encoding="utf-8" indent="no"/>
  <xsl:template match="/">
    <html>
      <body>
        <h1>
          <xsl:value-of select="name(/*)"/>
        </h1>
        <h2>
          <xsl:value-of select="//Message"/>
        </h2>
        <table border="1">
          <tr bgcolor="#9acd32">
            <th>StackTrace</th>
          </tr>
          <xsl:for-each select="//Frame">
            <tr>
              <td>
                <xsl:value-of select="."/>
              </td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

And here is the ExceptionXElement class definition:

using System;
using System.Collections;
using System.Linq;
using System.Xml.Linq;

/// <summary>Represent an Exception as XML data.</summary>
public class ExceptionXElement : XElement
{
    /// <summary>Create an instance of ExceptionXElement.</summary>
    /// <param name="exception">The Exception to serialize.</param>
    public ExceptionXElement(Exception exception)
        : this(exception, false)
    { }

    /// <summary>Create an instance of ExceptionXElement.</summary>
    /// <param name="exception">The Exception to serialize.</param>
    /// <param name="omitStackTrace">
    /// Whether or not to serialize the Exception.StackTrace member
    /// if it's not null.
    /// </param>
    public ExceptionXElement(Exception exception, bool omitStackTrace)
        : base(new Func<XElement>(() =>
        {
            // Validate arguments

            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            // The root element is the Exception's type

            XElement root = new XElement
                (exception.GetType().ToString());

            if (exception.Message != null)
            {
                root.Add(new XElement("Message", exception.Message));
            }

            // StackTrace can be null, e.g.:
            // new ExceptionAsXml(new Exception())

            if (!omitStackTrace && exception.StackTrace != null)
            {
                root.Add
                (
                    new XElement("StackTrace",
                        from frame in exception.StackTrace.Split('\n')
                        let prettierFrame = frame.Substring(6).Trim()
                        select new XElement("Frame", prettierFrame))
                );
            }

            // Data is never null; it's empty if there is no data

            if (exception.Data.Count > 0)
            {
                root.Add
                (
                    new XElement("Data",
                        from entry in
                            exception.Data.Cast<DictionaryEntry>()
                        let key = entry.Key.ToString()
                        let value = (entry.Value == null) ?
                            "null" : entry.Value.ToString()
                        select new XElement(key, value))
                );
            }

            // Add the InnerException if it exists

            if (exception.InnerException != null)
            {
                root.Add
                (
                    new ExceptionXElement
                        (exception.InnerException, omitStackTrace)
                );
            }

            return root;
        })())
    { }
}
like image 41
Alex Filipovici Avatar answered Sep 28 '22 08:09

Alex Filipovici