Managed C++: Reading and Writing Windows Event Log Entries

My previous article illustrated various tasks regarding the Windows Event Log service, including how to enumerate local and remote event logs, instantiate an EventLog object for a specific local or remote log, create a custom event log for your application's logging needs, and delete an event log. This article continues showing how to programmatically work with the Event Log by covering how to enumerate an event log, read specific event entries, and record new events.

Because the Event Log is a Windows service, this article pertains only to Windows NT, 2000, XP, 2003, and Longhorn.

Reading Event Log Entries

Reading event logs does not require an explicit call to a member method. This is because any time you associate an EventLog object with an event log—either through the object's constructor or via the EventLog::Log property—the EventLog::Entries array automatically fills with all event entries for that log. In the following snippet, both the log1 and log2 objects contain the event entries for the specified event log. Note that even if you've previously set the EventLog::Log property, setting it to another event log name causes the Entries array to re-initialize with the new event log's events.

// Get all events for "My Application Log"
EventLog* log1 = new EventLog(S"My Application Log");

// Get all events for "Application"
EventLog* log2 = new EventLog();
log2->Log = S"Application");

// Now get the "System" events
log2->Log = S"System";

Once the EventLog::Entries array has been filled, you can easily enumerate its entries—each encapsulated by the EventLogEntry class—via an enumerator. The following EnumerateLogEntries method takes a log name and machine name as its parameters and outputs all of the event log's entries:

// Method assumes caller will catch exceptions
// thrown by EventLog class
void EnumerateLogEntries(String* logName, String* machine = S".")
{
#pragma push_macro("new")
#undef new
  EventLog* log = new EventLog(logName, machine);
  System::Collections::IEnumerator* entries = log->Entries->
                                              GetEnumerator();
  while (entries->MoveNext())
  {
    EventLogEntry* entry = static_cast<EventLogEntry*>
                           (entries->Current);
    Console::WriteLine("Message: {0}", entry->Message);
    Console::WriteLine("\tType: {0}", __box(entry->EntryType));
    Console::WriteLine("\tEventID: {0}", __box(entry->EventID));
    Console::WriteLine("\tCat: {0}", entry->Category);
    Console::WriteLine("\tCatNbr: {0}", __box(entry->CategoryNumber));
  }
#pragma pop_macro("new")
}
When instantiating an EventLog object, if you specify a log name and not a machine name, the local computer (".") is assumed.

Unfortunately, there doesn't seem to be a way to filter events as the user can in the Windows Event Viewer application. Therefore, you'll need to perform any needed filtering by manually comparing EventLogEntry properties to the desired values. Here are some examples of manually filtering for the desired events:

// Want only events with an EventID of 100
if (entry->EventID == 100)
{
...

// Want only Error events
if (entry->EntryType 
== EventLogEntryType::Error)
{
...

// Want only events from the event
// source named "My Application"
if (0 == String::Compare(entry->Source,
                         S"My Application"))
{
...
Note that the EventLog::EventID is an obsolete member as of .NET 2.0.

In addition to reading the event entries of an event log using an enumerator, you can read them by specifying their relative index value, because the EventLog::Entries member is an array. Because the array is ordered by the date/time the event was recorded, you would need to read from the end of the array to read the last events. To illustrate that, the following method (ReadLastEventEntries) reads the last five entries (EventLogEntry objects) of the specified event log and outputs each entry's TimeGenerated and Message properties:

// Method assumes caller will catch exceptions
// thrown by EventLog class
void ReadLastEventEntries(String* logName, String* machine = S".")
{
#pragma push_macro("new")
#undef new
  EventLog* log = new EventLog(logName, machine);

  Console::WriteLine("Reading 5 entries for {0}/{1} event log",
                     (0 == machine->CompareTo(S".")) 
                        ? Environment::MachineName : machine,
                     logName);

  String* format = S"{0, -10} {1}";
  Console::WriteLine(format, S"DateTime", S"Message");

  EventLogEntry* entry;
  Int32 nEntries = log->Entries->Count;
  for (int i = nEntries; 
       i > Math::Max(0, nEntries - 5);
       i--)
  {
    entry = log->Entries->Item[i-1];
    Console::WriteLine(format, 
                       entry->TimeGenerated.ToShortDateString(),
                       entry->Message);
  }
#pragma pop_macro("new")
}

As an aside, note the use of the Environment::MachineName property to obtain the local machine's name. I use this value because the EventLog::MachineName isn't automatically converted to the local machine name when a "." is passed, although that value does internally represent the local machine.

Because the EventLogEntry property is read-only, you cannot modify an entry or write to the log using the Entries array. Instead, you must use the EventLog::WriteEntry method, which the next section covers.

Managed C++: Reading and Writing Windows Event Log Entries

Recording Events in an Event Log

Recording events using the Win32 SDK involves the following:

  1. Creating a Windows Registry that defines the application as an event source
  2. Creating a special file with a unique syntax that contains the messages, event IDs, and category IDs to be written
  3. Compiling that file with the Windows Message Compiler
  4. Calling the Win32 ReportEvent function—passing the IDs from the message file

[EventLogReadingAndWriting-1.jpg]

Figure 1: Recorded Events in a Custom Event Log

This is much easier—although admittedly not completely implemented—with .NET 1.1 and the EventLog class. (I'll cover what's changed between 1.1 and 2.0 of the .NET Framework with regards to the Windows Event Log support shortly.) First off, the EventLog::CreateEventSource method takes care of registering the application in the registry as an event source for the specified event log. Secondly, the EventLog::WriteEntry method has many overloads that make it very easy to record events. These overloads take various combinations of values indicating event source, message, event ID, category ID, event type, and even binary data. Here are some brief explanations of each of these values and its usage:

  • Source—The event source, or application, creating the event
  • Message—The textual value (that the user can read in the Event Viewer) that describes the reason for the event having been recorded
  • Event Type—An enum (represented by the EventLogEntryType) that represents the type of event that was recorded: Information (the default), Warning, Error, SuccessAudit, and FailureAudit
  • Event ID—An application-specific number that the user can filter in the Event Viewer
  • Category—(Obsolete in .NET 2.0) A means of associating a localized textual description from the message file to the recorded event (.NET 1.1 does not support this feature, so you would need to define and use a message file to realize its benefits. As a result, when using the .NET 1.1 Framework to write events, you can specify only a numeric value and not associate it with a localized textual description. Note the Category column in Figure 1 to see this.)
  • Binary data—(Referred to as "raw data") A value that enables you to record information such as a memory dump with the recorded event. (Figure 2 shows an example of what the user would see in the Event Viewer when binary data is written as part of an event.)

[EventLogReadingAndWriting-2.jpg]

Figure 2: Example of using binary data in an event.

Instead of going through each overload of the WriteEntry method, I list several examples illustrating everything from recording a simple informational event to recording an event with binary data:

EventLog* log = new EventLog(S"My Application Log", S".",
                             S"My Application");

// Text (type defaults to EventLogEntryType::Information)
log->WriteEntry(S"Informational message");

// Text + Type
log->WriteEntry(S"Warning message",
                EventLogEntryType::Warning);

// Text + Type + eventID
Int32 eventId = 100;
log->WriteEntry(S"Message with event id",
                EventLogEntryType::SuccessAudit,
                eventId);

// Text + Type + eventID + category
Int32 catId = 1000;
log->WriteEntry(S"Message with event id and category id",
                EventLogEntryType::SuccessAudit,
                eventId,
                catId);

// Text + Type + eventId + category + binaryData.
Byte binaryData[] = new Byte[4];
for (int i = 0; i < 4; i++)
  binaryData[i] = i;
log->WriteEntry(S"Error with event id, category id and binary data",
                EventLogEntryType::Error, 
                eventId, 
                catId, 
                binaryData);

Note that while I instantiate an EventLog object by specifying the log name, machine name, and event source, I could also set these values through the EventLog object's Log, MachineName, and Source properties, respectively.

Clearing Event Logs

You can programmatically clear an event log via the EventLog::Clear method. Here's an example:

EventLog* log = new EventLog(S"My Application Log");
log->Clear();

Looking Forward

This article illustrated how to enumerate event log entries, read specific event entries, manually filter events based on an event's desired property value, write new event entries, and clear an event log. However, the one thing missing is the ability to write localized event entries; something that's important to many applications in today's distributed computing world. Unfortunately, this is not easily done in version 1.1 of the .NET Framework and must be accomplished via Win32 programming and the Windows Message Compiler. Because this is a .NET series, I intentionally omitted this task. However, the good news is that version 2.0 of the .NET Framework does support writing localized events via the new EventLog::WriteEvent method and the EventSourceCreationData class. A future article will cover how to use these additions to the .NET Framework and how they enable the recording of localized events to an event log.



About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds