Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Event Management/Logging with Publisher Subscriber Pattern

4.71/5 (4 votes)
21 Oct 20059 min read 1   574  
Explanation of an Event Management / Logging component based on Publisher Subscriber pattern.

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:

  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 way 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.

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.

XML
<!--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 --> 

<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 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:

VB
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 InmyManagerSettings.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:

VB
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.

VB
Public Sub HandleEvent(ByRef anEvent As IEvent, _
       ByRef source As Object) Implements _
       CSI.Common.Framework.EventManagement.IEventManager.HandleEvent 
  'Now Call allthe Broadcasters and tell them about the event 
  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 
      '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 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.

VB
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:

VB
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. Good luck and if you have any further questions feel free to contact me at brianmrush@yahoo.com.

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here