WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Logging with Custom Web Events
Pages: 1, 2, 3, 4

Error Handling

There are many things that can go wrong with even such a simple program, but let's identify two obvious potential issues: when editing, the row might not be updated or an exception might be thrown.



You would like to log either of these events. Accomplishing this requires three steps:

  1. Catching the error or exception and creating a message to be logged
  2. Setting up the logging infrastructure in web.config
  3. Writing a custom web event to manage your error handling

web.config

The confusion, for most programmers, comes when they set up web.config. This is due in large measure to the fact that (a) the syntax is declarative, and (b) there are many options that don't have to be dealt with initially but they're not intuitive, and (c) everything depends on everything else so it is difficult to create a simple starting point.

We'll solve that by taking things one step at a time.

We begin by making sure that the web.config file already has the necessary elements in it that are not part of health monitoring--specifically, the membership connection string (so that we can log to the membership database) and the mail settings (so we can send email). Note that you don't need either of these. You could log to a file or a different database, in which case you'd just need to make sure you had the right connection string for your chosen database connection. And, nothing says you have to send email.

We've already seen the membership entry required to log to the membership table. The mail settings for smtp specify the mail host and (in my case) my login name and password:

<system.net>
    <mailSettings>
       <smtp from="jliberty@jliberty.com">
          <network host="mail.speakeasy.net" 
            password="????" userName="?????@????.com"/>
       </smtp>
    </mailSettings>
 </system.net>

With that in place, we are ready to set up the health monitoring section, which will handle our logging. Here is what you need to know (though there is much more to know when you move beyond the basics).

First, the entire health monitoring section is placed within the <system.web> element, along with the authentication, membership, compilation, and custom errors elements.

The two attributes that I add are enabled="true" and heartbeatInterval="0".

Zero indicates that the heartbeat check should be turned off, which is what we want (and which is the default).

The healthMonitoring element has four subelements that we care about: <bufferModes>, <providers>, <eventMappings>, and <rules>. These four combine to tell the system what you want to log, where you want to log to, and how you want to buffer the logging (so as not to slow down your system).

Creating a Custom Web Event

In our case, we want to log our own custom log events, so we will create a new class that will derive from one of the existing Web Events--specifically, WebBaseErrorEvent. This is a very convenient event to derive from, because it does a lot of the work of handling errors (and errors that have exceptions) for us. Our first step is to create a new class (which will be housed under App_Code. I'll place that code in the file Logging.cs, and name the class LoggingErrorEvent (though of course, you may choose whatever name you like).

My new class must have a constructor, and because I want to be able to send additional information I'll override the FormatCustomEventDetails method, which lets me send any text I like to the details part of the logged error.

The constructor for my class must pass four parameters to its base class:

  • A string message
  • The source of the problem (type object)
  • An integer that represents an error code
  • An Exception object (or null)

My LoggingErrorEvent class will create an enumeration for my own "loggingEventCodes." The Microsoft literature suggests that when you generate a code at the source of the problem you add WebEventCodes.WebExtendedBase to your code so as not to step on existing codes. I can't see making every class know about that, so I've put that into my enumeration as follows:

public enum loggingEventCode
{
   Exception = WebEventCodes.WebExtendedBase + 1,
   ProgramError,
   PotentialDataCorruption,
   Information,
};

The class itself holds on to the page that is passed in as the source and the exception that is passed in (if any) for use in the FormatCustomEventDetails method. Otherwise the constructor does nothing except translate the loggingEventCode back to an integer and pass its parameters to the base class.

public class LoggingErrorEvent : WebBaseErrorEvent
{
   private Exception exception;
   private Page page;

   public LoggingErrorEvent(
      string message,
      object source,
      loggingEventCode code,
      Exception ex
      ):
      base ( message, source, Convert.ToInt32(code), ex)
    {
      page = source as Page;
      exception = ex;
    }

Finally, my override of FormatCustomEventDetails checks to see whether I have an exception object and if I have a page, and sends along details about both if available (nothing fancy here).

public override void FormatCustomEventDetails( WebEventFormatter formatter )
{
   base.FormatCustomEventDetails( formatter );
   if ( exception != null )
   {
      if ( page != null)
      {
         formatter.AppendLine(page.ToString());
      }
      formatter.AppendLine(exception.Message);
      formatter.AppendLine(exception.Source);
      formatter.AppendLine("Exception stack trace...");
      formatter.AppendLine(exception.StackTrace);
   }
}

Event Mappings

web.config does not know about LoggingErrorEvents but it does know about WebBaseErrorEvents so the section can create a name for events of that type:

<healthMonitoring enabled="true" heartbeatInterval="0">
<eventMappings>
    <add name="All LoggingErrors" type="System.Web.Management.WebBaseErrorEvent" />
 </eventMappings>

Of course, you can have multiple mappings so that you could map errors to one name, exceptions to another, progress events to a third, and so forth.

Providers and Rules

With the name of our error event in place, next we need to tell web.config where to map each event type (in our case, we only have one type). This is done with two elements: <rules> and <providers>. A rule says "when you see this event name, use this provider." Each rule consists of three parts:

  • The name of the rule itself
  • The name of the event
  • The name of the provider

The name of the rule can be anything you like.

The name of the event will match the name you created in the <eventMappings> section.

The name of the provider will match the name you will create in the <providers> section.

You can create the rules before the providers or the providers before the rules; it is entirely up to you. I suspect most folks create the providers first. That way, the rules just match up events with providers. Let's do that.

Providers

The providers section has an element for each place you might send logging information (e.g., a database table, email, a file, etc.). Different providers need different information; email needs a To and From field, a database table does not. Here is my providers element for both email and for storing to the SQL table provided by the security table we looked at earlier:

<providers>
   <add name="MailEventProvider"
      type="System.Web.Management.SimpleMailWebEventProvider"
      from="jliberty@jliberty.com"
      to="jliberty@jliberty.com"
      bodyHeader="Logging Example Warning!"
      bodyFooter="Please investigate ASAP."
      subjectPrefix="Logging Error Notification: "
      buffer="true"
      bufferMode="Critical Notification" 
      maxEventLength="4096"
      maxMessagesPerNotification="1"/>
   
   <add name="SQLEventLog"
        type="System.Web.Management.SqlWebEventProvider"
        connectionStringName="LocalSqlServer"
        bufferMode="Logging Notification"
      />
</providers>

Notice that each provider has its own name (you may call each anything you like). In my case, I have named them MailEventProvider and SQLEvenLog, respectively. Nothing like consistency, and that was nothing like consistent.

Let's take the second one first. The SqlWebEventProvider has four attributes: its name, its type, the connectionStringName to indicate which database to connect to, and the name of the buffer mode. That last is defined in an element you haven't seen yet. Buffer modes are elements you define to tell the system how much you want to buffer the logging messages, trading off performance for the risk of losing a logging message should the system crash. We'll return to buffering shortly.

The first Provider is for email and contains more information. In addition to its type, it has fields for the From field, the To field (and you can add cc, etc.). It has a body header, footer, subject prefix, and so forth. You can indicate if it is to be buffered and, if so, which mode to use. Finally, you can indicate the maximum length and the maximum number of messages that should be put in a single notification.

I should also mention that I'm using the SimpleMailWebEventProvider. There is a second option--which I won't show here to keep things simple--that uses templates to create HTML email messages.

Rules

With the providers in place, we are ready to create the rules. As mentioned above, a rule has a name, and then it matches up an event with a provider. We want to match the All LoggingErrors with the MailEventProvider, and we want to match up that same event with the SqlEventLog, so we create two rules, each with its own name:

<rules>
   <add name="Email Error Notification" eventName="All LoggingErrors" 
      provider="MailEventProvider" />
   <add name="DB Logging" eventName="All LoggingErrors" provider="SQLEventLog" />
</rules>

Once again, you can imagine having many rules, allowing you to match some events with a subset of your providers, and other events with either a disparate or an overlapping subset of providers. But keeping things simple has its merits as well.

Pages: 1, 2, 3, 4

Next Pagearrow