Hits

Dec 20, 2011

Elmah Exception Logging with Azure

Summary

If you have an ASP.Net project mapped to an Azure worker role, then Elmah is a great exception handling framework for providing near code-less, world class exception handling for your project. Read on for a step-by-step guide.


  • Create an Azure web role
  • Reference the Elmah dll in your project
  • In its web.config file, in the configSections node under the configuration node, add the following section:
  • <sectionGroup name="elmah">
          <!-- NOTE! If you are using ASP.NET 1.x then remove the
              requirePermission="false" attribute from the section
              elements below as those are only needed for
              partially trusted applications in ASP.NET 2.0 -->
          <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
          <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
          <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
          <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
          <section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
    </sectionGroup>
  • Add the elmah node right after configSections
  • <elmah>
    
        <security allowRemoteAccess="1" />
        <errorLog type="Elmah.SqlErrorLog, Elmah"
                connectionString="data source=server;initial catalog=databaseuser id=user;password=password;multipleactiveresultsets=True;" />
    
        <!--<errorFilter>
          <test>
            <jscript>
              <expression>
                <![CDATA[
                        // @assembly mscorlib
                        // @assembly System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
                        // @import System.IO
                        // @import System.Web
    
                        HttpStatusCode == 404
                        || BaseException instanceof FileNotFoundException 
                        || BaseException instanceof HttpRequestValidationException
                        /* Using RegExp below (see http://msdn.microsoft.com/en-us/library/h6e2eb7w.aspx) */
                        || Context.Request.UserAgent.match(/crawler/i)                      
                        || Context.Request.ServerVariables['REMOTE_ADDR'] == '127.0.0.1' // IPv4 only
                        ]]>
              </expression>
            </jscript>
          </test>
        </errorFilter>-->
      </elmah>
  • Under the system.web section add the following modules and handlers
  • <httpHandlers>
          <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
        </httpHandlers>
        <httpModules>
          <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah"/>
          <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
          <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
        </httpModules>
  • Under the system.webServer section, add the following modules and handlers
  • <handlers>
          <add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" preCondition="integratedMode" type="Elmah.ErrorLogPageFactory, Elmah"/>
        </handlers>
    
        <modules runAllManagedModulesForAllRequests="true">
          <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah"/>
          <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
          <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
        </modules>
  • Create the logging table in your database by running the following SQL
  • --~Changing index [dbo].[ELMAH_Error].PK_ELMAH_Error to a clustered index.  You may want to pick a different index to cluster on.
    SET ANSI_NULLS ON
    SET QUOTED_IDENTIFIER ON
    IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ELMAH_Error]') AND type in (N'U'))
    BEGIN
    CREATE TABLE [dbo].[ELMAH_Error](
        [ErrorId] [uniqueidentifier] NOT NULL,
        [Application] [nvarchar](60) NOT NULL,
        [Host] [nvarchar](50) NOT NULL,
        [Type] [nvarchar](100) NOT NULL,
        [Source] [nvarchar](60) NOT NULL,
        [Message] [nvarchar](500) NOT NULL,
        [User] [nvarchar](50) NOT NULL,
        [StatusCode] [int] NOT NULL,
        [TimeUtc] [datetime] NOT NULL,
        [Sequence] [int] IDENTITY(1,1) NOT NULL,
        [AllXml] [nvarchar](max) NOT NULL,
     CONSTRAINT [PK_ELMAH_Error] PRIMARY KEY CLUSTERED 
    (
        [ErrorId] ASC
    )WITH (STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF)
    )
    END
    
    IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[ELMAH_Error]') AND name = N'IX_ELMAH_Error_App_Time_Seq')
    CREATE NONCLUSTERED INDEX [IX_ELMAH_Error_App_Time_Seq] ON [dbo].[ELMAH_Error] 
    (
        [Application] ASC,
        [TimeUtc] DESC,
        [Sequence] DESC
    )WITH (STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF)
    GO
    IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_ELMAH_Error_ErrorId]') AND type = 'D')
    BEGIN
    ALTER TABLE [dbo].[ELMAH_Error] ADD  CONSTRAINT [DF_ELMAH_Error_ErrorId]  DEFAULT (newid()) FOR [ErrorId]
    END
    
    GO
    SET ANSI_NULLS ON
    SET QUOTED_IDENTIFIER ON
    IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ELMAH_GetErrorsXml]') AND type in (N'P', N'PC'))
    BEGIN
    EXEC dbo.sp_executesql @statement = N'
    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
    
    ' 
    END
    GO
    SET ANSI_NULLS ON
    SET QUOTED_IDENTIFIER ON
    IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ELMAH_GetErrorXml]') AND type in (N'P', N'PC'))
    BEGIN
    EXEC dbo.sp_executesql @statement = N'
    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
    
    ' 
    END
    GO
    SET ANSI_NULLS ON
    SET QUOTED_IDENTIFIER ON
    IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ELMAH_LogError]') AND type in (N'P', N'PC'))
    BEGIN
    EXEC dbo.sp_executesql @statement = N'
    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
            )
    
    ' 
    END
    GO
    
  • To test it, go to http://yoursite/elmah.axd


Keywords: Windows Azure Exception Handling Logging

0 comments:

Post a Comment

I always welcome feedback from my readers.