Introduction
Chances are you have come across a need within one of your applications to log events. This may stem from the need to track long running jobs or to log debugging type information. Whatever the case maybe, this is a fairly common need within an application.
Well, I too have had this need so I built a small event management/logging component that could easily be tailored to fulfill various needs related to event management and logging.
In this article I explain the event management component I created. My event management component can either do or exposes the hooks to do:
- Log events asynchronously to various types of event logs.
- Tracking long running processes or debugging type information.
- Log events to either a file, database or some in memory object implemented within the application.
- Log events to different event logs. For example, security events can get logged to a file, while other informational type events can get logged directly into something like the Windows application event log.
- Turn event logging on and off. Moreover, fine tune the way events are logged. For example, during development, only debug type events are logged but during deployment they are not.
- “Listen in” on events being raised by an application.
- Customize the type of events logged within each event log. For example one event log may only care about security events while another event log may care about both debugging and security events.
- Filter event logs by the types of events logged. For example, filter on all error events.
If you need any of this functionality, you may be able to use my component or at the very least use some of the concepts I present here.
So then, how does this whole event management component work? Prior to getting into the details let me give you a brief overview.
Overview
Within an application I have the concept of a global event manager. The application is responsible for informing the global event manager any time a notable event occurs. In short, the application kind of “throws” events and lets the event manager handle the rest. This is accomplished through a simple static method called HandleEvent()
. A parameter in the method HandleEvent
is an interface called IEvent
. Within an application you can implement your own version(s) of IEvent
. This interface defines information such as where an event occurred, the name of the event, the time of the event, any associated message etc. The thinking was an application can have numerous custom implementations of IEvent
, each of which can be raised whenever and wherever applicable.
The event manager is initially loaded with some default subscribers defined within the app.config file. The event manager will notify any subscribers when an event has been raised by the application. Each subscriber then in turn determines how and where the events are logged. Each subscriber can be configured to handle all events or some specific set of events. Likewise, a subscriber can be configured to be either on or off in the sense it is doing some logging. Again, the app.config is the place where these settings are specified.
Last, the event manager exposes methods AddSubscriber
and RemoveSubscriber
. These methods allow for adding and removing additional subscribers at runtime. This works great in cases like long running jobs. For example, during a long running job, methods can raise events at specific check points allowing for any registered subscriber to receive these notifications and relay pertinent status information to an end user.
Noteworthy at this point is the fact that the event management component is based on the publisher-subscriber pattern (design patterns is another large topic way outside the scope of this article).
Prior to getting into the finer details of each class within the component, let me mention a few key concepts used within the context of the component.
Reflection
Reflection is used at application startup time in order to load the correct instance of the event manager along with the default subscribers defined within the app.config. The app.config defines the type of the classes for the IEventManager
, IEventLogger
(s), and IEvent
(s) (see below).
Threads
Threads are used to invoke long running jobs asynchronously in the sample application. More importantly, threads are used to notify subscribers when an event has been raised through the event manager.
Delegates
A delegate is a special type of managed class that allows you to work with type-safe function pointers. Each delegate type is based on a single method signature. In the event management component, each event “logger” or subscriber class implements a specific function called OnReceiveEvent
. This method is called by the event manager for each registered subscriber every time an event is “thrown” to the event manager.
Factory Pattern
The Factory pattern is used at startup time along with reflection in order to create the correct instance of the event manager being implemented within the application. The EventManagerSettingsHandler
is responsible for loading the event manager with all the correct cached objects.
Finally, let me also mention that a fully functional sample is available for download in which you can explore all the gory details at your “leisure”.
OK, now that we have defined the event management component and the key concepts used, let’s describe the major classes/interfaces and the role they play. After which time, we will get into what we all know and love, the .NET code.
Classes and Interfaces
IEventManager
This is the interface that defines the global event manager that will receive all event notifications from the application. The implementation of this interface is the class responsible for receiving events raised by the application. Likewise it is responsible for notifying any registered subscribers.
IEventLogger
This is the interface that defines a subscriber to the event manager. I use the word subscriber and logger interchangeably because they are really one and the same. From one perspective, an implementation of IEventLogger
subscribes to events raised to the event manager, while from another, they handle the physical logging of the events. Each implementation of IEventLogger
will define how it should handle the events received from the IEventManager
. For example, one logger may write to a file while another may write to the application event log.
IEvent
This is the interface that defines the events raised within an application. A user can choose to implement their application specific versions of IEvent
.
EventManagementSettingsHandler
This is the class responsible for reading the application configuration file and instantiating the corresponding class type of IEventManager
and IEventLogger
(s) respectively. In the sample code, the actual implementation of IEventManager
is the class ApplicationEventManager
. Note, the scope of this article does not cover reading custom sections in the app.config so suffice to say EventManagementSettingsHandler
contains all the plumbing that makes it possible to read the section <EventManagment>
in the app.config. There are some other helper classes involved but I don’t cover them in this article.
EventManagementFactory
This is the class that contains a static method called getEventManager
. This method provides access to the event manager created by the EvenManagementSettingsHandler
. Note, once the event manager is created it is cached within the factory. As a result, the application always calls getEventManger
to get a handle on the application event manager.
The Code
With all this background covered, let’s now roll up our sleeves and look at the code. The first item we need to examine is a node from the app.config.
<EventManagement>
<daEventManager xmlns="CSI.Common.Framework.EventManagement.EventManager"
name="MyEventManager"
type="CSI.Common.Framework.EventManagement.ApplicationEventManager"
mode="on">
<daEventLogger name="ApplicationLogger" mode="on"
type="CSI.Common.Framework.EventManagement.MessageQueueLogger">
<daEvent name="ApplicationEvents" mode="on"
type="CSI.Common.Framework.EventManagement.ApplicationEvent" />
</daEventLogger>
</daEventManager>
</EventManagement>
As I stated earlier, the EventManagementSettingsHandler
is responsible for reading this section of the app.config in order to create the global instance of the event manager. In the above node, the attribute type of the <daEventManager>
tag defines .Common.Framework.EventManagement.ApplicationEventManager
. This is the class type of the event manager that will be created for the application. Once this is created, the next step is to create all the subscribers. For each <daEventLogger>
under the <daEventManager>
tag, the corresponding subscriber will be created and registered with the ApplicationEventManager
. Note, the first <daEventLogger>
defines the type CSI.Common.Framework.EventManagement.MessageQueueLogger
. As a result, our event manager will have at least one subscriber of type MessageQueueLogger
. The last notable item about the node is each subscriber will handle all the events defined within the <daEvent>
tags. If no <daEvents>
tags are defined, then that corresponding subscriber/logger will handle all the events.
The code to make all this happen is in the class EventManagerFactory.GetEventManger
. In particular, note the section below:
If _eventManager Is Nothing Then
myManagerSettings = _
CType(ConfigurationSettings.GetConfig("EventManagement" & _
"/daEventManager"), daEventManager)
myEventManager = _
Activator.CreateInstance(Type.GetType(myManagerSettings.type))
If myManagerSettings.daEventLogger Is Nothing = False Then
For Each myLoggerSettings InmyManagerSettings.daEventLogger
If myLoggerSettings.mode = "on" Then
myLogger = _
Activator.CreateInstance(Type.GetType(myLoggerSettings.type))
myLogger.LoggerName= myLoggerSettings.name
myEventManager.AddSubscriber(myLogger)
If myLoggerSettings.daEvent Is Nothing = False Then
For Each myEventSettings In myLoggerSettings.daEvent
If myEventSettings.mode = "on" Then
myEvent = _
Activator.CreateInstance(Type.GetType(myEventSettings.type))
myLogger.AddHandledEvent(myEvent)
End If
Next
End If
End If
Next
End If
_eventManager = myEventManager
End If
Now that we have our event manager loaded, let’s examine how we raise events and what happens when an event is raised. In order to raise an event, we would invoke something like the following in our application:
CSI.Common.Framework.EventManagement.EventManagerFactory._
GetEventManager().HandleEvent(New _
CSI.Common.Framework.EventManagement.ApplicationEvent("Start LongEvent", _
Me, "Start Long Event", _
CSI.Common.Framework.EventManagement.EventTypes.Information), Me)
This code will create a new application event and it will be sent to our event manager. What the event manager does next is broadcast this event to any subscribers listening.
Let’s examine the code for two important methods within the ApplicationEvent
and then describe what they do.
Public Sub HandleEvent(ByRef anEvent As IEvent, _
ByRef source As Object) Implements _
CSI.Common.Framework.EventManagement.IEventManager.HandleEvent
OnReceiveEvent(source, anEvent)
End Sub
Public Sub OnReceiveEvent(ByRef sender As Object, ByRef evt AsEventArgs)
Dim targets() As System.Delegate
SyncLock Me
Try
targets = Me.EventReceivedEvent.GetInvocationList()
Catch ex As Exception
Exit Sub
End Try
End SyncLock
If targets.Length > 0 Then
Dim listener As _
CSI.Common.Framework.EventManagement.IEventManager.EventReceivedEventHandler
Dim ar As IAsyncResult
For Each listener Intargets
ar =listener.BeginInvoke(sender, _
CType(evt,System.EventArgs), Nothing, Nothing)
Next
End If
End Sub
As we can see, HandleEvent
calls OnReceiveEvent
. What OnReceiveEvent
does is iterate through all the delegate references to the event EventReceived
declared in IEventManager
and calls the corresponding function OnReceiveEvent
within each one of the registered subscribers. Remember, a delegate is a special type of managed class that allows you to work with type-safe function pointers. We registered all these pointers to OnReceiveEvent
in our subscriber objects through the method AddSubcriber
.
Public Sub AddSubscriber(ByRef aSubscriber As IEventLogger)_
Implements IEventManager.AddSubscriber
Try
If Me._subscribers.Contains(aSubscriber) Then
Exit Sub
End If
Me._subscribers.Add(aSubscriber.LoggerName, aSubscriber)
AddHandler Me.EventReceived, _
AddressOf aSubscriber.OnReceiveEvent
Catch ex As Exception
End Try
End Sub
One thing worth mentioning is the SyncLock
in the OnReceiveEvent
method. What this does is lock the event manager so that we are not caught in the middle of getting all the registered subscribers while some other portion of the application is adding another subscriber. This way we will be sure all subscribers at the time an event is raised gets notified.
The last idea to discuss is what happens when our event manager calls the OnReceiveEvent
function in a subscriber object. The cool thing is this is really up to each individual implementation. If we look at class MessageQueueLogger
, one default implementation in my sample, we see:
Public Overrides Sub OnReceiveEvent(ByRef sender _
As Object, ByRef evt As System.EventArgs)
Try
If Me._handledEvents.Count = 0 Then
Me._eventLog.Enqueue(evt)
Else
If Me._handledEvents.ContainsValue(evt.GetType) Then
Me._eventLog.Enqueue(evt)
End If
End If
Catch ex As Exception
Throw ex
End Try
End Sub
Within this specific implementation, we first check if the subscriber is set up to handle the event raised. If it is, then we put the event in our message queue. Other implementations may take this event and put it right in the Windows event log.
At this point if you feel this event management component could be used / tailored for your application then I think the best thing to do is download and step through the sample source code. The sample code is stripped down for example purposes. I recommend following Sub Button1_Clic
k within form GenericForm
. This form was set up to be a subscriber to the event manager. It will also show how to add a subscriber, handle events, and remove subscribers from the event manager.
That’s it. Note the sample code is scaled down for the purposes of this article but it is fully functional. Good luck and if you have any further questions feel free to contact me at brianmrush@yahoo.com.
Enjoy!