Click here to Skip to main content
15,881,881 members
Articles / Web Development / ASP.NET

Elmah

Rate me:
Please Sign up or sign in to vote.
4.89/5 (5 votes)
27 Dec 2012CPOL3 min read 52.4K   24   1
This .NET Tutorial explains how to set up and configure ELMAH (Error Logging Modules and Handlers) in an .NET 4.5 MVC 4 application

Overview

As promised, here is my next article regarding another tool I find completely invaluable in my life as a developer, Elmah. Basically Elmah sites quietly on your site, logging any exceptions (Code based or Web Server, for example, 404) which occur to (in this example) a database.  It then provides a nice neat GUI front end to allow you to view the details of these errors, including stack traces. If you're anything like me, and are tired of conversations which go like this:
  • User: "Karl, the website crashed earlier"
  • Karl: "Oh right, what were you doing"
  • User: "I don't remember, I was just on it, can you fix it please"
  • Karl: "Well I could do with reproducing it...
You will be happy Elmah exists!

Installation

Installation is easy, just follow these steps.  Remember, this guide is based around .NET 4.5 MVC 4 and the associated caveats, it may not be exactly what is required for your configuration but should be near as dammit.
  1. Ok, so firstly, install the Elmah package from the Package Manager console in Visual Studio:
    VB
    Install-Package elmah
    We use the package manager as it will handle the installation of any dependencies, and also add the required sections to your web.config (well, almost).
  2. Now open your web.config and find the <elmah> section.  You'll need to modify it to use SQL logging.  Make that section look something like this:
    VB
    <elmah>
      <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="YourConnectionStringName" applicationName="YourWebsiteName" />
      <security allowRemoteAccess="true" />
    </elmah>
    
    It's pretty self explanatory, the connection string name must be a valid connection string from the <connectionStrings> section of your web.config, and the application name should be the name you want to log errors for this application against.
  3. As you can see in point #2, I have set allowRemoteAccess to true, this is a personal preference, but if you do this you must secure it.  If you use forms authentication with roles this is simple to do, find the <location path="elmah.axd"> element, and make it look something like this:
    VB
    <location path="elmah.axd">
        <system.web>
          <httpHandlers>
            <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
          </httpHandlers>
          <authorization>
            <allow roles="Admin"/>
            <deny users="*"/>
          </authorization>
        </system.web>
        <system.webServer>
          <handlers>
            <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
          </handlers>
        </system.webServer>
      </location>  
    You'll notice here I have added a constraint which means only users which are members of the Admin group will be able to view Elmah.axd
  4. The next section you need to modify is in your system.webServer section, you need to add runAllManagedModulesForAllRequests="true" to the modules section.  Without this, elmah will not log exceptions in .NET MVC, you will simply find there is nothing logging to your SQL Server:
    VB
    <modules runAllManagedModulesForAllRequests="true">
          <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
          <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
          <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>

Custom Errors

When you use Elmah with Custom Errors redirects, you'll notice that nothing gets logged to elmah.  This is because the framework intercepts the error prior to elmah getting hold of it.  To get around this we need to add two global filters, the code for them is fairly, simple:
Public Class ElmahHTTPErrorAttribute
    Inherits System.Web.Http.Filters.ExceptionFilterAttribute

    Public Overrides Sub OnException(actionExecutedContext As System.Web.Http.Filters.HttpActionExecutedContext)
        If actionExecutedContext.Exception IsNot Nothing Then
            Elmah.ErrorSignal.FromCurrentContext().Raise(actionExecutedContext.Exception)
        End If
        MyBase.OnException(actionExecutedContext)
    End Sub
End Class

Public Class ElmahMVCErrorAttribute
    Implements IExceptionFilter

    Public Sub OnException(filterContext As ExceptionContext) Implements IExceptionFilter.OnException
        If filterContext.Exception IsNot Nothing Then
            Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception)
        End If
    End Sub
End Class
And then you need to modify your FilterConfig.vb.  What we're doing here is creating a new section for HTTP filter attributes, which we will call from global.asax later:
Public Class FilterConfig
    Public Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)

        'Add elmah attribute
        filters.Add(New ElmahMVCErrorAttribute, 1)

        'Add the standing handle error attribute
        filters.Add(New HandleErrorAttribute(), 2)

    End Sub
    Public Shared Sub RegisterHTTPFilters(ByVal filters As System.Web.Http.Filters.HttpFilterCollection)

        'Add a http filter attribute
        filters.Add(New ElmahHTTPErrorAttribute)

    End Sub
End Class
Finally, update Application_Start in your Global.asax.vb to register the new filters:
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
FilterConfig.RegisterHTTPFilters(GlobalConfiguration.Configuration.Filters)
And that's it, you're all configured and ready to go, if you go to http://yoursite.com/Elmah.axd, you should see a screen similar to the following:
Elmah GUI Example
You can drill down further, by clicking details, which will show you the stack trace if one is available.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Hewlett Packard Enterprise Security Services
United Kingdom United Kingdom
Technical Architect for Hewlett-Packard Enterprise Security Service.

Please take the time to visit my site

Comments and Discussions

 
SuggestionSQL Script For ELMAH Pin
Amit R Jaiswal10-Oct-17 21:05
professionalAmit R Jaiswal10-Oct-17 21:05 
I think the sql script for elmah or its link is missed here so adding it :


-- ELMAH DDL script for Microsoft SQL Server 2000 or later.

DECLARE @DBCompatibilityLevel INT
DECLARE @DBCompatibilityLevelMajor INT
DECLARE @DBCompatibilityLevelMinor INT

SELECT
@DBCompatibilityLevel = cmptlevel
FROM
master.dbo.sysdatabases
WHERE
name = DB_NAME()

IF @DBCompatibilityLevel <> 90
BEGIN

SELECT @DBCompatibilityLevelMajor = @DBCompatibilityLevel / 10,
@DBCompatibilityLevelMinor = @DBCompatibilityLevel % 10

PRINT N'
===========================================================================
WARNING!
---------------------------------------------------------------------------

This script is designed for Microsoft SQL Server 2005 (9.0) but your
database is set up for compatibility with version '
+ CAST(@DBCompatibilityLevelMajor AS NVARCHAR(80))
+ N'.'
+ CAST(@DBCompatibilityLevelMinor AS NVARCHAR(80))
+ N'. Although
the script should work with later versions of Microsoft SQL Server,
you can ensure compatibility by executing the following statement:

ALTER DATABASE ['
+ DB_NAME()
+ N']
SET COMPATIBILITY_LEVEL = 90
If you are hosting ELMAH in the same database as your application
database and do not wish to change the compatibility option then you
should create a separate database to host ELMAH where you can set the
compatibility level more freely.

If you continue with the current setup, please report any compatibility
issues you encounter over at:

https://github.com/elmah/Elmah/issues
===========================================================================
'
END
GO

/* ------------------------------------------------------------------------
TABLES
------------------------------------------------------------------------ */

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] NVARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[ELMAH_Error] WITH NOCHECK ADD
CONSTRAINT [PK_ELMAH_Error] PRIMARY KEY NONCLUSTERED ([ErrorId]) ON [PRIMARY]
GO

ALTER TABLE [dbo].[ELMAH_Error] ADD
CONSTRAINT [DF_ELMAH_Error_ErrorId] DEFAULT (NEWID()) FOR [ErrorId]
GO

CREATE NONCLUSTERED INDEX [IX_ELMAH_Error_App_Time_Seq] ON [dbo].[ELMAH_Error]
(
[Application] ASC,
[TimeUtc] DESC,
[Sequence] DESC
)
ON [PRIMARY]
GO

/* ------------------------------------------------------------------------
STORED PROCEDURES
------------------------------------------------------------------------ */

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

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

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

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

-- Get the ID of the first error for the requested page

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

-- Now set the row count to the requested page size and get
-- all records below it for the pertaining application.

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

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

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 NVARCHAR(MAX),
@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
)

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
AJ

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.