Sunday, August 30, 2015

Global IIS Logging

Up until several years ago Visual Studio included a C++ sample project which was a wrapper around the C API that could intercept IIS Logging events. The internal plumbing of IIS has changed many times since then and the sample vanished, which was a nuisance, as I was using it to track hits on my web sites in real-time and show them in a grid which sat at the bottom corner of my server's screen.

A few years ago I looked for way of implementing my old logging feature again, but due to confusing documentation and lack of clear guidance on how to do this in managed code I gave up.

This weekend I decided to have another bash with a clear mind on how to intercept global IIS logging using C# instead of C/C++. I eventually stumbled over these steps, which I want to document for myself and anyone else who might be interested:
  1. Create a class library targeting Framework 3.5 (not higher).
  2. Set the project to sign the assembly with a strong name (using a SNK file).
  3. Write a class of whatever name you like that implements IHttpModule.
  4. In the Init method add a listener to the PostLogRequest event.
  5. In the event handler you can cast the sender to a HttpApplication which then gives you properties for the Request and Response, and from there you can get lots of useful information to log.
  6. Add the assembly to the GAC by either dropping it into C:\windows\assembly or using the gacutil utility.
  7. In IIS Manager at the machine level node open IIS Modules, click Add managed Modules and you should see your assembly listed in the pick list. Give it a display name and uncheck the option for "only managed requests" (mine was unchecked by default).
There are many events that you can listen to, including a pair called LogRequest and PostLogRequest. Using guesswork and instinct I decided to use the latter event, and when I ran a web search I found someone who was also using it, but they had this statement inside:

var app = (HttpApplication)sender;
if (app.Context.IsPostNotification)
{
  // Log here
}

Why they were doing this wasn't clear, but I decided to do the same due to lack of time. So in the logging code I get useful properties like the RawUrl, StatusCode, UserAgent, UserHostAddress and more, put them into an XML element string and broadcast the string as utf-8 bytes using UdpClient. I like this idea because the logging code is quite short and fast, and all it does is spew broadcasts out to any other apps who might like to listen and display what's happening.

I did in fact write a small WPF desktop app to listen to the broadcasts and show them in a nice grid. Because events can arrive in rapid bursts, be sure to use UdpClient BeginReceive to asynchronously process the events. Look for some samples on how to do this thread safely and efficiently without blocking.

Windows Server 2012

In September 2017 I tried to follow the steps above on a 2012 Server in an Azure VM and ran into problems. You can no longer open C:\Windows\assembly in Windows Explorer and simply drag-and-drop a managed DLL file into the GAC that way. I installed part of the Windows SDK in the hope that I could get a copy of gacutil.exe that way, but it was a mistaken waste of time because the file was not included. I had to use PowerShell in an obscure way as described on this page. After many hours of pondering I eventually guessed correctly that you have to use the Add Roles and Features Wizard to add .NET Extensibility 3.5 to IIS, otherwise your managed module will not appear in the Add list.