Fixing problems with web sites can be tricky. On the desktop, if I can replicate the problem then attach a debugger and find the problem, When I am writing a web application this is what I do, I run IIS locally and debug locally and all is fine. However after the site is deployed things become more tricky.

In many hosted environments I often find myself restricted.

  1. I cannot attach a debugger to the running IIS or W3WP process
  2. I have no access to the event log
  3. I have no remote access to run programs such as Remote Desktop

There are good reasons for all these restrictions, and I am not complaining about them per say but they do mean that I have had to develop different techniques for diagnosing problems with a deployed web application in a hosted environment.

I like Enterprise Library, I have covered this before. One of the techniques I use is to extend the exception logging mechanism of enterprise library so that I can

 

  1. Write exceptions to a text file. I can write to local data areas in the hosted environment. This is provided to local data stores and logging / temp areas
  2. Add in extra information that will help in a web environment. The standard exception logging in enterprise library is not targeted at a web environment, I have added in many things from the ASP.NET HTTP request object that will help with diagnosing a problem. Much of the code for adding in extra information was inspired and written by Liam Corner, thanks Liam.

I address the two parts of this mechanism separately. In a previous post I have looked at the flat file logging with rollover and purging I have written for enterprise library.

To add in the extra information I have the logged event I have extended the Enterprise Library TextFormatter to add in extra information from the current HTTP context like this.

[ConfigurationElementType(typeof(CustomFormatterData))]
public class HttpTextFormatter : TextFormatter
{
    public HttpTextFormatter(NameValueCollection attributes)
              : base()
    {}

    ///
    /// Adds some HTTP context items,
    /// then calls the base class to do the formatting.
    ///
    public override string Format(
          Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry log
        )
    {
        // Be VERY careful not to do anything clever in here.
        // eg if you try to access the DB,
        // if DB access is broken you could end up in an
        // infinite loop of logging!
        try
        {
            HttpRequest request = HttpContext.Current.Request;
            log.ExtendedProperties.Add("Url",
                                        request.Url.ToString());
            log.ExtendedProperties.Add("UserHostAddress",
                                        request.UserHostAddress);

            foreach (string key in request.Headers.AllKeys)
            {
                log.ExtendedProperties.Add(
                       String.Format("Request header '{0}'", key),
                       request.Headers[key]
                   );
            }

            IPrincipal user = HttpContext.Current.User;

            if (user != null)
            {
                log.ExtendedProperties.Add("User name",
                      user.Identity.Name);

                RolePrincipal principal = user as RolePrincipal;

                if (principal != null && principal.IsRoleListCached)
                {
                    // Do not use the RoleProvider here
                    // in case it throws and logs another exception
                    log.ExtendedProperties.Add("User roles",
                          String.Join(",", principal.GetRoles()));
                }
            }
        }
        catch
        { }

        return base.Format(log);
    }
}

The Enterprise Library logging mechanism is a pipeline and all I am doing here is hooking into the pipeline by adding into the ExtendedPropoerties dictionary in the LogEntry. Calling the base class formatter causes Enterprise Library to iterate over this dictionary serialise it into the text file trace listener.

The extra information I am adding into the log entry is all the HTTP headers and the requested url.

Next I need to plug the formatter into the application, I did this by using the Enterprise Library configuration like this

httplogging

The key node is in the logging application block where I specified that for exceptions the formatter to be the HttpTextFormatter described above and the trace listener to be the rolling purging flat file trace listener described here. I also needed to configure the web site such that my web site could write to a folder called “Logging” below the root, the exceptions would be captured into a file called “exceptions.txt”.

Here is an excerpt from the txt file for a 404 error on the web site., it has been reformatted a bit to fit on this page.

----------------------------------------
  Timestamp: 10/26/2008 6:32:22 AM
  Message: HandlingInstanceID: 6fe50290-e169-4f4e-8a7a-ec3b2cec1c10
<Exception>
<Description>An exception of type 'System.Web.HttpException'
      occurred and was caught.</Description>
<DateTime>2008-10-26 00:32:22Z</DateTime>
<ExceptionType>System.Web.HttpException, System.Web,
      Version=2.0.0.0, Culture=neutral,
      PublicKeyToken=b03f5f7f11d50a3a</ExceptionType>
<Message>The file '/WorldolioWin.aspx' does not exist.
</Message>
<Source>System.Web</Source>
<HelpLink />
<Property name="ErrorCode">-2147467259
</Property>
<Property name="Data">
      System.Collections.ListDictionaryInternal
</Property>
<Property name="TargetSite">
      Void CheckVirtualFileExists(System.Web.VirtualPath)
</Property>
<StackTrace>
    ... stack trace removed ...
</StackTrace>
<additionalInfo>
<info name="MachineName"
       value="NT8"
   />
<info name="TimeStamp"
       value="10/26/2008 6:32:22 AM"
   />
<info name="FullName"
     value="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
                   Version=4.0.0.0, Culture=neutral,
                   PublicKeyToken=31bf3856ad364e35"
   />
<info name="AppDomainName"
       value="/LM/W3SVC/402/ROOT-1-128694185817341515"
   />
<info name="ThreadIdentity"
       value=""
   />
<info name="WindowsIdentity"
       value="NT8\worldolio"
   />
</additionalInfo>
</Exception>
  Category: Exceptions
  Priority: 0
  EventId: 100
  Severity: Error
  Title:Enterprise Library Exception Handling
  Machine: NT8
  App Domain: /LM/W3SVC/402/ROOT-1-128694185817341515
  ProcessId: 7096
  Process Name: c:\windows\system32\inetsrv\w3wp.exe
  Thread Name:
  Win32 ThreadId:2964
  Extended Properties: ASPIMPERSONATING -
  Url - <a
   href="http://www.worldolio.com/WorldolioWin.aspx?ctrl=screens"
   >http://www.worldolio.com/WorldolioWin.aspx?ctrl=screens</a>
  UserHostAddress - 65.98.224.4
  Request header 'Accept' -
      text/html,
      text/plain,
      application/pdf,
      application/msword,
      text/rtf,
      application/vnd.ms-excel,
      application/vnd.ms-powerpoint
  Request header 'Accept-Encoding' - gzip, x-gzip
  Request header 'Host' - www.worldolio.com
  Request header 'If-Modified-Since' - Sat, 30 Aug 2002 00:00:01 GMT
  Request header 'User-Agent' - TurnitinBot/2.1
     (<a href="http://www.turnitin.com/robot/crawlerinfo.html)"
      >http://www.turnitin.com/robot/crawlerinfo.html)</a>
  User name -
----------------------------------------

The section after Extended Properties has been added by the new formatter. It does make it a lot easier to tell what was in the original request that caused the error.

The formatter and the trace listener for Enterprise Library can be downloaded if you want to use them.