Based on my posts the last few days, we now have enough information to put together a spiffy class we can reuse in our projects. First, let me talk about a few design decisions I made.
All of the methods are static. If you are not familiar with what a static method is, see my post https://arcanecode.wordpress.com/2007/01/15/static-methods/ . I did this to make it easy to use the class, and so I wouldn’t have to a) make the class a global object or b) be constantly creating a new instance of the class. The downside is I always have to pass in the log name each time you call a routine.
As an alternative, you could elect to alter the class by making each routine non static, then creating a Log property to set and forget the log name, and finally creating an instance of the class and making it global throughout your app.
Next, in the class you’ll see I kept the line wrap fairly small, this was simply to make it easier to reproduce in the blog. Normally I’d use much longer line lengths.
I like overloads, so I used a lot of them in the class, to make it as flexible as possible. I also commented each routine (although the overloads only get one set of comments since except for a parameter they are identical). OK, let’s look at the main routine.
public static void Log(string LogName, string Source,
string Message, EventLogEntryType EventType, int EventID,
short CategoryID, byte [] RawData)
{
EventLog evt = new EventLog(LogName);
evt.Source = Source;
evt.WriteEntry(Message, EventType, EventID, CategoryID, RawData);
}
If you compare the WriteEntry method here to what I had in my post a few days ago, you’ll notice some extra parameters. In my introduction to WriteEntry I already mentioned Message and EventType, Message being the main message that’s logged and EventType being an enum that indicates if this is an Informational message, Warning, or Error.
EventID is a generic, positive integer that you can use in your program in anyway you see fit. My suggestion would be to create a unique number for each place in your code that you log a message. Then it’s very easy to jump to the exact spot in your code the message was generated.
CategoryID is another number you can use as you please. You can create your own categories for the type of message, or perhaps use the category to indicate which class the message came from. The category ID must be a positive short value.
Note that it is possible to tie the category ID to a string. The string is located in a resource file, compiled in your DLL. I elected not to do that for this simple logger class, however if you want to read more about it take a look at http://msdn2.microsoft.com/en-us/library/system.diagnostics.eventloginstaller.categoryresourcefile.aspx
or http://shrinkster.com/lav .
The final parameter is RawData, which is an array of bytes. You probably won’t use this too often, but if you had a blob of data in memory or from a database you could write it out.
I mentioned I liked overloads, so does the .Net Framework. There are various overloads to WriteEntry, so I created overloads for the Log method to correspond as it dropped parameters. The simplest version of Log only passes Log Name, Source, Message, and EventType as parameters.
To make things a little simpler and more self documenting, I then created LogMessage, LogWarning, and LogError methods. These have the same signature as the Log method, except they don’t have the EventType parameter. Instead they set this internally in the method. They then call the Log method. Here’s one example, the simplest version of LogMessage:
public static void LogMessage(string LogName, string Source,
string Message)
{
Log(LogName, Source, Message, EventLogEntryType.Information);
}
OK, that covers the gambit of routines to log the error. For reading the error I have two versions of a method called ReadLog. The first is almost a clone of the one I blogged about two days ago, except the LogName is passed in as a parameter instead of being hard coded.
For the second version I added a parameter called OldestValue. This parameter indicates the oldest date we want to include in our return string, and goes forward to today from there. Let’s take a look.
public static string ReadLog(string LogName, DateTime OldestValue)
{
StringBuilder returnValue = new StringBuilder();
EventLog el = new EventLog();
el.Log = LogName;
foreach (EventLogEntry myEntry in el.Entries)
{
if (myEntry.TimeGenerated >= OldestValue)
{
returnValue.AppendLine(formatEntry(myEntry));
}
}
return returnValue.ToString();
}
You can see, all I needed was a simple if clause to check to see if I should include this record. You could choose to add a third method to do a date range, I decided not to on the logic that 99% of the time the error probably occurred today, and your oldest value might be yesterday or the day before.
I also made one other enhancement; you can see I have the returnValue append the results of a method called formatEntry. The other ReadLog method also uses this, I wanted to create one spot to format the data we read from the event log.
private static string formatEntry(EventLogEntry myEntry)
{
StringBuilder returnValue = new StringBuilder();
// Detect what type of entry it is
string entryType = “Unknown”;
switch (myEntry.EntryType)
{
case EventLogEntryType.Information:
entryType = “Information”;
break;
case EventLogEntryType.Error:
entryType = “Error”;
break;
case EventLogEntryType.Warning:
entryType = “Warning”;
break;
case EventLogEntryType.SuccessAudit:
entryType = “Success Audit”;
break;
case EventLogEntryType.FailureAudit:
entryType = “Failure Audit”;
break;
}
// Add the various elements together
returnValue.AppendLine(“Source: “ + myEntry.Source);
returnValue.AppendLine(“Entry Type:” + entryType);
returnValue.AppendLine(“Message: “ + myEntry.Message);
returnValue.AppendLine(“Entry Generated at “
+ myEntry.TimeGenerated.ToString());
// .Net reports that EventID is now depricated. So although we
// pass in EventID in the WriteEntry method, to get it out we
// use InstanceID. (You can use EventID, but you get a compiler
// warning message about the depreciation.)
returnValue.AppendLine(“Event ID: “
+ myEntry.InstanceId.ToString());
returnValue.AppendLine(“Category ID: “
+ myEntry.CategoryNumber.ToString());
returnValue.AppendLine(“Machine Name: “ + myEntry.MachineName);
// Append Data, if we have any
if (myEntry.Data.Length > 0)
{
returnValue.Append(“Data: “);
foreach (byte dataByte in myEntry.Data)
{
// Convert each byte into a two character
// hexidecimal string
returnValue.Append(string.Format(“{0:X2} “, dataByte));
}
returnValue.AppendLine(“”); // End the line of the data
}
returnValue.AppendLine(” “); // Blank Line
// All done, return to the calling method
return returnValue.ToString();
}
First I determine what type of event was logged using the switch statement. Next I grab all the important data exposed by the EventLogEntry object. Many you will recognize as the values you passed in from the Log method so I won’t reiterate. A few though are new.
TimeGenerated simply indicates the date/time the item was generated. I could have used the TimeWritten, but felt the TimeGenerated to be the more valuable and accurate of the two.
MachineName is another self evident property, if a user runs your same app on many PCs it can be helpful to know which one the event was generated on.
InstanceID is a bit odd, when we write the data out using WriteEntry .Net had no complaints. However when I tried to read it back in using the EventID property, the compiler produced a warning that EventID has been depreciated and replaced with InstanceID. The short story, EventID and InstanceID are the same thing, for reading back use InstanceID.
Finally, the logger class has three more methods: Clear, SetToOverwriteAsNeeded, and SetLogSizeInKB. These are identical to the routines I showed you in yesterdays post, except I pass in the log name as a parameter.
Well, that was quite a bit of work, but now we have a class that will make it easy to write and read events to the Windows Event Log. Of course I can already think of new routines and methods, perhaps a ReadLogAsXML? Or the previously mentioned ability to read a date range instead of from a single date. Sounds like good homework!
Meanwhile, I have uploaded the class in plain text at EventLogger.cs.txt, just remove the .txt when you save it (I added the .txt so you could view in your browser). For those who’d like an entire test app, I’ve zipped up the project, just shoot me an e-mail at arcanecode (at) gmail.com (since wordpress won’t let me upload zips). It comes with a form that tests all the features of the event logger class.
OK, no more excuses, go start logging today!
Please send me zip file
thanks,