Logging with Custom Web Eventsby Jesse Liberty
Every serious ASP.NET application requires logging, though not every application gets the logging it requires. Keeping an automated log allows you to track exceptions, errors, and unexpected events. A log can be used to create an audit trail, to track changes to your database, and to monitor the health and usage of your system.
There is any number of ways to create a logging system, and there is any number of places to log to. Among the most popular methods are logging to a file, logging to a database and, at times, sending an email to IT support (or, worse, to the developer!) when something goes drastically wrong.
ASP.NET provides extensive support for logging with the Health Monitoring classes provided in the System.Management namespace, but few know of it, and fewer use it. My personal theory is that this underutilization is due to a perfect storm of three factors: the system is typically presented in all its off-putting complexity; it is declarative, which is not a "natural" way for C# or VB programmers to think about coding; and Microsoft led us astray by naming the system Health Monitoring.
In this article, I will not attempt to explain the Health Monitoring system. Instead, I will extract the nuggets you can use to create a minimal yet very effective logging system that will send email in a crisis and routinely log to a database, and that is robust and extensible so that you can add the functionality you need with little effort.
Examining the Log
Before going into the details of how to add logging to your program, let's examine the output we'd like to see. In the application we're going to build, we'll take advantage of just a couple of the possible features, and log all errors and some events to a SQL Express table. We'll send an email message whenever an exception is thrown that might lead to a mismatch between what the app saves to a database and what the user hoped for.
It turns out that when the tables are created for Forms security and personalization (see my previous article), an extra table is added to the ADPNETDB.MDB database file: aspnet_WebEvents_Events, which is incredibly handy for capturing (surprise!) Web Events--the backbone of our logging. The structure of this table is shown in Figure 1.
Figure 1. aspnet_WebEvents_Events table structure
Web Events are provided by the Framework, in a hierarchy of classes, all deriving from the base class
WebBaseEvent. This base class holds the date and time of the event, a unique ID, and an event code and a detail code, as well as a text message.
The event code is an enumeration of errors that might arise during the life cycle of an application (e.g., "
RuntimeErrorValidationFailure"), a list of which can be obtained in the documentation, but that does not concern us here since we'll be creating our own event codes. (
EventDetailCodes are used to subdivide event codes.)
This information can also be packaged into an email message, as shown in the following snippet:
MyProject Warning! ** Summary ** --------------- This message contains events 1 to 1 from the total of 1 events scheduled for this notification. There were 0 events left in the buffer at the beginning of this notification. ** Application Information ** --------------- Application domain: cfde81e7-1-128169905359687500 Trust level: Full Application Virtual Path: /My Application Path: D:\Test\MyApplication \ Machine name: BERNSTEIN ** Events ** --------------- Event code: 3005 Event message: An unhandled exception has occurred. Event time: 3/26/2007 2:09:44 PM Event time (UTC): 3/26/2007 7:09:44 PM Event ID: a9224a59cb8b495b98dcd88740c79ecf Event sequence: 28 Event occurrence: 1 Event detail code: 0 Process information: Process ID: 4500 Process name: WebDev.WebServer.EXE Account name: BERNSTEIN\Jesse Exception information: Exception type: System.Data.OleDb.OleDbException Exception message: Data type mismatch in criteria expression. Request information: Request URL: http://localhost:2573/MyApplication/MyPage.aspx Request path: /MyApplication/MyPage.aspx User host address: 127.0.0.1 User: jesse Is authenticated: True Authentication Type: Forms Thread account name: BERNSTEIN\Jesse Thread information: Thread ID: 8 Thread account name: BERNSTEIN\Jesse Is impersonating: False Stack trace: at [stack trace here] --------------- [footer message here]