What are best practices for event id management?
Asked Answered
T

3

17

I'm trying to figure out how to manage my event ids. Up to this point I've been putting each event id in each method manually with each step in a method numbered sequentially. This doesn't allow me to effectively filter events in the event log. In order to use the filter in the event log, it seems that every logged event must have its own unique id.

I could store them all in a table with the description linked to them, but then as my code executes, I'm logging "magic" meaningless event codes.

I did a Google search, but I appear to be at a loss as to the correct keywords to use to get to the bottom of this issue.

Thanks in advance

Tripos answered 28/10, 2009 at 14:6 Comment(3)
Never used event ids when logging to the event log. I just put in enough information about what was happening at that point and let her rip...Flattery
@Will - So how do you filter your event log when you're trying to find relevant items? For instance, if I have a component that is currently logging verbosely but I'm after a specific event - the only way I can filter on that is filtering on the event id.Tripos
@Will - the idea here is to evaluate the frequency of occurrence of a particular event without having to dig through the log item by item to find the relevant ones.Tripos
A
7

First thought - and I haven't entirely thought this through yet, but it seems like a reasonable possibility:

public class LogEvent
{
    /* This is the event code you reference from your code 
     * so you're not working with magic numbers.  It will work
     * much like an enum */
    public string Code; 

    /* This is the event id that's published to the event log
     * to allow simple filtering for specific events */
    public int Id; 

    /* This is a predefined string format that allows insertion
     * of variables so you can have a descriptive text template. */
    public string DisplayFormat;

    /* A constructor to allow you to add items to a collection in
     * a single line of code */
    public LogEvent(int id, string code, string displayFormat)
    {
        Code = code;
        Id = id;
        DisplayFormat = displayFormat;
    }
    public LogEvent(int id, string code)
        : this(id, code, null)
    {
    }
    public LogEvent()
    {
    }
}

You can then have an event manager class that wraps your list of events providing a method that queries the list according to the parameter you pass - for instance:

public class EventManager
{
    private List<LogEvent> _eventList;
    public LogEvent this[string eventCode]
    {
        get
        {
            return _eventList.Where(i => i.Code.Equals(eventCode)).SingleOrDefault();
        }
    }
    public LogEvent this[int id]
    {
        get
        {
            return _eventList.Where(i => i.Id.Equals(id)).SingleOrDefault();
        }
    }
    public void AddRange(params LogEvent[] logEvents)
    {
        Array.ForEach(logEvents, AddEvent);
    }
    public void Add(int id, string code)
    {
        AddEvent(new LogEvent(id, code));
    }
    public void Add(int id, string code, string displayFormat)
    {
        AddEvent(new LogEvent(id, code, displayFormat));
    }
    public void Add(LogEvent logEvent)
    {
        _events.Add(logEvent);
    }
    public void Remove(int id)
    {
        _eventList.Remove(_eventList.Where(i => i.id.Equals(id)).SingleOrDefault());
    }
    public void Remove(string code)
    {
        _eventList.Remove(_eventList.Where(i => i.Code.Equals(code)).SingleOrDefault());
    }
    public void Remove(LogEvent logEvent)
    {
        _eventList.Remove(logEvent);
    }
}

This allows simplified management of event definitions which can be managed independently for each TraceSource.

var Events = new EventManager();
Events.AddRange(
    new LogEvent(1, "BuildingCommandObject", "Building command object from {0}."),
    new LogEvent(2, "CommandObjectBuilt", "Command object built successfully."),
    new LogEvent(3, "ConnectingToDatabase", "Connecting to {0}."),
    new LogEvent(4, "ExecutingCommand", "Executing command against database {0}".),
    new LogEvent(5, "CommandExecuted", "Command executed succesfully."),
    new LogEvent(6, "DisconnectingFromDatabase", "Disconnecting from {0}."),
    new LogEvent(7, "Disconnected", "Connection terminated.")
)

And you can access the events using the meaningful identifier you assigned:

var evt = Events["ConnectingToDatabase"];
TraceSource.TraceEvent(TraceEventType.Information, evt.Id, evt.DisplayFormat, otherParams);

or

var evt = Events[1024];
Console.WriteLine("Id: {1}{0}Code: {2}{0}DisplayFormat{3}", 
    Environment.NewLine, evt.Id, evt.Code, evt.DisplayFormat);

This would probably simplify your event management, you're no longer calling your events by magic numbers, it's simple to manage all your events in one place - your EventManager class and you can still filter your event log by the magic numbers it requires you to filter by.

Arbil answered 28/10, 2009 at 18:42 Comment(2)
"And you can access the events using a meaningful identifier while still passing the" - passing the what?Tripos
I would refactor _eventList to use ImmutableDictionary. This would eliminate the need to use a slow LINQ query. It may not seem slow, but its computational complexity is O(n), whereas ImmutableDictionary guarantees O(1) read access. Further, I don't see a use case to remove events. Allowing removal of events will create orphaned event log records with no key. Don't call AddRange; just pass in the dictionary. Further, the Formatter should be a first-class generic Formatter<T0>(string format) rather than a string, so its clear to the consumer what argument type to pass into the formatter.Typescript
H
11

Like Ben's suggestion, it's probably worth using a level of indirection - but instead of using an int for the code, I'd use an actual enum, so for Ben's example:

public enum EventId
{
    [Format("Building command object from {0}.")]
    BuildingCommandObject = 1,
    [Format("Command object build successfully.")]
    CommandObjectBuilt = 2,
    [Format("Connecting to {0}.")]
    ConnectingToDatabase = 3,
    [Format("Executing command against database {0}.")]
    ExecutingCommand = 4,
    [Format("Command executed successfully.")]
    CommandExecuted = 5,
    [Format("Disconnecting from {0}.")]
    DisconnectingFromDatabase = 6,
    [Format("Connection terminated")]
    Disconnected = 7
}

Or alternatively (and in a more object-oriented fashion) use the "smart enum" pattern):

public class LogEvent
{
    public static readonly LogEvent BuildingCommandObject = new LogEvent(1,
         "Building command object from {0}");
    // etc

    private readonly int id;
    private readonly string format;

    // Add the description if you want
    private LogEvent(int id, string format)
    {
        this.id = id;
        this.format = format;
    }

    public void Log(params object[] data)
    {
        string message = string.Format(format, data);
        // Do the logging here
    }
}

You can then call:

LogEvent.BuildingCommandObject.Log("stuff");

With a bit of work you may be able to expose this in a safe way with different log events having a different interface to make it safe (at compile-time) in terms of how many parameters each one has. In fact I'm sure you could do it using interfaces and a private nested class, but it's 4am and I'm too tired to write it out atm :)

Harmaning answered 29/10, 2009 at 4:7 Comment(0)
A
7

First thought - and I haven't entirely thought this through yet, but it seems like a reasonable possibility:

public class LogEvent
{
    /* This is the event code you reference from your code 
     * so you're not working with magic numbers.  It will work
     * much like an enum */
    public string Code; 

    /* This is the event id that's published to the event log
     * to allow simple filtering for specific events */
    public int Id; 

    /* This is a predefined string format that allows insertion
     * of variables so you can have a descriptive text template. */
    public string DisplayFormat;

    /* A constructor to allow you to add items to a collection in
     * a single line of code */
    public LogEvent(int id, string code, string displayFormat)
    {
        Code = code;
        Id = id;
        DisplayFormat = displayFormat;
    }
    public LogEvent(int id, string code)
        : this(id, code, null)
    {
    }
    public LogEvent()
    {
    }
}

You can then have an event manager class that wraps your list of events providing a method that queries the list according to the parameter you pass - for instance:

public class EventManager
{
    private List<LogEvent> _eventList;
    public LogEvent this[string eventCode]
    {
        get
        {
            return _eventList.Where(i => i.Code.Equals(eventCode)).SingleOrDefault();
        }
    }
    public LogEvent this[int id]
    {
        get
        {
            return _eventList.Where(i => i.Id.Equals(id)).SingleOrDefault();
        }
    }
    public void AddRange(params LogEvent[] logEvents)
    {
        Array.ForEach(logEvents, AddEvent);
    }
    public void Add(int id, string code)
    {
        AddEvent(new LogEvent(id, code));
    }
    public void Add(int id, string code, string displayFormat)
    {
        AddEvent(new LogEvent(id, code, displayFormat));
    }
    public void Add(LogEvent logEvent)
    {
        _events.Add(logEvent);
    }
    public void Remove(int id)
    {
        _eventList.Remove(_eventList.Where(i => i.id.Equals(id)).SingleOrDefault());
    }
    public void Remove(string code)
    {
        _eventList.Remove(_eventList.Where(i => i.Code.Equals(code)).SingleOrDefault());
    }
    public void Remove(LogEvent logEvent)
    {
        _eventList.Remove(logEvent);
    }
}

This allows simplified management of event definitions which can be managed independently for each TraceSource.

var Events = new EventManager();
Events.AddRange(
    new LogEvent(1, "BuildingCommandObject", "Building command object from {0}."),
    new LogEvent(2, "CommandObjectBuilt", "Command object built successfully."),
    new LogEvent(3, "ConnectingToDatabase", "Connecting to {0}."),
    new LogEvent(4, "ExecutingCommand", "Executing command against database {0}".),
    new LogEvent(5, "CommandExecuted", "Command executed succesfully."),
    new LogEvent(6, "DisconnectingFromDatabase", "Disconnecting from {0}."),
    new LogEvent(7, "Disconnected", "Connection terminated.")
)

And you can access the events using the meaningful identifier you assigned:

var evt = Events["ConnectingToDatabase"];
TraceSource.TraceEvent(TraceEventType.Information, evt.Id, evt.DisplayFormat, otherParams);

or

var evt = Events[1024];
Console.WriteLine("Id: {1}{0}Code: {2}{0}DisplayFormat{3}", 
    Environment.NewLine, evt.Id, evt.Code, evt.DisplayFormat);

This would probably simplify your event management, you're no longer calling your events by magic numbers, it's simple to manage all your events in one place - your EventManager class and you can still filter your event log by the magic numbers it requires you to filter by.

Arbil answered 28/10, 2009 at 18:42 Comment(2)
"And you can access the events using a meaningful identifier while still passing the" - passing the what?Tripos
I would refactor _eventList to use ImmutableDictionary. This would eliminate the need to use a slow LINQ query. It may not seem slow, but its computational complexity is O(n), whereas ImmutableDictionary guarantees O(1) read access. Further, I don't see a use case to remove events. Allowing removal of events will create orphaned event log records with no key. Don't call AddRange; just pass in the dictionary. Further, the Formatter should be a first-class generic Formatter<T0>(string format) rather than a string, so its clear to the consumer what argument type to pass into the formatter.Typescript
N
0

I know this is an old question, but perhaps you were looking for a way to do something like this, where you use custom Event IDs for different purposes, and call them in their appropriate places in your code:

public class ErrorLog
{
    //Notifications
    public const int NOTIFY_ALPHA = 2000;
    public const int NOTIFY_BETA = 2001;
    public const int NOTIFY_GAMMA = 2002;

    public static string[] errMessage = 
        {"Critical Error.",           //2000
         "File not found.",          //2001
         "Unknown Event Action Encountered - "     //2002
        };

    public static string GetErrMsg(int errNum)
    {
        return (errMessage[errNum-2000]);
    }

    private static bool ErrorRoutine(int eventLogId)
    {
        try
        {
            string eventAppName = "My Application";
            string eventLogName = "My Apps Events";
            string msgStr = GetErrMsg(eventLogId);  // gets the message associated with the ID from the constant you passed in

            if (!EventLog.SourceExists(eventAppName))
                EventLog.CreateEventSource(eventAppName, eventLogName);

            EventLog.WriteEntry(eventAppName, msgStr, EventLogEntryType.Error, eventLogId);

            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }
}

And then you'd call this class like this, when you threw your exception:

ErrorLog.ErrorRoutine(ErrorLog.NOTIFY_ALPHA);

As far as best practices go, I would say it is good to have all error handling in its own class if it will be as custom (or more so, such as when tying in warnings and information EventLogEntryTypes or additional info than the canned messages give) than this one. Having individual IDs you can look up to their messages like this will make your life easier when trying to sort out what message to call, when, and where.

Nabila answered 9/4, 2015 at 2:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.