Event Managment/Logging through Publisher/Subscriber Pattern
by Brian Rush

  Download Source Code
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 may, 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:

1.  Log events asynchronously to various types of event logs.
2.  Tracking long running processes or debugging type information.
3.  Log events to either a file, database or some in memory object implemented within the application.
4.  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.
5.  Turn event logging on and off.  Moreover, fine tune the type events are logged. For example, during development, only debug type events are logged but during deployment they are not.
6.  "Listen in" on events being raised by an application.
7.  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.
8.  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.
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 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 can then receive these notifications and relay pertinent status information to an end user.
Noteworthy at this point is 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 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, .net code.
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 interchangeable 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 section in 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.
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:

<!--Event Management Section for Logging Will  create the correct Event Managemer object
 through reflection and set up all the default subscribers to the Event Management -->
lt;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">
	<!--Since Events are defined then only accept these type of events-->
	  <daEvent name="ApplicationEvents" mode="on"
                      type="CSI.Common.Framework.EventManagement.ApplicationEvent" />
	   </daEventLogger>
	  <!-->   Security Logger to only Receive Security Events.  Note this is not implemented
	<daEventLogger name="SecurityLogger" mode="on" type="SecurityLogger">
		<daEvent name="SecurityEvents" mode="off" type="SecurityEvent"/>
	</daEventLogger>
          Could Implement your own file Logger.  Note, not implement only there for sample purposes 
	  <daEventLogger name="FileLogger" mode="on" 
                   type="CSI.Common.Framework.EventManagement.FileLogger">
		All Events Logged when no events defined
	  </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 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 events.
The code to make all this happen is in class EventManagerFactory.GetEventManger.  In particular, note the section below:
If _eventManager Is Nothing Then
 	myManagerSettings = CType(ConfigurationSettings.GetConfig("EventManagement/daEventManager"),
                         daEventManager)
                'First we load the correct event manager 
                myEventManager = Activator.CreateInstance(Type.GetType(myManagerSettings.type))
                'Next we load up any listeners
                If myManagerSettings.daEventLogger Is Nothing = False Then
                    For Each myLoggerSettings In myManagerSettings.daEventLogger
                        If myLoggerSettings.mode = "on" Then
                            myLogger = Activator.CreateInstance(Type.GetType(myLoggerSettings.type))
                            myLogger.LoggerName = myLoggerSettings.name
                            myEventManager.AddSubscriber(myLogger)
                            'For Each Logger Add the Specific Events that it listens to
                            'If none defined then it will listen to all event types passed in 
                            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 Long Event", 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 2 important methods within 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
        'Now Call all the Broadcasters and tell them about the event
        OnReceiveEvent(source, anEvent)
    End Sub

Public Sub OnReceiveEvent(ByRef sender As Object, ByRef evt As EventArgs)
        Dim targets() As System.Delegate
        SyncLock Me
            Try
                targets = Me.EventReceivedEvent.GetInvocationList()
            Catch ex As Exception
                'No listeners/subscribers
                Exit Sub
            End Try
        End SyncLock
        'Call all broadcasters asychronously
        If targets.Length > 0 Then
            Dim listener As CSI.Common.Framework.EventManagement.IEventManager.EventReceivedEventHandler
            Dim ar As IAsyncResult
            For Each listener In targets
                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
            'Do nothing if already in the collection
            'Throw ex
        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_Click 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.