As we work with Windows Azure we quickly appreciate the need for remote debugging. Azure will log unhandled exceptions and other events if we provide for it in our applications, and that is the topic of the HOL Debugging Applications in Windows Azure (in your WATK folder under \Labs\DebuggingCloudServices-VS2012). Most of the interesting code is pre-written in the C# version of this lab, so doing it in F# has the added benefit of letting us write all the code ourselves.
We start as we did Part 1, by creating a C# / F# MVC project using the template from Nuget. (Note: the bug discussed in Part 2 caused by incorrect versioning of the F# project has been fixed in Version 1.7.) The solution this creates has nothing to do with the Fabrikam Insurance project used in the C# lab, but that doesn’t matter. We just need to add some code to FsWeb.Controllers.ContactsController that will throw an exception when the user enters some arbitrarily “bad” input, such as if the contact’s first name is less than 3 letters long. While we’re at it we’ll add some routine tracing calls in the Get() and Post() methods.
Now we’re ready for Exercise 1 Task 2, Running the Application as a Windows Azure Project. Per the lab instructions we add to the solution an empty cloud service named FabrikamInsuranceService, then we associate it with our AzureDebuggingWebSpa project. We can now set FabrikamInsuranceService as the solution’s startup project and run our application in the Azure emulator.
We’re ready for Exercise 2, Adding diagnostic trace. The lab manual instructs us to use a pre-written C# project, but we’re going to write our own in F#. We add an F# library project named AzureDiagnostics to our solution and give it references to System.Data.Services.Client and Microsoft.WindowsAzure.StorageClient.
We add a source file named LogEntry.fs and define the LogEntry class.
Next we add a source file for the TableStorageTraceListener class. This is the most complex code in the project, so we’ll break it down for discussion. Here’s the first part:
Because F# doesn’t support the const keyword, we define DEFAULT_DIAGNOSTICS_CONNECTION_STRING in a private module. But the public static readonly member DIAGNOSTICS_TABLE can be declared inside the class.
Much of the complexity of this class comes from the requirement that it be thread-safe (tracing requests could come from multiple threads as the application runs). The static member messageBuffer uses the ThreadStatic attribute, which means there it should have a single, unique instance in each thread. Since the use of ThreadStaticAttribute on let bindings is deprecated, we declare messageBuffer with the val keyword. We make messageBuffer an option instead of a plain StringBuilder to avoid unnecessary use of nulls. The private helper function MessageBuffer encapsulates the logic for creating a new StringBuilder.
The tableStorage and traceLog members have “guard” objects for use with F#’s lock keyword. It would have been easier to make tableStorage immutable and initialize it once in the TableStorageTraceListener constructor, but the HOL authors chose to create it in the Flush() method, apparently as an optimization in case Flush() is never called. As with messageBuffer, we encapsulate the logic for creating tableStorage inside a helper function, and we use an option to avoid letting it be null.
The appendEntry function adds new LogEntries to the traceLog, and the overridden Flush() method saves them to the storage table. Both read pretty much the same as the C# version.
Finally we override a bunch more TraceLister methods. I won’t show them all here, just enough to illustrate the pattern.
Having written our own AzureDiagnostics project, we can proceed to Exercise 2, Task 1 in the HOL manual. In our F# project, AzureDebuggingWebSpa, we add a reference to AzureDiagnositics, also to Microsoft.WindowsAzure.ServiceRuntime, and Microsoft.WindowsAzure.StorageClient. We then add the following code to the Start() method of Global.fs.
The call to CloudStorageAccount.SetConfigurationSettingPublisher() is a bit more verbose in F# than in C#, for reasons I discussed here. The function configureTraceListener is defined inside the Start() method and makes use of F# pattern matching to shorten the code in two ways: (1) the keyword function after the signature allows matching on a single boolean parameter without naming the parameter, and (2) we use pattern matching to capture the result of bool.TryParse() in a single tuple instead of passing a ref parameter. Since the delegate we add to RoleEnvironment.Changed isn’t called from anywhere else, we define it with an anonymous function.
We now do Task 8 just as instructed in the HOL manual. To do Task 9 we add a source file named WebRole.fs to AzureDebuggingWebAppSpa.
Because this class inherits from Microsoft.WindowsAzure.ServiceRuntime.RoleEntryPoint, it needs to be represented in the web role project by a C# class that inherits from it. So we add a new file SubWebRole.cs to AzureDebuggingWebSpa.
We now have a listener to record exceptions and other events in Azure table storage in response to System.Diagnostics.Trace methods. In the rest of Task 1 we’ll add code to trace unhandled exceptions. First we add an error handler to the web role’s Global class. Next we add a filter for unhandled exceptions in AzureDebuggingWebAppSpa controllers. This works differently than in the C# HOL because our ContactsController inherits from System.Web.Http.ApiController instead of System.Web.Mvc.Controller. We need to define a subclass of System.Web.Http.Filters.ExceptionFilterAttribute, then register it at the end of Global.Start().
We’re now ready for Exercise 2 Task 2, Creating a Log Viewer Tool. We add a new F# console project named LogViewer and add references to Microsoft.WindowsAzure.StorageClient, System.Data.Services.Client and System.Configuration.
We add a source file named ProgressIndicator.fs and implement the progress indicator as a module.
Finally, we replace the code in Program.fs with this.
We’re now ready for the Verification step, which we carry out exactly as the HOL manual instructs. Our results should be the same as for the C# version.