Server-Side Logging

The most important place for logging in your application is on the server side, where your remote components execute. The reasons for this include the following:

  • The server-side objects perform the majority of the work, including manipulating data and accessing the database. By the time a database error is propagated to the client, it has probably been rethrown as a more generic exception, which hides sensitive details about the internal system of your application. Logging this generic information might not be enough to enable you to diagnose the original problem.

  • The server-side objects can track the actions of all clients, which is useful when you are auditing the overall use of a system or searching for suspicious behavior.

  • Client-side logs are difficult to retrieve. A client can use a central log through some type of central component (like an XML Web service), but this adds complexity and further possibilities for error.

    Note

    Generally, client-side logging is the best way to identify problems in the client software or connection difficulties. It's a valuable feature but not a replacement for server-side logging. Client-side logging is discussed later in this chapter.


You need to make two decisions when implementing server-side logging: where to log the information and where to add the logging code. .NET provides numerous possibilities, each with its own benefits and drawbacks. Table 14-1 outlines some of your choices.

Table 14-1. Server-Side Logging Approaches in .NET 

Logging Approach

Description

Windows event log

A simple, low-overhead way to store error-related information that might be reviewed by an administrator at a later date. The chief drawback is that information is tied to a specific computer and is short-term. You can't assume that it will be reviewed before it expires.

Direct database

Best when the information being collected is more historical than error-related and demands later analysis rather than immediate action. For example, you can store security audit information. The chief drawback is that it requires more resources than some other event logging approaches.

Message Queuing

Sends error information to a message queue so that it can be retrieved and processed by another component. This approach enables you to use custom classes with logging information. However, it is suitable only if you design an automated utility to read the queue and designate how to further handle the error (for instance, send it to a database or trigger an administrator alert).

Direct mail

Sends an e-mail message with error information to an administrator. Useful for immediate alerts but tends to couple the error logging code with a specific error response strategy. Should be reserved for serious errors because it can easily lead to e-mail clutter (and disregarded messages).

Windows event logging and direct mailing haven't yet been addressed in this book; they are discussed a little later in this chapter.

ADO.NET database interaction and Message Queuing are easy to implement using the information covered in the first part of the book. The Message Queuing approach is particularly interesting and extremely flexible. Although it adds complexity, it enables you to completely separate your error logging code from your error response strategy. The remainder of this section outlines a sample approach you can use with Message Queuing.

First, you define a custom error object that contains information about problems. Listing 14-1 shows an example.

Listing 14-1 A serializable LoggedError class
 ' This enumeration represents how a component chooses to react ' to an error. <Serializable()>_ Public Enum ErrorAction     Unknown     RecoveredFull     RecoveredPartial     Ignored     RaisedToClient End Enum ' This is the class that will be send to the message queue. <Serializable()> _ Public Class LoggedError     ' This contains the original (caught) exception.     Public OriginalException As Exception 
     ' This indicates the name of the component and procedure      ' where the error occurred.     Public FailedComponent As String     Public FailedProcedure As String     Public FailedComponentVersion As String     ' A severity level is assigned as an integer from 0 to 10.     Public SeverityLevel As Integer     Public ActionTaken As ErrorAction End Class 

An exception handler in your component catches an error. It then creates an instance of the error object and calls a helper method to deal with it (as shown in Listing 14-2).

Listing 14-2 Logging the error through a facade
 Try     ' (Attempt action here.) Catch Err As Exception     Dim LogItem As New LoggedError()     LogItem.OriginalException = Err     ' (Other property set statements omitted.)     ' Log the error. In this case, LogComponent is a member variable     ' that references an instance of the EventLogger class shown in the     ' next code listing.     LogComponent.LogError(LogItem)     ' (Handle the error accordingly.) End Try 

The LogComponent object is an instance of the EventLogger class shown in Listing 14-3, which sends the error information to a message queue.

Listing 14-3 Sending the error information to a message queue
 Public Class EventLogger     Private Queue As New MessageQueue("MyDomain/MyLogQueue")     Public Sub LogError(ByVal logItem As LoggedError)         Dim ErrMessage As New Message(LogItem) 
         Queue.Send(LogItem, "Error")     End Sub     ' (Other methods could be added to log informational messages     ' differently from errors or warning, or to change the current     ' queue.) End Class 

Finally, a dedicated listener application receives the error message, examines it, and then decides how to act on it. Figure 14-1 shows the full approach.

Figure 14-1. Using message queuing to handle error logging

graphics/f14dp01.jpg

Logging with Facades and Reflection

The Message Queuing example is shows the basic pattern you should follow when adding logging. As much as possible, the code that performs the logging should be isolated from the code that handles the exception. This basic pattern enables you to quickly replace your logging strategies without touching the business code. For example, Listing 14-2 works with any class that exposes a LogError method. If you modify the LogComponent variable so that it contains an instance of a DatabaseLogger class, the application can continue to function seamlessly. This is nothing new in fact, it's a straightforward implementation of the facade pattern introduced in Chapter 10.

To improve on this approach, you can formalize the arrangement by defining an ILogger interface, which might look something like this:

 Public Interface ILogger     Sub LogError(logItem As LoggedError)     Sub LogWarning(logItem As LoggedWarning)     Sub LogInformation(logItem As LoggedInfo) End Interface 

Every logging class will then implement this interface:

 Public Class EventLogger     Implements ILogger     Private Queue As New MessageQueue("MyDomain/MyLogQueue")     Public Sub LogError(ByVal logItem As LoggedError) _       Implements ILogger.LogError         Dim ErrMessage As New Message(LogItem)         Queue.Send(LogItem, "Error")     End Sub     ' (Remainder of implementation code omitted.) End Class 

Finally, the business facade can just retain LogComponent as an ILogger variable:

 Private LogComponent As ILogger 

It can then use some initialization code at startup to determine which type of logging to implement. To implement this code, you might use a value in a database or you might use a setting in an application configuration file.

Listing 14-4 shows a constructor for the business facade that retrieves the name of the logging component class and the assembly it is contained in using the application configuration file. Keep in mind that the assembly of the logger component is probably not the same assembly as the one that contains the facade. You can deal with this reality using .NET reflection, which enables you to programmatically load a class from an external assembly using only the filename and class name.

Listing 14-4 Instantiating a logger dynamically
 Public Sub New()     ' Retrieve the name of the logger assembly.     ' This is a full path like "s:\MyAssemblies\Loggers\EventLog.dll"     Dim File As String     File = ConfigurationSettings.AppSettings("AssemblyFile")     ' Retrieve the name of the logger class.     ' This is a fully qualified name like "Loggers.EventLogger"     Dim Class As String     Class = ConfigurationSettings.AppSettings("LogClass")     ' Load the assembly.     Dim LoggerAssembly As Sytem.Reflection.Assembly     LoggerAssembly = System.Reflection.Assembly.LoadFrom(File)     ' Create an instance of the logger.     LogComponent = CType(LoggerAssembly.CreateInstance(Class), ILogger) End Sub 

For a much closer look at reflection, refer to Chapter 15, where it's used as the basis for a self-updating application and dynamic application browser. For now, note that reflection can come in handy in your exception handling code to determine information about the executing assembly (as shown in Listing 14-5). This way, when you review the logged message later, you'll know exactly which version of the application failed.

Listing 14-5 Using reflection to retrieve version information
 Try     ' (Attempt action here.) Catch Err As Exception     Dim LogItem As New LoggedError()     LogItem.OriginalException = Err     ' Set some properties obtained through reflection.     Dim CurrentAssembly As Sytem.Reflection.Assembly     CurrentAssembly = Sytem.Reflection.Assembly.GetExecutingAssembly()     LogItem.FailedComponentVersion = CurrentAssembly.GetName().Version     LogItem.FailedComponent = CurrentAssembly.GetName().Name     ' (Other property set statements omitted.) 
     ' Log the error.     LogComponent.LogError(LogItem)     ' (Handle the error accordingly.) End Try 

You now know the types of details you need to consider details that have less to do with .NET technology than they do with the overall design of your logging framework. The next few sections consider how to actually implement the code that goes into an ILogger class such as EventLogger.

The Windows Event Log

The Windows event log is a repository for information and error-related system and application messages. It provides its own basic management features (for example, a size limit that ensures it won't grow too large) and can be easily examined using the graphical Event Viewer utility included with Windows. The Windows event log offers a number of advantages:

  • Simplicity

    Even in the case of a database or system error, attempts to write log messages will probably still succeed. Unlike with simple file logging, concurrency problems won't occur. And because event logs are wrapped with easy-to-use .NET classes, there's little chance that a syntax error in your code will derail an important log message.

  • Standardization

    The event log is a basic part of the Windows operating system. You don't need to specifically configure a computer if you relocate your remote components; you can always assume that the event log will work.

  • Remote use

    The Windows event log is often criticized because it is machine-specific. Although this is true, you can programmatically log messages to another computer (or retrieve them in the same way) a requirement in any distributed system.

The Windows event log often has some nontrivial limitations as well, including these:

  • No object support

    Log messages are little more than simple strings. You can append binary data, but you need to manipulate it manually. This differs sharply from Message Queuing, which enables you to send custom objects. The lack of object support might not be a disadvantage if an administrator is reviewing the log, but it prevents you from creating an automated utility that scans and interprets log messages.

  • Size restrictions

    By default, log entries are automatically overwritten when the maximum log size is reached (typically half a megabyte), as long as these entries are of at least a certain age (typically seven days). This means that application logs can't be easily used to log critical information that you need to retain for a long period of time.

  • Little customizability

    You can create a custom log to store your messages. You also can assign an application-defined category number to your messages. However, that is the extent of the event log's customizability. There are no other ways to group or distinguish messages.

  • Location dependency

    Log messages are tightly bound to a specific computer. If you have several servers in a Web farm or if you migrate your remote components from one computer to another, you might end up with messages scattered everywhere.

In short, the event log is an ideal place to log short-term information with a minimum of fuss. It's not a suitable location for the long-term storage of auditing or profiling information.

To view the event log on the current computer, select Event Viewer from the Administrative Tools section of Control Panel. By default, you'll see the three standard event logs on the current computer, as described in Table 14-2.

Table 14-2. Standard Event Logs

Log

Description

Application

Used to track errors or warnings from any application. Generally, you will use this log or create your own custom log.

Security

Used to track security-related problems. For the most part, this log is used exclusively by the operating system.

System

Used to track operating system events.

To connect to the event logs on a different computer, right-click on the Event Viewer item at the top of the tree and choose Connect To Another Computer (as shown in Figure 14-2).

Figure 14-2. The Event Viewer

graphics/f14dp02.jpg

You also can right-click on one of the logs in the Event Viewer and choose Properties to modify its maximum size and overwrite settings, or you can create a new custom log. Figure 14-3 shows the properties of the Application event log.

Figure 14-3. Event log properties

graphics/f14dp03.jpg

To get an idea about what type of information is stored in the log, you might want to look at an average log entry that's been left by another application. You'll find that messages are generally classified as warnings, errors, or notifications. They include time and date information, along with the name of the application or component that entered the event and a full message.

Writing Log Entries

You can interact with event logs using the classes in the System.Diagnostics namespace. The EventLog class represents an individual log, and it's this class that you need to work with to retrieve and write log entries.

The event log doesn't permit indiscriminate writing. Before you can add a message to a log, you need to register your application as an event-writing source for that log. (Technically, you don't register your application just a string that identifies it.) You can perform this task manually using the EventLog.CreateEventSource method, or you can coax .NET into doing the work for you by setting the EventLog.Source property before you attempt to write a message. If you neglect to perform one of these steps, an error will occur.

Listing 14-6 shows the .NET code required to write an error message to the Application log.

Listing 14-6 Windows event logging
 ' Create a reference to the application log. Dim Log As New EventLog() ' Define an event source. If it is not registered, .NET will register ' it automatically when you write the log entry. Log.Source = "MyComponent" ' Write the entry. Log.WriteEntry("This is an error message", EventLogEntryType.Error) 

Figure 14-4 shows the corresponding item as viewed in the Event Viewer, and Figure 14-5 shows the actual error message content.

Figure 14-4. The updated error listing

graphics/f14dp04.jpg

Figure 14-5. The error message

graphics/f14dp05.jpg

Note that when you create an EventLog object without specifying the log you want to use, the Application log on the current computer is used by default. You can change this logic by using a different EventLog constructor, as shown here:

 ' Create a reference to the Security log. Dim SecurityLog As New EventLog("SecurityLog") ' Create a reference to the Application log on a computer ' named MyServer. Dim RemoteLog As New EventLog("Application", "MyServer") 

You can even create your own log using the shared EventLog.CreateEventSource method. This is a useful way to separate your messages from the clutter of entries added by other applications (as shown in Listing 14-7).

Listing 14-7 Creating a custom log
 ' Create the log (and register the event source) if the log does not ' already exist. If Not EventLog.Exists("MyCustomLog") Then     EventLog.CreateEventSource("MyComponent", "MyCustomLog") End If         Dim Log As New EventLog("MyCustomLog") Log.Source = "MyComponent" Log.WriteEntry("This is an error message", EventLogEntryType.Error) 

When writing a log entry, you have the choice of 10 overloaded versions of the WriteEntry method. You can choose to supply an event ID (a number you use to identify the event), a category number (a number you use to group messages), and an array of bytes. The EventLogEntryType enables you to indicate how the Event Viewer should classify the entry, according to the values shown in Table 14-3.

Table 14-3. EventLogEntryType Values

Value

Description

Information

Logs a non-error condition. The Microsoft recommendation is to use this for "an infrequent but significant successful operation." For example, you might use it to demarcate when a long-running system service starts. However, don't use this for a common occurrence; otherwise, you will quickly exhaust the available space in the event log.

Warning

Indicates a problem that isn't immediately significant (and doesn't result in an exception) but that might cause future complications. Many warning messages deal with resource consumption (for example, when disk space is low). If the application can't recover from the event without loss of functionality or data, it should use an Error event instead.

Error

Indicates a significant problem that results in the loss of functionality or data (for example, if a service can't be loaded or can't access a database).

SuccessAudit

A security event that occurs when some sort of security check succeeds (for example, a successful user login).

FailureAudit

A security event that occurs when a security check fails (for example, when a user login attempt fails). Failure audits are important because they can show suspicious patterns that indicate when the system's security has been compromised or when an intrusion has been attempted.

Note

Unless you want the responsibility of creating a byte array (and, if needed later, translating it back to its original form), the event log limits you to an ordinary string of human-readable information. If you're logging an error, you can use the Exception.ToString method. The ToString method returns a string with the name of the class that threw the exception, the exception message, and the stack trace. The return string also includes the result of calling ToString on the inner exception.


Retrieving Log Entries

When you create an application that uses a Windows event log, you also create a requirement for someone (generally a system administrator) to review that information. That person can perform this task using the Event Viewer, but the task can be complicated if the log is on another computer that doesn't allow remote access. In this case, you might need to create a remote component or XML Web service that exposes methods that allow authenticated users to retrieve log information. This service can retrieve the log information using the classes in the System.Diagnostics namespace.

Consider the Web method shown in Listing 14-8, which uses the EventLog.Entries property. This collection represents all the entries added to an event log. The Web method converts this information into a DataTable, which can easily be sent to the client and displayed using data binding.

Listing 14-8 Returning log information
 <WebMethod()> _ Public Function RetrieveLog(ByVal logName As String) As DataSet     Dim Log As New EventLog(logName)     ' Create a table to store the event information.     Dim dt As New DataTable()     dt.Columns.Add("EntryType", GetType(System.String))     dt.Columns.Add("Message", GetType(System.String))     dt.Columns.Add("Time", GetType(System.DateTime))     Dim Entry As EventLogEntry     For Each Entry In Log.Entries         ' Add this entry to the table.         Dim dr As DataRow = dt.NewRow()         dr("EntryType") = Entry.EntryType.ToString()         dr("Message") = Entry.Message         dr("Time") = Entry.TimeGenerated         dt.Rows.Add(dr)     Next     ' The DataTable is not a valid XML Web service return type.     ' Thus, you must add it to a DataSet before returning it.     Dim ds As New DataSet()     ds.Tables.Add(dt)     Return ds End Function 

The client requires a mere two lines to display this information using a DataGrid control:

 Dim Proxy As New localhost.MyService() DataGrid1.DataSource = Proxy.RetrieveLog("Application").Tables(0) 

Figure 14-6 shows the retrieved data.

Figure 14-6. The retrieved log information

graphics/f14dp06.jpg

Handling Log Events

The EventLog class also provides an EntryWritten event that enables you to respond when a new entry is added to a log. Using this technique, you can design a utility that waits for events and then attracts the administrator's attention by displaying a message box or playing a sound.

Before using the EntryWritten event, familiarize yourself with a few details:

  • To receive an event notification, you must set the EventLog.EnableRaisingEvents property to True.

  • Sadly, you can receive event notifications only when entries are written on the local computer. You can't receive notifications for entries written on remote computers, even if you create an EventLog object to represent such a log.

  • The EntryWritten event fires only a maximum of once every 5 seconds, even if log messages are written more frequently.

  • If you want to handle the EntryWritten event, make sure that you're using a custom log. Otherwise, the event is triggered when other applications add unrelated messages to the same log.

Handling the EntryWritten event is as straightforward as any other .NET event. You just connect the event handler (after creating a valid EventLog object):

 Dim Log As New EventLog("MyCustomLog") Log.Source = "MyComponent" AddHandler Log.EntryWritten, AddressOf OnEntryWritten Log.EnableRaisingEvents = True 

Then you handle the event:

 Private Sub OnEntryWritten(ByVal sender as System.Object, _   ByVal e as System.Diagnostics.EventLogEvent)      ' You can check what source sent the message, if desired.     If e.Entry.Source = "MyComponent" Then         MessageBox.Show("Entry written with message: " & _                         e.Entry.Message)     End If End Sub 

Direct Mail Notification

You can also send e-mail from a .NET application without needing to do much more than create a single object. The only catch is that to use this e-mail capability, you must have a properly configured SMTP server to send the message. (You can configure settings for the SMTP server using IIS manager.) If you don't, you won't be informed of an error and the message will never be received. For more information, refer to Microsoft's excellent introduction to Internet e-mail and mail servers at http://www.microsoft.com/TechNet/prodtechnol/iis/deploy/config/mail.asp.

The .NET e-mail features are found in the System.Web.Mail namespace. The key classes include MailMessage (which represents a single e-mail message), SmtpMail (which exposes shared methods you can use to send a message), and MailAttachment (which represents a file attachment that you can link to a message).

Sending a message is as simple as creating a new MailMessage object; setting some properties to identify the recipient, the priority, the subject, and the message itself; and using the SmtpMail.Send method to send it on its way (as shown in Listing 14-9).

Listing 14-9 Sending an error e-mail
 Dim MyMessage As New MailMessage() MyMessage.To = "someone@somewhere.com" MyMessage.From = "Automatic Logger" MyMessage.Subject = "Error" MyMessage.Priority = MailPriority.High MyMessage.Body = "Critical error: " & _                  LoggedError.OriginalException.ToString() SmtpMail.SmtpServer = "localhost" SmtpMail.Send(MyMessage) 


Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net