Introduction
You may have heard about, and perhaps even used, ELMAH, which is an Open Source ASP.NET project that logs unhandled exceptions for a web application. There is also a way to make it available for an entire website full of web applications developed by disparate team members. However, it is not an easy accomplishment unless one already knows enough about the pieces outside of ELMAH that are required to make this happen. The developer can spend many hours (I did, at least) chasing down bits of information from different sources until it all comes together. Until now, I have not seen a complete document on setting up ELMAH for flexible, site-wide use.
This is a step-by-step to show how to set up ELMAH on your web server so that all ASP.NET WebForms applications for a given .NET Framework version will be unobtrusively monitored by ELMAH, and all of the unhandled exceptions will be logged. I will show how to optionally code the web.config of an individual application so that its error notifications (for those unhandled exceptions caught and logged by ELMAH) will be sent to a particular email address. This limits the delivery of an application's error email messages to only the interested party (developer) for that application, rather than to developers who didn't even work on that particular application.
Background
Many .NET developers have already discovered ELMAH (Error Logging Modules and Handlers), a great way to log and view a web application's unhandled exceptions. ELMAH was developed by Atif Aziz, and the project lives here at Google Code. Besides the ELMAH DLL and samples of its use, ELMAH's Google Code home contains discussions, a section for issues, and a Wiki with a list of ELMAH articles. In fact, the inspiration for this how-to is a discussion between Atif Aziz and Bart Hermans on that site.
ELMAH can be easily installed on a per-application basis by adding a reference in the project (putting Elmah.dll in the project's bin folder) and adding specific lines to the project's web.config file. Why use ELMAH? It's not likely you will be able to expect and set a trap for every possible error in your web app with Try/Catch blocks. ELMAH lets you know the details of every exception that "slips by". This is also a great way to learn of bugs in a WebForms application during testing.
Even if you don't want to attempt a site-wide ELMAH implementation, it's well worth your time to download ELMAH from the link above. You can quickly set it up to record unhandled exceptions for an individual web application project. Once you see how it can capture and report to you every unhandled exception, you may be hooked. And then you'll want to come back here and follow the "How To" for adding it to your overall website, so that you won't need to put ELMAH's DLL in every project's /bin and so that you won't need to put the same ELMAH sections in every single application's web.config.
Step 1 - Download the sample web.config and source code from the ELMAH Google Code site
The official ELMAH sample web.config shows you the ELMAH sections to put into your own web.config file. They are well-commented. For an individual web application, all you need are the precompiled binaries (choose 32-bit or 64-bit for your environment) from the downloads page. A sample web.config is included. However, for site-wide use, get the binaries only for experimenting with an individual web application. What you really need to download are the sources so you can compile a strong-named Elmah.dll for your global assembly cache. If you don't download the binaries, there's a sample web.config you can download.
Step 2 - Compile source code with Strong Name Key
All assemblies placed in the GAC (Global Assembly Cache) must be strong named, which ensures their global uniqueness thanks to the private key with which the strong name tool signs them.
Unzip your downloaded "Sources", and drill down to src\Solutions and open the solution file appropriate to the .NET Framework version for which you want to install ELMAH. At the time of this writing, the only solution for .NET 2.0 and above is Visual Studio 2010. If you only have VS2005 or VS2008, you may be able to compile the source code using the free Visual Studio 2010 Express or Visual C# 2010 Express (the source code files are in C#).
With the solution open in Visual Studio 2010, right-click the project in Solution Explorer and choose "Properties". Now, choose "Signing" and check "Sign the Assembly".
From the drop down list, select <New...> and type in a Key file name of your choice. Uncheck the "Protect my key file with a password" checkbox. Save the properties. Build the project as you would normally do. Your bin folder should now has an Elmah.dll that has been strong name signed. To verify it is indeed strong named, you can use the Strong Name Tool (sn.exe) or the Microsoft Intermediate Language Disassembler (ILDASM.EXE). Here's a blog that shows how to do either one. Or you can simply trust that it is strong named. If it isn't, the next step won't work, and that will tell you!
Step 3 - Install the Strong Named Elmah.dll assembly into the Global Assembly Cache
You can get the assembly into the global assembly cache by creating a setup project and using MSI to install it. Although that's the official Microsoft way (and perhaps the only way if your server has really tight access control), it's beyond the scope of this article. A simpler way is to do it like this: copy the strong named Elmah.dll to a temporary location in a folder on your web server. Then log in to that server, either at its console or via Remote Desktop, before trying to add the assembly (Elmah.dll) to the GAC. I mention this because I like to use the method of dragging and dropping the DLL from one Windows Explorer instance to another. (I learned from personal experience that the view of the GAC in Windows Explorer, which is a shell extension, shows the files of my own PC's GAC when I try to view the web server's GAC over the network, not the files of the web server's GAC!)
Once you have two Windows Explorer instances open on the web server (one for the folder where you have temporarily put the strong named Elmah.dll, and the other for the GAC, which is typically at C:\Windows\Assembly), you'll notice that the Windows Explorer view of the GAC looks different than you may have expected. That's because you're seeing into the GAC via the shell extension I mentioned. It shows you properties such as the Assembly Name, Version, and Public Key Token.
Drag and drop the Elmah.dll from your temporary location to the Windows Explorer view of the GAC. If it's truly strong named, the Elmah.dll will now appear in the GAC view (although you may need to refresh the view to see it).
Congratulations - the hardest part is done! Now, right-click ELMAH in the Assembly Name column and choose "Properties". Select the "Public Key Token" value and copy/paste it somewhere safe, like a text file, for your next step.
Step 4 - Modify your server's web.config
As you can see from the sample web.config you downloaded from the ELMAH site, it contains some ELMAH-specific sections. You'll need to add at least some of these to your own web.config. You could put these in your web application's web.config, and ELMAH would work, even though there is no Elmah.dll in your web app's bin folder. That's because you've put a strong named Elmah.dll in the Global Assembly Cache. And this is fine, if you only want to log errors for specific applications. But if you want to log the unhandled exceptions for every web app that uses a particular version of the .NET Framework, the better route to go is to modify the "root" web.config for, say, the .NET 2.0 Framework.
Each version of the .NET Framework (except 3.0 and 3.5, which piggyback onto the 2.0 framework) has its own "CONFIG" folder with a machine.config and a web.config. For our example, look for the web.config that resides in C:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG. Save a copy of it for backup, especially if this is a production server.
Open the server's framework web.config mentioned above, and edit it to include the ELMAH sections from the sample web.config you have downloaded from the ELMAH site. I'll show some sample code here that is enough for the minimum of logging the errors and setting a page handler to display them to any authenticated user who goes to look at the errors for a specific web application. Note that I like to put comments around an "ELMAH specific section" to help me find them later. Also, I've used a vertical ellipsis simply to indicate existing sections of the web.config that don't need to be shown in this example. Don't code those ellipses into your web.config!
<!---->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!---->
<sectionGroup name="elmah">
<section name="security" requirePermission="false"
type="Elmah.SecuritySectionHandler, Elmah,
Version=1.2.13605.0, Culture=neutral,
PublicKeyToken=6235d7ef90b85eg2"/>
<section name="errorLog" requirePermission="false"
type="Elmah.ErrorLogSectionHandler, Elmah, Version=1.2.13605.0,
Culture=neutral, PublicKeyToken=6235d7ef90b85eg2"/>
</sectionGroup>
<!---->
</configSections>
<!---->
<elmah>
<security allowRemoteAccess="yes" />
<errorLog type="Elmah.SqlErrorLog, Elmah, Version=1.2.13605.0,
Culture=neutral, PublicKeyToken=6235d7ef90b85eg2"
connectionStringName="ELMAH_Logging" />
</elmah>
<!---->
<!---->
<location path="elmah.axd">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
<!---->
.
.
.
<system.web>
.
.
.
<httpHandlers>
<!---->
<add verb="POST,GET,HEAD" path="elmah.axd"
type="Elmah.ErrorLogPageFactory, Elmah, Version=1.2.13605.0,
Culture=neutral, PublicKeyToken=6235d7ef90b85eg2"/>
<!---->
.
.
.
</httpHandlers>
<httpModules>
<!---->
<add name="ErrorLog"
type="Elmah.ErrorLogModule, Elmah, Version=1.2.13605.0,
Culture=neutral, PublicKeyToken=6235d7ef90b85eg2"/>
<!---->
.
.
.
</httpModules>
.
.
.
</system.web>
<connectionStrings>
<!---->
<add name="ELMAH_Logging"
connectionString="Server=OurDBServer;Database=SomeDB;
User ID=user;Password=ourpass;" />
<!---->
</connectionStrings>
</configuration>
Code above has been wrapped to avoid page scrolling
I want to point out several things about the above web.config file (which, I'll say again, is the "root" web.config for all web apps for a particular framework version in your whole web server):
- First, there are no sections having to do with emailing error messages to the developer. I recommend putting those sections into each web application's web.config file, because that gives us granular control over who receives messages for each application. You most likely would not want to send ELMAH's messages for all the server's web apps to any one email address or email distribution list.
- Second, you must add the
Version
and PublicKeyToken
parts to the "type
" attributes in each ELMAH specific element in the web.config, because you are using a strong named version of Elmah.dll in the GAC. I also added the Culture=neutral
in mine, but I don't know if that is essential. You may want to keep that in yours, too, especially if the Windows Explorer GAC view shows a value in the Culture column. I'll quote a tip from ELMAH's author, Atif Aziz, here: Make sure the value of the type
attribute is not 'wrapped'. It should be (all) on one line.... The .NET Framework is very particular about that.
- Third, be sure to use the
Version
and PublicKeyToken
that corresponds to YOUR strong named Elmah.dll. Remember at the end of Step 3, where we saved a copy of the Public Key Token value in a text file? You'll paste that value into the PublicKeyToken
part of each ELMAH element in your web.config. The one you see in the example above is a unique one I generated just for the example.
Step 5 - Modify a test web application project and its web.config so you can receive emails from ELMAH
As mentioned above, we're going to limit the email messages from ELMAH (if configured for email, ELMAH sends a new message for each exception that it logs) so that the person responsible for a particular web application is the recipient for error messages. He or she won't receive messages about errors occurring in everyone else's web applications. And, if you are going to include any ELMAH sections in your individual application's web.config (such as to receive individualized emails), your project needs a reference to the ELMAH assembly.
- Add a Reference in your project to Elmah.dll
In Solution Explorer, right click "References" and choose "Add Reference". Browse to a location where you have saved a copy of Elmah.dll that is the same version as the one you strong named and have put in the GAC. Your reference doesn't need to be to the strong named DLL, but it definitely should be to a copy of an Elmah.dll of the same version number.
- Set "Copy Local" to False
Now that you've added the reference, select it from your list of References. In the Properties window, find the "Copy Local" entry, and choose "False", because there is no need to have this Elmah.dll be included in the bin folder of your deployed project. As Microsoft says in How to display an assembly in the "Add Reference" dialog box: "If you want to use an assembly from the global assembly cache, you should drop your assemblies to a local folder, and then add a reference to the assembly from this folder. You may want to set the Copy Local property to False for that assembly if you do not want the assembly to be copied locally to your project folders. At runtime, the application will use the assemblies from the global assembly cache."
- Add a few ELMAH sections to your web application's web.config, as shown below.
="1.0"
<configuration>
<configSections>
-->
<sectionGroup name="elmah">
<section name="errorMail" requirePermission="false"
type="Elmah.ErrorMailSectionHandler, Elmah, Version=1.2.13605.0,
Culture=neutral, PublicKeyToken=6235d7ef90b85eg2"/>
</sectionGroup>
-->
</configSections>
-->
<elmah>
<errorMail
from="noreply@yourdomain.com"
to="YouTheDeveloper@yourdomain.com"
priority="high"
smtpServer="yourSmtpServer.yourdomain.com" />
</elmah>
-->
<system.web>
<httpModules>
-->
<add name="ErrorMail"
type="Elmah.ErrorMailModule, Elmah, Version=1.2.13605.0,
Culture=neutral, PublicKeyToken=6235d7ef90b85eg2"/>
-->
</httpModules>
.
.
.
</configuration>
Code above has been wrapped to avoid page scrolling
This was not too much to add to an individual web application's web.config, and it's easy to keep track of the ELMAH additions if you put the "Begin/End and ELMAH specific section" comments around each one.
Step 6 - Testing and putting into production
Hopefully, this is all you need to do. Also hopefully, you did all this on a test webserver first! If you will add in some bad code to your app for ELMAH to catch, you'll see if it works for you. Please refer to the official ELMAH site for details on how to see your errors. The many articles found there will help. I need to state a few assumptions based on the code above:
- If you've had any exceptions logged, you can see all of them by typing "/elmah.axd" at the end of your web application folder's URL. That is, if you used
path="elmah.axd"
in your web.config, as in the example above. Part of the beauty of ELMAH is that any web application on the server using this version of the .NET Framework, even older ones that were there before you began using ELMAH, and that you've never modified to mention ELMAH in its web.config, will also have its unhandled exceptions logged. And, yes, you simply have to type "/elmah.axd" as above, even for those non-ELMAH apps. Because of the ELMAH sections in that Framework version's root web.config, ELMAH is doing its thing for you!
- My example assumes the use of Microsoft SQL Server as the place where ELMAH logs the errors. ELMAH's documentation outlines other ways to log errors.
- My web.config didn't mention a default error page that you can set up in the
<customErrors>
element. It's a really good idea to have one, so that your users don't see the "Yellow Screen of Death" (YSOD) page when an unhandled exception occurs. The great thing about ELMAH is that it will email you a copy of the YSOD page for you to see, even though the web user only sees the harmless default error page to which you are redirecting users when the error occurs.