ELMAH – Add logging to an ASP.NET project in under 10 minutes

Looking to add a simple catch-all logging framework to your ASP.NET project? Look no further. Introducing Elmah (Error Logging Modules and Handlers), a fully configurable, application-wide logging framework for ASP.NET apps.

elmah

ELMAH offers the following features (no assembly required!):

  • Logging of nearly all unhandled exceptions.
  • A web page to remotely view the entire log
  • Real-time emailing of exceptions as they occur
  • RSS feed
  • In-memory, database or physical file log storage

Today I’ll be showing you how to install ELMAH on a new or existing ASP.NET project in under 10 minutes. Out of the box, ELMAH is configured to run in-memory, meaning that logs are deleted when the host server is restarted. In most cases, this isn’t very useful, so I’ll also be showing you how to configure ELMAH to save the logs to a database like SQL Server.

Installing ELMAH

The quickest and simplest way to install ELMAH is using the Nuget package manager. Open the Package Manager Console (in Visual Studio 2015: View > Other Windows > Package Manager Console). Run the following command:

PM> Install-Package elmah

npm

ELMAH is now added to your ASP.NET application. Believe it or not, you now have a working logging framework! If you run your application and browse to /elmah.axd you will see the log page where you can view exceptions as they occur.

elmah-log-empty

Unless your application throws a lot of exceptions (hey, we’re not all perfect) your log will most likely be empty the first time you view it (like the log above). To see a working example, either manually throw an exception in your application or add some C# code similar to this:

Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("hey!"));

Configuring ELMAH to Log to a Database

We now have a logging framework – awesome. Logging in-memory is kind of useless though…they are logs after all. Let’s configure ELMAH to log to a database like SQL Server.

The first thing we’ll need to do is setup an ELMAH database table. ELMAH provides a nice DDL script which can be manually run against your database. Click here to download that script.

If you are using Entity Framework with code first migrations, here’s a migration script you can use instead:

public override void Up()
 {
 Sql(@"CREATE TABLE [dbo].[ELMAH_Error]
 (
 [ErrorId] UNIQUEIDENTIFIER NOT NULL,
 [Application] NVARCHAR(60) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [Host] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [Type] NVARCHAR(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [Source] NVARCHAR(60) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [Message] NVARCHAR(500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [User] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [StatusCode] INT NOT NULL,
 [TimeUtc] DATETIME NOT NULL,
 [Sequence] INT IDENTITY(1, 1) NOT NULL,
 [AllXml] NTEXT COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
 ) ");

Sql("EXEC('ALTER TABLE [dbo].[ELMAH_Error] WITH NOCHECK ADD CONSTRAINT[PK_ELMAH_Error] PRIMARY KEY([ErrorId])')");

Sql("EXEC('ALTER TABLE [dbo].[ELMAH_Error] ADD CONSTRAINT[DF_ELMAH_Error_ErrorId] DEFAULT(NEWID()) FOR[ErrorId]')");

Sql(@"EXEC('CREATE NONCLUSTERED INDEX [IX_ELMAH_Error_App_Time_Seq] ON [dbo].[ELMAH_Error] 
 (
 [Application] ASC,
 [TimeUtc] DESC,
 [Sequence] DESC
 )')");

Sql(@"EXEC('CREATE PROCEDURE [dbo].[ELMAH_GetErrorXml] (@Application NVARCHAR(60), @ErrorId UNIQUEIDENTIFIER) AS
 SET NOCOUNT ON
 SELECT [AllXml] FROM [ELMAH_Error] WHERE [ErrorId] = @ErrorId AND [Application] = @Application')");

Sql(@"EXEC('CREATE PROCEDURE [dbo].[ELMAH_GetErrorsXml]
 (@Application NVARCHAR(60), @PageIndex INT = 0, @PageSize INT = 15, @TotalCount INT OUTPUT)
 AS 
 SET NOCOUNT ON 
 DECLARE @FirstTimeUTC DATETIME
 DECLARE @FirstSequence INT
 DECLARE @StartRow INT
 DECLARE @StartRowIndex INT

SELECT @TotalCount = COUNT(1) FROM [ELMAH_Error] WHERE [Application] = @Application

SET @StartRowIndex = @PageIndex * @PageSize + 1

IF @StartRowIndex <= @TotalCount
 BEGIN 
 SET ROWCOUNT @StartRowIndex

SELECT @FirstTimeUTC = [TimeUtc], @FirstSequence = [Sequence] FROM [ELMAH_Error]
 WHERE [Application] = @Application ORDER BY [TimeUtc] DESC, [Sequence] DESC 
 END
 ELSE
 BEGIN 
 SET @PageSize = 0 
 END

SET ROWCOUNT @PageSize

SELECT 
 errorId = [ErrorId], 
 application = [Application],
 host = [Host], 
 type = [Type],
 source = [Source],
 message = [Message],
 [user] = [User],
 statusCode = [StatusCode], 
 time = CONVERT(VARCHAR(50), [TimeUtc], 126) + ''Z''
 FROM [ELMAH_Error] error WHERE [Application] = @Application AND [TimeUtc] <= @FirstTimeUTC
 AND [Sequence] <= @FirstSequence ORDER BY [TimeUtc] DESC, [Sequence] DESC FOR XML AUTO')");

Sql(@"EXEC('CREATE PROCEDURE [dbo].[ELMAH_LogError] (@ErrorId UNIQUEIDENTIFIER, @Application NVARCHAR(60), @Host NVARCHAR(30),
 @Type NVARCHAR(100), @Source NVARCHAR(60), @Message NVARCHAR(500), @User NVARCHAR(50), @AllXml NTEXT, @StatusCode INT,
 @TimeUtc DATETIME) AS

SET NOCOUNT ON

INSERT INTO [ELMAH_Error] ([ErrorId], [Application], [Host], [Type], [Source], [Message], [User], [AllXml], [StatusCode], [TimeUtc])
 VALUES (@ErrorId, @Application, @Host, @Type, @Source, @Message, @User, @AllXml, @StatusCode, @TimeUtc)')");
 }

public override void Down()
 {
 Sql("EXEC('DROP PROCEDURE [ELMAH_GetErrorXml]')");
 Sql("EXEC('DROP PROCEDURE [ELMAH_GetErrorsXml]')");
 Sql("EXEC('DROP PROCEDURE [ELMAH_LogError]')");
 Sql("Drop table ELMAH_Error");
 }

Next we’ll make some changes to the web.config to tell ELMAH to log to our new table we created. Add the following in the node of web.config:

  <elmah>
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="YourConnectionString" applicationName="YourAppName"/>
  </elmah>

YourConnectionString in the snippet above should be the connection string to the database where you created the ELMAH table. It should look something like this:

<connectionStrings>
    <add name="YourConnectionString" connectionString="Initial Catalog=your_database;data source=.\SQLEXPRESS;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
</connectionStrings>

That’s it! You now have a logging framework added to your ASP.NET application that will store its logs in a SQL Server database. Take a look at the ELMAH documentation to learn about what else you can accomplish now that you’re basic setup is complete.

Thanks for reading!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s