DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 6.7 Using One or More Event Logs in Your Application

Problem

You need to add the ability for your application to read and write one or more event logs of specific events that occur in your application, such as startup, shutdown, critical errors, and even security breaches. Along with reading and writing to a log, you need the ability to create, clear, close, and remove logs from the event log.

Your application might need to keep track of several logs at one time. For example, your application might use a custom log to track specific events as they occur in your application, such as startup and shutdown. To supplement the custom log, your application could make use of the Security log already built into the event log system to read/write security events that occur in your application.

Support for multiple logs comes in handy when one log needs to be created and maintained on the local computer, and another duplicate log needs to be created and maintained on a remote machine. (This remote machine might contain logs of all running instances of your application on each user's machine. An administrator could use these logs to quickly find any problems that occur or discover if security is breached in your application. In fact, an application could be run in the background on the remote administrative machine that watches for specific log entries to be written to this log from any user's machine. Recipe 6.10 uses an event mechanism to watch for entries written to an event log and could easily be used to enhance this recipe.)

Solution

Use the event log built into the Microsoft Windows operating system to record specific events that occur infrequently. The following class contains all the methods needed to create and use an event log in your application:

using System;
using System.Diagnostics;

public class AppEvents
{
    // Constructors
    public AppEvents(string logName) : 
        this(logName, Process.GetCurrentProcess( ).ProcessName, ".") {}

    public AppEvents(string logName, string source) : this(logName, source, ".") {}

    public AppEvents(string logName, string source, string machineName)
    {
        this.logName = logName;
        this.source = source;
        this.machineName = machineName;

        if (!EventLog.SourceExists(source, machineName)) 
        {
            EventLog.CreateEventSource(source, logName, machineName);
        }

        log = new EventLog(logName, machineName, source);
        log.EnableRaisingEvents = true;
    }

    // Fields
    private EventLog log = null;
    private string source = "";
    private string logName = "";
    private string machineName = ".";

    // Properties
    public string Name
    {
        get{return (logName);}
    }

    public string SourceName
    {
        get{return (source);}
    }

    public string Machine
    {
        get{return (machineName);}
    }

    // Methods
    public void WriteToLog(string message, EventLogEntryType type, 
                           CategoryType category, EventIDType eventID)
    {
        if (log == null)
        {
            throw (new ArgumentNullException("log", 
                "This Event Log has not been opened or has been closed."));
        }

        log.WriteEntry(message, type, (int)eventID, (short)category);
    }

    public void WriteToLog(string message, EventLogEntryType type, 
                           CategoryType category, EventIDType eventID, 
                             byte[] rawData)
    {
        if (log == null)
        {
            throw (new ArgumentNullException("log", 
                "This Event Log has not been opened or has been closed."));
        }

        log.WriteEntry(message, type, (int)eventID, (short)category, 
          rawData);
    }

    public EventLogEntryCollection GetEntries( )
    {
        if (log == null)
        {
            throw (new ArgumentNullException("log", 
                "This Event Log has not been opened or has been closed."));
        }

        return (log.Entries);
    }

    public void ClearLog( )
    {
        if (log == null)
        {
            throw (new ArgumentNullException("log", 
                "This Event Log has not been opened or has been closed."));
        }

        log.Clear( );
    }

    public void CloseLog( )
    {
        if (log == null)
        {
            throw (new ArgumentNullException("log", 
                "This Event Log has not been opened or has been closed."));
        }

        log.Close( );
        log = null;
    }

    public void DeleteLog( )
    {
        if (EventLog.SourceExists(source, machineName)) 
        {
            EventLog.DeleteEventSource(source, machineName);
        }

        if (logName != "Application" && 
            logName != "Security" &&
            logName != "System")
        {
            if (EventLog.Exists(logName, machineName)) 
            {
                EventLog.Delete(logName, machineName);
            }
        }

        if (log != null)
        {
            log.Close( );
            log = null;
        }
    }
}

The EventIDType and CategoryType enumerations used in this class are defined as follows:

public enum EventIDType
{
    NA = 0,
    Read = 1,
    Write = 2,
    ExceptionThrown = 3,
    BufferOverflowCondition = 4,
    SecurityFailure = 5,
    SecurityPotentiallyCompromised = 6 
}

public enum CategoryType : short
{
    None = 0,
    WriteToDB = 1,
    ReadFromDB = 2,
    WriteToFile = 3,
    ReadFromFile = 4,
    AppStartUp = 5,
    AppShutDown = 6,
    UserInput = 7        
}

Discussion

The AppEvents class created for this recipe provides applications with an easy-to-use interface for creating, using, and deleting single or multiple event logs in your application. Support for multiple logs comes in handy when one log needs to be created and maintained on the local computer and another duplicate log needs to be created and maintained on a remote machine. (This remote machine might contain logs of all running instances of your application on each user's machine. An administrator could use these logs to quickly discover whether any problems occur or security is breached in your application. In fact, an application could be run in the background on the remote administrative machine that watches for specific log entries to be written to this log from any user's machine. Recipe 6.10 uses an event mechanism to watch for entries written to an event log and could easily be used to enhance this recipe.)

The methods of the AppEvents class are described as follows:


WriteToLog

This method is overloaded to allow an entry to be written to the event log with or without a byte array containing raw data.


GetEntries

Returns all the event log entries for this event log in an EventLogEntryCollection.


ClearLog

Removes all the event log entries from this event log.


CloseLog

Closes this event log, preventing further interaction with it.


DeleteLog

Deletes this event log and the associated event log source.

An AppEvents object can be added to an array or collection containing other AppEvent objects; each AppEvents object corresponds to a particular event log. The following code creates two AppEvents classes and adds them to a ListDictionary collection:

public void CreateMultipleLogs( )
{
    AppEvents appEventLog = new AppEvents("AppLog", "AppLocal");
    AppEvents globalEventLog = new AppEvents("System", "AppGlobal");

    ListDictionary logList = new ListDictionary( );
    logList.Add(appEventLog.Name, appEventLog);
    logList.Add(globalEventLog.Name, globalEventLog);
}

To write to either of these two logs, obtain the AppEvents object by name from the ListDictionary object, cast the resultant object type to an AppEvents type, and call the WriteToLog method:

((AppEvents)logList[appEventLog.Name]).WriteToLog("App startup", 
                    EventLogEntryType.Information, CategoryType.AppStartUp,
                    EventIDType.ExceptionThrown);

((AppEvents)logList[globalEventLog.Name]).WriteToLog("App startup security check", 
                    EventLogEntryType.Information, CategoryType.AppStartUp,
                    EventIDType.BufferOverflowCondition);

Containing all AppEvents objects in a ListDictionary object allows you to easily iterate over all AppEvents objects that your application has instantiated. Using a foreach loop, you can write a single message to both a local and a remote event log:

foreach (DictionaryEntry log in logList)
{
    ((AppEvents)log.Value).WriteToLog("App startup", EventLogEntryType.FailureAudit, 
            CategoryType.AppStartUp, EventIDType.SecurityFailure);
}

To delete each log in the logList object, you can use the following foreach loop:

foreach (DictionaryEntry log in logList)
{
    ((AppEvents)log.Value).DeleteLog( );
}
logList.Clear( );

There are several key points that you should be aware of. The first concerns a small problem with constructing multiple AppEvents classes. If you create two AppEvents objects and pass in the same source string to the AppEvents constructor, an exception will be thrown. Consider the following code, which instantiates two AppEvents objects with the same source string:

AppEvents appEventLog = new AppEvents("AppLog", "AppLocal");
AppEvents globalEventLog = new AppEvents("Application", "AppLocal");

The objects are instantiated without errors, but when the WriteToLog method is called on the globalEventLog object, the following exception is thrown:

An unhandled exception of type 'System.ArgumentException' occurred in system.dll.

Additional information: The source 'AppLocal' is not registered in log 'Application'. 
(It is registered in log 'AppLog'.) " The Source and Log properties must be matched, 
or you may set Log to the empty string, and it will automatically be matched to the 
Source property.

This exception occurs because the WriteToLog method internally calls the WriteEntry method of the EventLog object. The WriteEntry method internally checks to see whether the specified source is registered to the log you are attempting to write to. In our case, the AppLocal source was registered to the first log it was assigned to—the AppLog log. The second attempt to register this same source to another log, Application, failed silently. You do not know that this attempt failed until you try to use the WriteEntry method of the EventLog object.

One way to prevent this exception from occurring is to modify the AppEvents class constructor to create a new EventLog object with an empty string for the log name parameter. This modified constructor call is highlighted in the following code:

public AppEvents(string logName, string source, string machineName)
{
     this.logName = logName;
     this.source = source;
     this.machineName = machineName;

     if (!EventLog.SourceExists(source, machineName)) 
     {
         EventLog.CreateEventSource(source, logName, machineName);
     }

     log = new EventLog("", machineName, source);
     log.EnableRaisingEvents = true;
}

Now, instead of an exception being thrown, the system searches for the log that the source is registered to and uses that log in place of the one specified in the logName parameter. If source is not registered to any log, the source will be registered with the Application log, and that log will be used by this EventLog object as well.

Another key point about the AppEvents class is the following code, placed at the beginning of each method (except for the DeleteLog method):

if (log == null)
{
    throw (new ArgumentNullException("log", 
        "This Event Log has not been opened or has been closed."));
}

This code checks to see whether the private member variable log is a null reference. If so, an ArgumentException is thrown, informing the user of this class that a problem occurred with the creation of the EventLog object. The DeleteLog method does not check the log variable for null since it deletes the event log source and the event log itself. The EventLog object is not involved in this process except at the end of this method, where the log is closed and set to null, if it is not already null. Regardless of the state of the log variable, the source and event log should be deleted in this method.

The DeleteLog method makes a critical choice when determining whether to delete a log. The following code prevents the Application, Security, and System event logs from being deleted from your system:

if (logName != "Application" && 
    logName != "Security" &&
    logName != "System")
{
    if (EventLog.Exists(logName, machineName)) 
    {
        EventLog.Delete(logName, machineName);
    }
}

If any of these logs are deleted, so are the sources registered with the particular log. Once the log is deleted, it is permanent; believe us, it is not fun to try and recreate the log and its sources without a backup.

As a last note, the EventIDType and CategoryType enumerations are designed mainly to log security type breaches as well as potential attacks on the security of your application. Using these event IDs and categories, the administrator can more easily track down potential security threats and do post-mortem analysis after security is breached. These enumerations can easily be modified or replaced with your own to allow you to track different events that occur as a result of your application running.

You should minimize the number of entries written to the event log from your application. The reason for this is that writing to the event log causes a performance hit. Writing too much information to the event log can noticeably slow your application. Pick and choose the entries you write to the event log wisely.


See Also

See Recipe 6.10; see the "EventLog Class" topic in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section