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.)
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 }
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 Recipe 6.10; see the “EventLog Class” topic in the MSDN documentation.