Question

I'm using the Windows Event Log to record some events. Events within the Windows Event Log can be assigned a handful of properties. One of which, is an EventID.

Now I want to use the EventId to try and group related errors. I could just pick a number for each call to the logging method I do, but that seems a little tedious.

I want the system to do this automatically. It would choose an eventId that is "unique" to the position in the code where the logging event occurred. Now, there's only 65536 unique event IDs, so there are likely to be collisions but they should be rare enough to make the EventId a useful way to group errors.

One strategy would be to take the hashcode of the stacktrace but that would mean that the first and second calls in the following code would have generate the same event ID.

public void TestLog()
{
   LogSomething("Moo");
   // Do some stuff and then a 100 lines later..
   LogSomething("Moo");
}

I thought of walking up the call stack using the StackFrame class which has a GetFileLineNumber method. The problem with this strategy is that it will only work when built with debug symbols on. I need it to work in production code too.

Does anyone have any ideas?

Was it helpful?

Solution

The IL offset number is available without debug symbols. Combined with the stack information and hashed, I think that would do the trick.

Here's an article that, in part, covers retrieving the IL offset (for the purpose of logging it for an offline match to PDB files--different problem but I think it'll show you what you need):

http://timstall.dotnetdevelopersjournal.com/getting_file_and_line_numbers_without_deploying_the_pdb_file.htm

OTHER TIPS

Here is some code you can use to generate an EventID with the properties I describe in my question:

 public static int GenerateEventId()
 {
     StackTrace trace = new StackTrace();

     StringBuilder builder = new StringBuilder();
     builder.Append(Environment.StackTrace);

     foreach (StackFrame frame in trace.GetFrames())
     {
           builder.Append(frame.GetILOffset());
           builder.Append(",");
     }

     return builder.ToString().GetHashCode() & 0xFFFF;
 }

The frame.GetILOffset() method call gives the position within that particular frame at the time of execution.

I concatenate these offsets with the entire stacktrace to give a unique string for the current position within the program.

Finally, since there are only 65536 unique event IDs I logical AND the hashcode against 0xFFFF to extract least significant 16-bits. This value then becomes the EventId.

Create a hash using the ILOffset of the last but one stack frame instead of the line number (i.e. the stack frame of your TestLog method above).

*Important: This post focuses at solving the root cause of what it appears your problem is instead of providing a solution you specifically asked for. I realize this post is old, but felt it important to contribute. *

My team had a similar issue, and we changed the way we managed our logging which has reduced production support and bug patching times significantly. Pragmatically this works in most enterprise apps my team works on:

  1. Prefix log messages with the "class name"."function name".
  2. For true errors, output the captured Exception to the event logger.
  3. Focus on having clear messages as part of the peer code review as opposed to event id's.
  4. Use a unique event id for each function, just go top to bottom and key them.
  5. when it becomes impractical to code each function a different event ID, each class should just just have a unique one (collisions be damned).
  6. Utilize Event categories to reduce event id reliance when filtering the log

Of course it matters how big your apps are and how sensitive the data is. Most of ours are around 10k to 500k lines of code with minimally sensitive information. It may feel oversimplified, but from a KISS standpoint it pragmatically works.

That being said, using an abstract Event Log class to simplify the process makes it easy to utilize, although cleanup my be unpleasant. For Example:

MyClass.cs (using the wrapper)

class MyClass
{
    // hardcoded, but should be from configuration vars
    private string AppName = "MyApp";
    private string AppVersion = "1.0.0.0";
    private string ClassName = "MyClass";
    private string LogName = "MyApp Log";

    EventLogAdapter oEventLogAdapter;
    EventLogEntryType oEventLogEntryType;

    public MyClass(){
        this.oEventLogAdapter = new EventLogAdapter(
              this.AppName
            , this.LogName
            , this.AppName
            , this.AppVersion
            , this.ClassName
        );
    }

    private bool MyFunction() {
        bool result = false;
        this.oEventLogAdapter.SetMethodInformation("MyFunction", 100);
        try {
            // do stuff
            this.oEventLogAdapter.WriteEntry("Something important found out...", EventLogEntryType.Information);

        } catch (Exception oException) {
            this.oEventLogAdapter.WriteEntry("Error: " + oException.ToString(), EventLogEntryType.Error);
        }
        return result;
    }
}

EventLogAdapter.cs

class EventLogAdapter
{
    //vars
    private string _EventProgram = "";
    private string _EventSource = "";
    private string _ProgramName = "";
    private string _ProgramVersion = "";
    private string _EventClass = "";
    private string _EventMethod = "";
    private int _EventCode = 1;
    private bool _Initialized = false;
    private System.Diagnostics.EventLog oEventLog = new EventLog();
    // methods
    public EventLogAdapter() {  }
    public EventLogAdapter(
          string EventProgram
        , string EventSource
        , string ProgramName
        , string ProgramVersion
        , string EventClass
    ) {
        this.SetEventProgram(EventProgram);
        this.SetEventSource(EventSource);
        this.SetProgramName(ProgramName);
        this.SetProgramVersion(ProgramVersion);
        this.SetEventClass(EventClass);
        this.InitializeEventLog();
    }
    public void InitializeEventLog() {
        try {
            if(
                !String.IsNullOrEmpty(this._EventSource)
                && !String.IsNullOrEmpty(this._EventProgram)
            ){
                if (!System.Diagnostics.EventLog.SourceExists(this._EventSource)) {
                    System.Diagnostics.EventLog.CreateEventSource(
                        this._EventSource
                        , this._EventProgram
                    );
                }
                this.oEventLog.Source = this._EventSource;
                this.oEventLog.Log = this._EventProgram;
                this._Initialized = true;
            }
        } catch { }
    }
    public void WriteEntry(string Message, System.Diagnostics.EventLogEntryType EventEntryType) {
        try {
            string _message = 
                "[" + this._ProgramName + " " + this._ProgramVersion + "]"
                + "." + this._EventClass + "." + this._EventMethod + "():\n"
                + Message;
            this.oEventLog.WriteEntry(
                  Message
                , EventEntryType
                , this._EventCode
            );
        } catch { }
    }
    public void SetMethodInformation(
        string EventMethod
        ,int EventCode
    ) {
        this.SetEventMethod(EventMethod);
        this.SetEventCode(EventCode);
    }
    public string GetEventProgram() { return this._EventProgram; }
    public string GetEventSource() { return this._EventSource; }
    public string GetProgramName() { return this._ProgramName; }
    public string GetProgramVersion() { return this._ProgramVersion; }
    public string GetEventClass() { return this._EventClass; }
    public string GetEventMethod() { return this._EventMethod; }
    public int GetEventCode() { return this._EventCode; }
    public void SetEventProgram(string EventProgram) { this._EventProgram = EventProgram; }
    public void SetEventSource(string EventSource) { this._EventSource = EventSource; }
    public void SetProgramName(string ProgramName) { this._ProgramName = ProgramName; }
    public void SetProgramVersion(string ProgramVersion) { this._ProgramVersion = ProgramVersion; }
    public void SetEventClass(string EventClass) { this._EventClass = EventClass; }
    public void SetEventMethod(string EventMethod) { this._EventMethod = EventMethod; }
    public void SetEventCode(int EventCode) { this._EventCode = EventCode; }

}

Thanks for the idea of hashing the call stack, I was going to ask that very same question of how to pick an eventId.

I recommend putting a static variable in LogSomething that increments each time it is called.

Now I want to use the EventId to try and group related errors.

You have filters in event viewer so why (Go to find ? You have 65536 unique event IDs too.

Or rather use log4net or something ??

just my ideas....

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top