Logging Errors

Capturing error information is important for troubleshooting. Not only is it common for a user to forget details related to what error they received and how they got there, but if you've handled the error and replaced the default error message, the real error probably isn't even visible. Logging errors enables you to get the specific error message without re-creating the error or needing to provide details that are visible to an end user.

While error logging is very important, you only want to use it to trap specific levels of errors, because it carries overhead and can reduce the performance of your application. Your goal should be to log errors that are critical to your application's integrity—for instance, an error that would cause the data that the application is working with to become invalid.

There are three main approaches to error logging:

1. Write error information in a text file or flat file located in a strategic location.
2. Write error information to a central database.
3. Write error information to the system's Event Logs, which are available on all versions of Windows supported by the .NET Framework 4 or later. The .NET Framework includes a component that can be used to write to and read from the System, Application, and Security Logs on any given machine.

The type of logging you choose depends on the categories of errors you wish to trap and the types of machines on which you will run your application. If you choose to write to an Event Log, then you'll need to categorize the errors and write them in the appropriate log file. Resource-, hardware-, and system-level errors fit best into the System Event Log. Data-access errors fit best into the Application Event Log. Permission errors fit best into the Security Event Log.

The Event Log

Three Windows Event Logs are commonly available: the System, Application, and Security Logs. Events in these logs can be viewed using the Event Viewer, which is accessed from the Control Panel. Access Administrative Tools and then select the Event Viewer subsection to view events. Typically, your applications would use the Application Event Log.

Note that if you are creating a Windows RT application it is likely that your application will not have permission to write to the event log. Before you lament this fact, keep in mind that this is also true if you are working with an ASP.NET application. In both cases you will find that you are running in an account that does not have default permission to write to an event log.

Event logging, when available, can be found through the EventLog object. This object can both read and write to all of the available logs on a machine. The EventLog object is part of the System.Diagnostics namespace. This component allows adding and removing custom Event Logs, reading and writing to and from the standard Windows Event Logs, and creating customized Event Log entries.

Event Logs can become full, as they have a limited amount of space, so you only want to write critical information to your Event Logs. You can customize each of your system Event Log's properties by changing the log size and specifying how the system will handle events that occur when the log is full. You can configure the log to overwrite data when it is full or overwrite all events older than a given number of days. Remember that the Event Log that is written to is based on where the code is running from, so if there are many tiers, then you must locate the proper Event Log information to research the error further.

There are five types of Event Log entries you can make. These five types are divided into event-type entries and audit-type entries.

Event type entries are as follows:

  • Information—Added when events such as a service starting or stopping occurs
  • Warning—Occurs when a noncritical event happens that might cause future problems, such as disk space getting low
  • Error—Should be logged when something occurs that prevents normal processing, such as a startup service not being able to start

Audit-type entries usually go into the Security Log and can be either of the following:

  • Audit Success—For example, a success audit might be a successful login through an application to an SQL Server.
  • Audit Failure—A failure audit might come in handy if a user doesn't have access to create an output file on a certain file system.

If you don't specify the type of Event Log entry, an information-type entry is generated.

Each entry in an Event Log has a Source property. This required property is a programmer-defined string that is assigned to an event to help categorize the events in a log. A new source must be defined prior to being used in an entry in an Event Log. The SourceExists method is used to determine whether a particular source already exists on the given computer. Use a string that is relevant to where the error originated, such as the component's name. Packaged software often uses the software name as the source in the Application Log. This helps group errors that occur by specific software package.


Note
Certain security rights must be obtained in order to manipulate Event Logs. Application programs can access the event log based on the permissions under which the application is running. The ability to read and write to Event Logs is privileged.

The following code snippet is a simple example of how to create and use an EventLog object to record errors that occur within your application. Note that it looks for a way to put messages into a custom event log, which typically would carry the application's name.

Sub LoggingExample1()
  Dim objLog As New EventLog()
  Dim objLogEntryType As EventLogEntryType
  Try
    Throw (New EntryPointNotFoundException())
  Catch objA As System.EntryPointNotFoundException
    If Not EventLog.SourceExists("VB2012") Then
      EventLog.CreateEventSource("VB2012", "System")
    End If
    objLog.Source = "Example"
    objLog.Log = "System"
    objLogEntryType = EventLogEntryType.Information
    objLog.WriteEntry("Error: " & objA.Message, objLogEntryType)
  End Try
End Sub

This code declares two variables: one to instantiate your log and one to hold your entry's type information. Note the code checks for the existence of a target log, and if not found creates it prior to attempting to log to it. If you attempt to write to a source that does not exist in a specific log, then you get an error.

After you have verified or created your source, you can set the Source property of the EventLog object, set the Log property to specify which log you want to write to, and set EventLogEntryType to Information. After you have set these three properties of the EventLog object, you can then write your entry. In this example, you concatenated the word Error with the actual exception's Message property to form the string to write to the log.

Using the Trace and Debug Objects

As an alternative to the event log, you can write your debugging and error information to files. Trace files are also a good way to supplement your event logging if you want to track detailed information that would potentially fill the Event Log, or diagnosis of a problem requires analysis of a specific sequence of execution events. Just as important in an enterprise environment, you can arrange to either retrieve or email such files so that you can be notified when application issues occur.

This section will be leveraging the Debug and Trace shared objects that are available during your application's run time. Note that Windows applications and ASP.NET applications reference these objects in very different manners. This chapter will focus on using the Trace and Debug listeners.

The difference between the Trace and Debug objects relates to runtime availability. When you compile your application for debugging, both Trace and Debug object references are included in your executable. However, when you target your application for a release build, the compiler automatically omits any references to the Debug class. As a result you can create custom logging for use while developing your application, which is automatically removed when you are ready to deploy.

The Debug and Trace classes have the same methods and properties. For simplicity this chapter will simply refer to either the Debug or Trace class, even though in most cases the information provided is equally applicable to both the Trace and Debug classes.

The Trace class is interfaced with the streamwriter and other output objects by encapsulating them within listener objects. The job of any listener object is to collect, store, and send the stored output to text files, logs, and the Output window. In the example, you will use the TextWriterTraceListener class.

Trace listeners are output targets and can be a TextWriter or an EventLog, or can send output to the default Output window (which is DefaultTraceListener). The TextWriterTraceListener accommodates the WriteLine method of a Debug object by providing an output object that stores information to be flushed to the output stream, which you set up by the StreamWriter interface.

Table 6.3 lists some of the methods associated with the Debug object, which provides the output mechanism for the text file example to follow.

Table 6.3 Common Trace/Debug Object Methods

Method Description
Assert Checks a condition and displays a message if False
Close Executes a flush on the output buffer and closes all listeners
Fail Emits an error message in the form of an Abort/Retry/Ignore message box
Flush Flushes the output buffer and writes it to the listeners
Write Writes bytes to the output buffer
WriteLine Writes characters followed by a line terminator to the output buffer
WriteIf Writes bytes to the output buffer if a specific condition is True
WriteLineIf Writes characters followed by a line terminator to the output buffer if a specific condition is True

The TextWriterTraceListener class is associated with a StreamWriter to output a trace file. In this case, a trace file is a text file, so you need to understand the concepts involved in writing to text files by setting up stream writers. The StreamWriter interface is handled through the System.IO namespace. It enables you to interface with the files in the file system on a given machine.

As you will see, the StreamWriter object opens an output path to a text file, and by binding the StreamWriter object to a listener object you can direct debug output to a text file. Table 6.4 lists some of the commonly used methods from the StreamWriter object.

Table 6.4 Common StreamWriter Methods

Method Description
Close Closes the StreamWriter.
Flush Flushes all content of the StreamWriter to the output file designated upon creation of the StreamWriter.
Write Writes byte output to the stream. Optional parameters allow location designation in the stream (offset).
WriteLine Writes characters followed by a line terminator to the current stream object.

The following code snippet shows how you can open an existing file (called TraceResults.txt) for output and assign it to the Listeners object of the Trace object so that it can output your Trace.WriteLine statements.

    Friend Shared LogPath As String = "TraceResults.txt"

    Friend Shared Sub LogMessage(message As String)
        Dim id As Integer = Trace.Listeners.Add(New 
                                TextWriterTraceListener(LogPath))
        Try
            Trace.WriteLine(Now.ToShortDateString & " @ " & 
                            Now.ToShortTimeString & " , " & message)
            Trace.Listeners(id).Flush()

        Finally
            Trace.Listeners(id).Close()
            Trace.Listeners.RemoveAt(id)
        End Try
    End Sub 

Looking in detail at this code, you first see a declaration of a log path outside of the method. In a production environment, this is typically created as an application configuration setting. This allows for easy review and changes to the location of the resulting file. When no path but only a file name is used, the default behavior will create the file in the running application's default folder.

Next you see a Shared method declaration. The method is shared because there is nothing instance specific associated with it. It can live within a Module if you so desire. The key is it accepts a single parameter, which is the error message that needs to be logged.

The first step is to create a new TextWriterTraceListener and add it to the collection of listener currently associated with your Trace object. This collection can log errors to more than one listener, so if you wanted errors to also be reported to the event log, it is possible to just add multiple listeners. Note however, that because multiple listeners can be used, the final step in this method is to remove the listener which was just added.

Within the Try-Finally block that is then used to output the error, additional information that can be expanded to better classify the message time and/or location can be appended to the message. Once the output is written the buffer is flushed, the file handle is closed, and the Trace object is restored to the state it was in prior to calling this method.

Variations of this simple logging method can be used across multiple different production applications. Over the years the output recorded by such a simple function has proved invaluable in resolving “unexplained” application errors that occurred on remote client machines.

If you have been running the sample code this method was left uncommented specifically so that you could open the TraceResults.txt file and review all of your activity while testing the sample code that is part of this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset