Implementing a real-time error notification system

A real-time notification system is often useful, if not necessary, to handle unexpected events in order to make humans or their automated counterparts aware of what's going on while things happen. An unhandled exception could well be one of those scenarios where such a system could become interesting. In this recipe, we'll see how we can set up a sample application so that it can intercept unhandled exceptions in a single central place and post them to a monitoring system that will then use SignalR to broadcast the details about the errors it receives to its users.

We will not need many features from SignalR; actually, the only feature that we'll really need is the capability to invoke a client-side callback from outside any hub's method, thanks to the hub context.

Getting ready

Our sample will consist of the following two different projects:

  • Recipe52: This is a simple and traditional sample web application with just one page that raises an exception. In this application, we'll add a global exception handler, and from there, we'll collect information about the error to post to the Recipe52.Errors web application. We'll need to create a new empty web application to build this one.
  • Recipe52.Errors: A generic SignalR-based web application that we can use to publish errors that come from monitored applications. Also, in this case, we'll start by creating an empty web application.

How to do it…

Let's describe the two projects in the same order in which we listed them previously.

Recipe52 is just a sample application that generates errors; let's quickly illustrate the steps that are needed to build it:

  1. We start by adding a single page called index.cshtml, which will always throw an exception:
    @using System
    @{
        throw new Exception("Something went wrong!");
    }

    We use a Razor Web Page (.cshtml) in order to easily trigger a server-side exception; therefore, we need to enable Web Pages support by adding the following configuration key to our web.config file:

      <appSettings>
        <add key="webpages:Version" value="2.0" />
      </appSettings>
  2. We then add a global.asax file, removing all the plumbing code added by the Visual Studio template except for the Application_Error() method, which will be triggered every time an unhandled exception will be thrown on the application:
            protected void Application_Error(
                object sender, EventArgs e)
            {
                HttpContext.Current.Error.Post(
                    "Recipe52",
                    new Uri(
                    "http://localhost:15852/PostError.axd"));
            }

    We are invoking an extension method called Post() on the Error member of HttpContext.Current, which contains the last exception thrown on the current request's thread. The goal of this method is to provide a way to post information about the error to an external application. The method is supplied with a string that contains the name of the throwing application and a target URL to which the details of the error will be posted.

  3. Of course, the Post() method we just used does not exist yet, so let's define it:
        public static class ExceptionExtensions
        {
            public static void Post(this Exception error,
                string application, Uri destination)
            {
                using (var wc = new WebClient())
                {
                    var descriptor = new NameValueCollection
                    {
                        { "Application", application },
                        { "Error", error.Message }
                    };
    
                    try
                    {
                        wc.UploadValues(destination, 
                            descriptor);
                    }
                    finally
                    {
                        
                    }
                }
            }
        }

    Its implementation prepares a collection of key-value pairs that contain the name of the application received as a parameter and the Message property of the exception the method is invoked on. The collection is then posted to the destination URL using the WebClient type and its UploadValues() method.

The source of errors is ready. Of course, in a real-world scenario, the Post() method would probably belong to an external and reusable library, and maybe, it would post errors in an asynchronous way, but the way we did this is enough to illustrate the concept.

Let's now move on to Recipe52.Errors, the actual monitoring application where, of course, SignalR is a necessary component. As usual, we add it through the Microsoft.AspNet.SignalR NuGet package before starting to add the components of the application. Perform the following steps to do so:

  1. We start by adding a persistent connection called ErrorsConnection, whose content will be extremely simple, as shown:
        public class ErrorsConnection : PersistentConnection
        {
        }

    The client page of our monitoring application will connect here, and then it will have the receive() method of the connection object called whenever the server will send data to them. We'll be using ErrorsConnection on the server side shortly.

    We could have used a simple Hub and, if the monitoring application would need more features, it would've been the most sensible option. However, in this case, the implementation is so simple that we decided to use a persistent connection to illustrate its usage in a real-world sample.

  2. We also need, as usual, a Startup class that is generated using the corresponding template and whose implementation is a simple bootstrap sequence for persistent connection (exposed on the "/errors" endpoint), as shown in the following code snippet:
            public void Configuration(IAppBuilder app)
            {
                app.MapSignalR<ErrorsConnection>("/errors");
            }
  3. The interesting code of this recipe happens in an ASP.NET Handler called Errors, which we are going to add to the project using the template Visual Studio provides for its creation, as shown in the following screenshot:
    How to do it…

    This handler can be targeted by any external application to post information about their errors to the monitoring system. We just need to expose it by registering it in the web.config file:

      <system.webServer>
        <handlers>
          <add name="PostError" verb="POST" 
              path="PostError.axd" 
              type="Recipe52.Errors.Errors" />
        </handlers>
      </system.webServer>

    This new entry in the handlers section declares that an instance of our Errors type will be invoked any time a client reaches an endpoint called PostError.axd with an HTTP POST request.

  4. After having seen how to create and wire the new handler, let's take a look at its code, which is as follows:
        public class Errors : IHttpHandler
        {
            public bool IsReusable
            {
                get { return true; }
            }
    
            public void ProcessRequest(HttpContext context)
            {
                var post = context.Request.Params;
    
                var application = post["Application"];
                var error       = post["Error"];
    
                var connection = GlobalHost.ConnectionManager
                    .GetConnectionContext<ErrorsConnection>()
                    .Connection;
                connection.Send(
                   new ConnectionMessage(
                       connection.DefaultSignal, 
                       new
                       {
                           application, error
                       }));
            }
        }

    Just like any other ASP.NET Handler, Errors implements IHttpHandler; therefore, it has to provide a property called IsReusable, which, in our case, can simply return true and a method called ProcessRequest, which will be invoked every time an HTTP request reaches it. The content of ProcessRequest is quite simple and is as follows:

    • It first extracts the two fields that we are expecting in the POST payload from any client that contacts these handlers: Application and Error.
    • Then, it gets an instance of ConnectionManager from GlobalHost, and from there, it retrieves a connection context related to our ErrorsConnection type, which is eventually used to send data to every connected client in the form of an anonymous type instance that contains the application name and the error message that comes from the posting application.

We're almost done; now, we just need a simple client page that will receive all incoming errors posted to our monitoring application in real time.

We create a page called index.html and add the following content:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="Scripts/jquery-2.1.0.js"></script>
    <script src="Scripts/jquery.signalR-2.0.2.js"></script>
    <script>

        $(function() {

            $.connection('errors')
                .received(
                    function (e) {
                        var message =
                            e.application +
                            ' failed with error: ' +
                            e.error;
                        $('#errors')
                            .prepend($('<li/>').text(
                                message));
                    })
                .start();

        });

    </script>
</head>
<body>
    <ul id="errors"></ul>
</body>
</html>

The code is very simple; it just contains the necessary JavaScript references, an unordered list in the body section of the page, and a piece of script to open a SignalR connection after having registered a receive callback that will be invoked for every error descriptor broadcasted by the server-side Errors handler. The callback will build a simple string with some detail about the error, and it will prepend it to the unordered list on the page.

In order to test the monitoring application we have to build the Recipe52.Errors project, launch it, and then navigate to its index.html page. While we keep that browser instance around, we build and launch Recipe52, we navigate to its index.cshtml page, and we observe the Yellow Screen Of Death (YSOD) that appears because of the exception it raises. At the same time, on the browser window we opened earlier pointing at the Recipe52.Errors application, we should see a message describing the same error and displayed at the same time the error happens on Recipe52.

There's more…

With this sample, you should have a general idea about how to build such a monitoring system to notify errors in real time, but it's also true that there are a lot of details to take care of if you want to provide a full-fledged solution to the problem. In case you are interested, a similar solution already exists and is basically built on the same ideas that we just saw. It's called ElmahR (http://elmahr.apphb.com/), and it's a real-time error-monitoring dashboard that can be configured to display live information about errors that happen on multiple monitored applications. The potential sources of errors just have to use ELMAH (https://code.google.com/p/elmah/), a very well known library to intercept unhandled exceptions and plug into it a module exposed by ElmahR.Elmah, which belongs to the ElmahR project. ELMAH and ElmahR.Elmah are both available on NuGet, and together, they play the same role of both the Error handler and the Post extension method from Recipe52, while Recipe52.Errors corresponds to the ElmahR dashboard. For more details, you can check its source code at https://bitbucket.org/wasp/elmahr/wiki/Home.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.160.216