October 11, 2018

ASP.NET Health Monitoring with Custom Events

In the last post we have created a custom provider to send log data to WCF service client using ASP.NET Health Monitoring feature. We have seen that there are multiple events which we can map to the provider in order to log information about that events. Just to recap here is the list of default events available by ASP.Net Health Monitoring feature.

  • All Events
  • Heartbeats
  • Application Lifetime Events
  • Request Processing Events
  • Infrastructure Errors
  • Request Processing Errors
  • All Audits
  • Failure Audits
  • Success Audits

These events could provide plenty of useful information which can help us to analyze application state if there comes any problem while running in production environment. But there may be the case where you might want to log your own custom event. For example, I want to log a hit event for specific page using this feature rather than use some other logging library or write my own, which further may require extra configuration steps or wrapper classes. Writing your own custom event helps you to log event information with ASP.Net Health Monitoring feature and saves you from that extra effort.

Lets start writing the custom event.

First we have to inherit base class WebRequestEvent found in the namespace System.Web.Management. While calling the base constructor we have to provide eventCode. Note that for custom events we have available event codes starting from 100000. You may find full list of event codes defined as constant fields in WebEventCodes sealed class. The Last EventCode constant defined is:

public const int WebExtendedBase = 100000;

For custom events we have to use codes starting from this number. In this exmaple I am using constant event code variable by adding to 10 to the WebExtendedBase event code value, as:

private const int EVENT_CODE = System.Web.Management.WebEventCodes.WebExtendedBase + 10;

10 is just an arbitrary value, you can pick any number.

If you want to add extra information then you have to write an override of Raise() function, which is not necessary, but in this example I am writing this override to add my custom message, in a class level private variable I am using this variable to add information about current UserId from session variable if present:

private string defaultLocalMessage = "";

Which I want to log alongwith other fields. But this variable could not be directly accessible from WebBaseEvent object's properties which we see in last example, in ProcessEvent() method of custom provider. If we can not directly access additional variables then what is the benefit of using this variable? Well, There is a workaround!

Any additional information holding by custom variables, can be used in another overriden function FormatCustomEventDetails. Although this function returns void, it will not directly return any value but this function is interally called when the provider invokes one of the ToString() methods. So, finally when you call ToString() function for WebBaseEvent object in the function ProcessEvent(), you will get that additional information that you holded in local variables and added to the WebEventFormatter object in another overriden method FormatCustomEventDetails(). When you see the code you will get it more clear.

Here is the complete code listing for custom event class PageHitRequestEvent.

public class PageHitRequestEvent : System.Web.Management.WebRequestEvent
{
    private string defaultLocalMessage = "";
    private const int EVENT_CODE = System.Web.Management.WebEventCodes.WebExtendedBase + 10;

    public PageHitRequestEvent(string eventMessage, object eventSource)
        :
        base(eventMessage, eventSource, EVENT_CODE)
    {
        
    }

    // Raises the PageHitRequestEvent.
    public override void Raise()
    {
        // prepare custom message.
        defaultLocalMessage = "";
        if(HttpContext.Current == null)
        {
            defaultLocalMessage = ", LocalMessage: {Request Context is null";
        }
        else
        {
            defaultLocalMessage += "SessionID: " + HttpContext.Current.Session.SessionID;

            string userId = HttpContext.Current.Session["UserId"] as string;
            if (string.IsNullOrEmpty(userId))
            {
                defaultLocalMessage += ", UserId: (Anonymous)";
            }
            else
            {
                defaultLocalMessage += ", UserId: " + HttpContext.Current.Session["UserId"];
            }
            defaultLocalMessage += "}";
        }
        
        // raise the event. 
        base.Raise();
    }

    public override void FormatCustomEventDetails(WebEventFormatter formatter)
    {
        base.FormatCustomEventDetails(formatter);

        // Add custom data.
        formatter.AppendLine("");

        formatter.IndentationLevel += 1;

        formatter.TabSize = 4;

        formatter.AppendLine("* PageHitRequestEvent Start *");

        // Display custom event information.
        formatter.AppendLine(defaultLocalMessage);
              
        formatter.AppendLine("* PageHitRequestEvent End *");

        formatter.IndentationLevel -= 1;
    }   
    
}

The second step is to raise this event from the source we want to log information about. Lets say we have some critical page in WebForms application or Contoller/Action in MVC Application, and we want to log data every time user hit that URL. Only we have to create the object of custom event class and simply call the Raise() method, which will just trigger the event and any related provider will handle this event as usual.

PageHitRequestEvent myEventObject = new PageHitRequestEvent("some logging message", this);
// raise the event.
myEventObject.Raise();

If you are using the custom provider from my previous post, where I logged detailed information from ToString() method, you will also get the information which you have written to the WebEventFormatter object in FormatCustomEventDetails method.

Finally comes the configuration part. Although in custom provider example, we have mapped eventName="All Events" in rules section, which will allow the corresponding provider to handle all events, so also our custom event we have created in this example.

But also if you want you can separately add custom event and its mapping with the desired provider.

First add the following line in eventMappings tag to define custom event name.

 <add name="My Event" type="PageHitRequestEvent" />

Second add following line in rules tag to map this event to the desired provider.

 <add name="My Event Rule" eventName="My Event" provider="FailedAuthenticationProvider2"
            minInstances="1" maxLimit="Infinite" minInterval="00:00:00" custom="" />
 

Now the given provider should be able to handle the custom event when triggered.

Resources:

No comments:

Post a Comment