Tracking Exceptions in Web Services with GUIDs

Note: This article first appeared in CVu Issue 17.5 (October 2005). CVu is the journal of ACCU, an organisation in the UK dedicated to excellence in software programming. Some links may not work.

Synopsis

This article demonstrates a technique for tracking exceptions across process boundaries in distributed systems using Globally Unique Identifiers (GUIDs); data from log files, bug reports, and on-screen error messages can then be correlated, allowing specific errors to be pinpointed.

Particular attention is paid to XML Web Services, with additional reference to DCOM, CORBA, Java/RMI and .Net Remoting.

The examples are given in C# [.NET 1.1], but the technique can be applied easily to Java and other languages.

Introduction

When dealing with modern distributed applications, it is often very useful to track an exception as it crosses process and machine boundaries. A comprehensive picture of the “distributed error” can then be constructed from log files of the different applications and components affected.

Languages such as C# and Java have an inbuilt mechanism to provide detailed error information – the Exception Stack Trace – which helps greatly to identify the source of runtime application errors. However, this information in itself does not always provide the full picture of an error condition.

The approach outlined here uses GUIDs (see Sidebar: GUIDs) effectively to “tag” exceptions before they leave one part of an application and appear in another. This allows exceptions to be tracked irrespective of whether the application is stand-alone or distributed, and irrespective of the transport, protocol or component architecture used.

Sidebar: GUIDs

A GUID (pronounced like “squid”) is a Globally Unique IDentifier, and is normally represented in string form something like this: DCF70619-01D8-42a9-97DC-6005F205361A. The GUID (also known as UUID – Universal Unique IDentifier) is an IETF standard, as defined in RFC 4122 [1]. The .Net Framework documentation for System.Guid [4] has this to say:

A GUID is a 128-bit integer (16 bytes) that can be used across all computers and networks wherever a unique identifier is required. Such an identifier has a very low probability of being duplicated.

GUIDs are generated using a variety of data, such as the current date & time, the IEEE 802 (MAC) address of the machine’s network card, etc.[2]. This is designed to ensure that the likelihood of generating the same GUID twice is very small.

There are UUID/GUID implementations for many different languages and platforms (see Sidebar: GUID Implementations), and even an online GUID generator [3]. In addition, it is easy to identify GUIDs written in the standard string representation (above) using Regular Expressions.

Note: UUIDs/GUIDs should not be confused with GIDs/UIDs on *nix systems!

XML Web Services: A Very Brief Introduction

XML Web Services is the name given to the latest class of middleware technologies designed to provide cross-platform Remote Procedure Calls (RPC). Previous technologies such as DCOM, CORBA and Java/RMI all have strengths, but often suffer from implementation difficulties, and none is really both platform- and language-independent [10], [11].

XML Web Services are described concisely in [16] as follows:

Web services are a new breed of Web application. They are self-contained, self-describing, modular applications that can be published, located, and invoked across the Web. Web services perform functions, which can be anything from simple requests to complicated business processes… Once a Web service is deployed, other applications (and other Web services) can discover and invoke the deployed service.

The increasing success of XML Web Services, and SOAP [9] in particular, as the “glue” for cross-platform RPC can be put down to several factors, including:

  • the technology is “platform-agnostic”, relying only on XML as the data exchange format
  • there is no explicit transport  protocol (most implementations use HTTP, but SMTP or other protocols are also valid [13])
  • the use of these standard protocols allows Web Services to be accessed behind a firewall (Port 80 for HTTP will usually be left open, for example)
  • Web Services can be discovered and used automatically using UDDI [14], due to Web Services being “self-describing” via WSDL [15].

All these properties make XML Web Services very attractive for building scalable, flexible distributed applications. For a more comprehensive introduction to XML Web Services, see [8].

SOAP

The Simple Object Access Protocol (SOAP) [9] is probably the most common dialect used by XML Web Services. The W3 introduction [17] to SOAP states:

SOAP is fundamentally a stateless, one-way message exchange paradigm, but applications can create more complex interaction patterns (e.g., request/response, request/multiple responses, etc.) by combining such one-way exchanges with features provided by an underlying protocol and/or application-specific information.

Although SOAP does not specify a transport protocol, HTTP is normally used. With the resultant “SOAP-HTTP Binding”, XML Web Services using SOAP and HTTP rely on message exchange using a combination of an HTTP Header and SOAP (XML) payload.

A SOAP message to retrieve (say) the author and title of a book might look something like this:

POST /cgi-bin/book-info.cgi HTTP/1.1
MethodName: GetBookDetails
MessageType: Call
Content-Type: text/xml-SOAP
<GetBookDetails>
     <ISBN>0201615622</ISBN>
</GetBookDetails>

Figure 1 – SOAP Request

Notice the standard HTTP POST request (first five lines), and that the SOAP payload (the GetBookDetails element) is simply XML.

The response from the Web Service might look like this:

200 OK
Content-Type: text/xml
Content-Length: 115
<GetBookDetailsResponse>
     <author>
         Herb Sutter
     </author>
     <title>
         Exceptional C++
     </title>
</GetBookDetailsResponse>

Figure 2 – SOAP Response

The XML payload contains a root element GetBookDetailsResponse that is the response to the original GetBookDetails request. Real-world SOAP messages would be somewhat more complex, but the principle above remains. For a much more comprehensive overview of SOAP, see [10].

SOAP deals with errors using a Fault response element. Any errors encountered by the Web Service, either in the request itself, or during the processing of the request, are detailed in the Fault element. For example, if the GetBookDetails request above failed due to an unknown ISBN, the XML payload of the response might look something like this:

<GetBookDetailsResponse>
     <fault>
         <faultcode>700</faultcode>
         <faultstring>Processing Error</faultstring>
         <runcode>1</runcode>
         <details>
              <Message>
                   ISBN Not Recognised!
              </Message>
         </details>
     </fault>
</GetBookDetailsResponse>

Figure 3 – SOAP Fault

The /fault/details node contains a Message element, with an explanation of the error. (Note: in practice, XML Namespaces would be used for both the request and the response XML: these have been omitted for clarity.)

We will return to the SOAP Fault shortly.

SoapExceptions in the .Net Framework

The Microsoft .Net Framework simplifies many of the implementation details of SOAP web services by classes in the System.Web.Services namespace [20], in particular, the WebService class, from which – by default – all other SOAP Web Services in .Net are derived.[A]

One of the most useful aspects of the Framework is that a SOAP Fault response is converted automatically to a System.Web.Services.Protocols. SoapException [18], which is thrown in the context of the calling client. Details of the error contained in the SOAP Fault element are made available as properties of the SoapException instance. [B]

This conversion also works in the opposite direction: a SoapException escaping from the Web Service is converted automatically into a SOAP Fault response. In fact, the .Net Framework ensures that only exceptions of type SoapException escape from a Web Service call: if an uncaught exception raised within a Web Service method is not a SoapException, the Framework throws a new SoapException, storing the uncaught exception as its InnerException. Details of the original exception are extracted into the SOAP Fault/details element. Figure 4 shows these steps:

Mapping SoapExceptions to SOAP Faults

Figure 4 – Mapping SoapExceptions to SOAP Faults

Figure 4 shows how information about an error in the Web Service method is transmitted back to the calling client. Thus, if the Web Service were to create a unique identifier for the error, and pass that identifier back to the client, we would be able to track that specific error as it travels across machine/process boundaries. Assuming that the error is logged in both places, we would have a way to link together error information from one application component with that from another.

Implementing Exception Tracking with GUIDs

Figure 5 shows a C# class definition for a simple base class to help with tracking exceptions:

using System;
public class TrackedException : Exception
{
     #region .ctor signatures in System.Exception
     public TrackedException() : this(Guid.NewGuid())
     {}
     public TrackedException(string message) : this(message, Guid.NewGuid())
     {}
     // etc...
     #endregion
     #region .ctors providing tracking ability using GUIDs
     protected TrackedException(Guid errorID) : base()
     {
         this.errorID = errorID;
     }
     protected TrackedException(string message, Guid errorID) : base(message)
     {
         this.errorID = errorID;
     }
     // etc...
     #endregion
     #region Tracking
     private Guid errorID;
     /// <summary>
     /// Uniquely identifies this exception
     /// </summary>
     public Guid ErrorID
     {
         get { return errorID; }
     }
     #endregion
}

Figure 5 – A basic TrackedException base class

The TrackedException class in Figure 5 automatically creates a new GUID in its constructors. Derived classes therefore do not need to concern themselves with GUID creation: in fact, they cannot, as the constructors relating to GUIDs are protected. Other useful base class constructors could be defined (e.g. matching the signatures of System.Exception), also priming the ErrorID property.

Wherever a TrackedException is thrown in code, we know that it will have a GUID-based ErrorID property, which we can include in log file data.

Crucially, however, in the context of Web Services, we can also include this GUID in the SOAP Fault response, by throwing explicitly a SoapException from within a Web Service method if an exception is thrown during processing:

[WebMethod] // Attribute needed for Web Service 'plumbing'
public string GetBananaPrice(string cityName)
{
     try
     {
         // some processing
     }
     catch (TrackedException te)
     {
         string message = te.GetType().Name + " " + te.Message;
         // N.B. Constructor parameters simplified here
         throw new SoapException(
              message,
              ExceptionHelper.WrapDetails(te.ErrorID),
              te);
     }
}

Figure 6 – Using a TrackedException in a Web Service

The call to the hypothetical ExceptionHelper.WrapDetails() method returns a System.Xml.XmlNode object that will be inserted into the SOAP Fault response XML payload.

The client calling the Web Service in Figure 6 would use code like this:

// Create local proxy for Web Service
// Connection to remote machine is handled automatically
PricesWebService ws = new PricesWebService();
try
{
     string bananaPrice = ws.GetBananaPrice("Havana");
}
catch (SoapException se)
{
     // Extract the GUID stored by the Web Service from the XML    
     string errorGUID = se.Details.InnerText;
     string message = se.Message;
     // Log the error here at the client...
     Console.Out.WriteLine("Error when calling GetBananaPrice(): " + message + " GUID: " + errorGUID);
     // TODO:
     //   Present the GUID to the user
     //   Notify admin using GUID
}

Figure 7 – Catching a SoapException at the client  and logging the GUID

The Details property of the SoapException at the client contains the XML from the XmlNode that was inserted in the catch handler of the Web Service method: the GUID of the original exception (see Figure 6). Logging this at the client will allow us to tie together the error logs from the two applications; showing the GUID to the user (see Figure 8) would allow her to copy/paste the GUID into a bug report (for example), further correlating the error information.

The same GUID appearing in different log files will refer to the same exception instance; due to the nature of GUIDs (see Sidebar: GUIDs) we can assume that a GUID will never be duplicated.

There is an advantage in presenting only a GUID rather than detailed error information to the user: it may not always be appropriate to divulge the details of an error (for security reasons, for example). The GUID acts as an opaque handle to the already-logged error; a bug report containing just the GUID should be enough to put that report in context.

Presenting the error GUID to the user

Figure 8 – Presenting the error GUID to the user

Other Technologies

It is fairly easy to extend the GUID-based error tracking to certain other frameworks and technologies. For Java/RMI (and its .Net analogue, .Net Remoting), it is basically enough to make the TrackedException class available to each side of the Remoting channel.[C] The ErrorID property of the exception instance will be serialized along with the rest of the object, and therefore be available to the calling client. [Note that a UUID class was introduced only in Java 2 SE 1.5, so earlier versions of Java will have to rely on other UUID implementations – see Sidebar: GUID Implementations, [c]]

CORBA and DCOM differ substantially in their support for exceptions. DCOM does not transmit exception details from server to client, relying instead on (much less useful) “HRESULT” return codes [5] [22]. There is therefore no simple way to extend the GUID-based exception tracking to DCOM.[D]

Unlike DCOM, CORBA does transmit exceptions across the communication channel. If we make TrackedException a CORBA::UserException, we can define a public property errorID, which will contain the string representation of the GUID:

// CORBA IDL
#pragma prefix "example.com"
module TrackedExceptionExample
{
    interface FruitPrices
    {
        exception TrackedException
        {
            string errorID;
        };
        string get_banana_price(in string cityName)
            raises (TrackedException);
    };
};

Figure 9 – TrackedException in CORBA

Some CORBA implementations already provide for a way to associate extra information with the exception – see [23].

However, for situations that do not provide this ability to ‘hook’ the exception GUID (for example, in ‘interop’ scenarios [21] [E]), it may be possible to append the GUID to the error message. C# code for a modified version of the TrackedException class to append the GUID to the error message would look like this:

using System;
public class TrackedException : Exception
{
     #region .ctor signatures in System.Exception
     public TrackedException() : this(Guid.NewGuid())
     {}
     public TrackedException(string message) : this(message, Guid.NewGuid())
     {}
     // etc...
     #endregion
     #region .ctors providing tracking ability using GUIDs
     protected TrackedException(Guid errorID) : base(String.Format(FormatString, "TrackedException", errorID))
     {
         this.errorID = errorID;
     }
     protected TrackedException(string message, Guid errorID) : base(String.Format(FormatString, message, errorID))
     {
         this.errorID = errorID;
     }
    // etc...
     #endregion
     #region Tracking
     public static readonly string FormatString =
         "{0} - ErrorID: {1}";
     private Guid errorID;
     /// <summary>
     /// Uniquely identifies this exception
     /// </summary>
     public Guid ErrorID
     {
         get { return errorID; }
     }
     #endregion
}

Figure 10 – TrackedException modified to store the GUID in the error message

The constructors ensure that a GUID is associated with the exception (as in Figure 5), but also automatically store the string version of the GUID in the Message property of the exception, by modifying the data passed to the base class constructor.

This approach sacrifices encapsulation for the ability to track exceptions across process and machine boundaries. Logging the received error message would still allow the correlation of error information from the various parts of the distributed system, because the original exception GUID is stored in the error message.

Future Developments

The System.Exception class in C# 2.0 (currently in Beta, and due for release some time in 2005) has a new member: Data of type IDictionary. This allows the association of arbitrary named data items with a given exception. The ErrorID property of TrackedException could be re-implemented to store the GUID in the Exception.Data dictionary, because this would remove the need for special treatment:

public class TrackedException : Exception
{
     // Constructors go here...
     // ...
     // for example:
     public TrackedException (Guid errorID) : base ()
     {
         this.Data["GUID"] = errorID;
     }
     public Guid ErrorID
     {
         get
         {
              return this.Data["GUID"] as Guid;
         }
     }
}

Figure 11 – ErrorID implementation on C# 2.0

The reason for this becomes clear when logging exceptions, we would just log all information in the Data dictionary (logging the Name, and calling .ToString() on the Value for each entry):

try
{
     // some processing...
     //
}
catch (Exception ex)
{
     // In practice, this code would go in a generic logging
     // method somewhere...
     Exception innerException = ex;
     while (null!=innerException)
     {
         // Log the basic exception details here
         // e.g. StackTrace, Message, etc.
         // ...
         // Log the Data dictionary entries
         //
         string[] names = ex.Data.Names;
         foreach (string name in names)
         {
              Log.WriteLine(name + " = " + ex.Data[name]);
         }
         innerException = innerException.InnerException;
     }
}

Figure 12 – Logging Exception GUIDs in C# 2.0

Arguably, the need for a separate TrackedException class is removed with C# 2.0, because any and every exception can have a tracking GUID associated with it, stored as a named entry in the IDictionary Data member, rather than needing a separate property ErrorID.

Sidebar: GUID Implementations

  1. Python: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/163604 and http://pyro.sourceforge.net/manual/11-implementation.html#util
  2. Perl: http://cpan.uwinnipeg.ca/dist/Data-UUID and http://perl.apache.org/docs/2.0/api/APR/UUID.html
  3. Java: [in standard Java 1.4 there is no UUID class] http://platform.jxta.org/nonav/java/impl/net/jxta/impl/id/UUID/UUID.html (Java 1.4.2) and http://java.sun.com/j2se/1.5.0/docs/api/java/util/UUID.html (Java 2 SE 5.0)
  4. SQL: “UUID() was added in MySQL 4.1.2.” – http://dev.mysql.com/doc/mysql/en/miscellaneous-functions.html (MySQL) and “Using GUIDs with IDS 9.x” – http://www-128.ibm.com/developerworks/db2/library/techarticle/dm-0401roy/ (DB2)

Observations and Notes

The concept of exception tracking presented in this article is similar to the idea presented in [6] and [7], where contextual information associated with an exception is maintained for later analysis. This article extends the idea across process, machine and protocol boundaries, however, relying on offline log file analysis to restore the contextual information.

The overhead associated with generating and transmitting a GUID may be unacceptable in some cases. However, in most situations, the benefits gained from  being able to track exceptions using a string of 30-something characters will probably outweigh the slight performance hit.

An example project demonstrating the ideas in this article is available from [www.accu.org].

Summary

This article demonstrated a simple scheme to track exceptions across Web Services and other distributed systems using GUIDs.

The benefits of using this scheme become apparent when the need arises to correlate information from multiple error logs and bug reports: the details of specific exceptions can be reconstructed at a later stage, and problems diagnosed more thoroughly.

A prototype of this scheme has been in operation for several months for a real-world project using SOAP Web Services (http://www.lamip.org/) and proven to be very useful indeed.

References

  1. GUID/UUID Specification – http://www.ietf.org/rfc/rfc4122.txt
  2. Background to UUIDs/GUIDs (UUIDs in DCE RPC) – http://www.opengroup.org/onlinepubs/9629399/apdxa.htm
  3. Online GUID Generator: http://kruithof.xs4all.nl/uuid/uuidgen
  4. System.Guid documentation for the .Net Framework: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemguidclasstopic.asp
  5. Get Seamless .NET Exception Logging From COM Clients…http://msdn.microsoft.com/msdnmag/issues/05/01/ExceptionLogging/
  6. Maintaining Context for Exceptions by Rob Hughes, CVu 15.4 (August 2003)
  7. Maintaining Context for Exceptions (Alternative) by Andy Nibbs, CVu 15.6 (December 2003)
  8. A Web Services Primer – Venu Vasudevan http://webservices.xml.com/lpt/a/ws/2001/04/04/webservices/index.html
  9. SOAP Specification – http://www.w3.org/TR/soap/
  10. SOAP (Introduction) – http://www.microsoft.com/mind/0100/soap/soap.asp
  11. Java RMI, CORBA or COM?Prithvi Rao –  http://www.usenix.org/publications/java/usingjava13.html
  12. A Young Person’s Guide to The Simple Object Access Protocol – Don Box –  http://msdn.microsoft.com/msdnmag/issues/0300/soap/
  13. SMTP as a [SOAP] Transport – http://blogs.msdn.com/rdias/archive/2004/06/17/158802.aspx
  14. UDDI – http://www.uddi.org/whitepapers.html
  15. WSDL Specification – http://www.w3.org/TR/wsdl
  16. Web Services – The Web’s next revolution  – IBM DeveloperWorks – http://www6.software.ibm.com/developerworks/education/wsbasics/wsbasics-ltr.pdf
  17. SOAP Version 1.2 – http://www.w3.org/TR/2003/REC-soap12-part0-20030624/
  18. SoapException documentation – http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwebservicesprotocolssoapexceptionclasstopic.asp
  19. Using Web Services with J2EEhttp://webservices.sys-con.com/read/39434.htm
  20. The .Net System.Web.Services namespace – http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwebservices.asp
  21. CORBA / .Net interop: Janeva – http://www.devx.com/interop/Article/19916/1954?pf=true and http://www.borland.com/us/products/janeva/
  22. Exceptions in COM – Bob DeRemer – http://msdn.microsoft.com/msdnmag/issues/04/03/ExceptionsinCOM/default.aspx
  23. CORBA CORBA::UserException::id() method – http://publib.boulder.ibm.com/infocenter/adiehelp/index.jsp?topic=/com.ibm.wasee.doc/info/ee/corba/concepts/ccor_ipgmce.html

Acknowledgements

Thanks to the Editor, Paul Johnson, for requesting this article.


[A] Web Services in .Net can also be built ‘from scratch’ if required.

[B] There are similar implementations for Java: see [19] for an example.

[C] Technically, it will also be necessary to ensure that the class is serializable. For a C# class with simple (serializable) fields, it is sufficient to mark the class with the [Serializable] attribute.

[D] The target COM Object could be made to support the IErrorInfo interface, which could then be queried for the exception GUID.

[E] See also http://udk.openoffice.org/common/man/uno_the_idea.html for an interesting discussion on Java/RMI, CORBA and DCOM.

Join the discussion...

This site uses Akismet to reduce spam. Learn how your comment data is processed.