2011年10月10日 星期一

ASP.NET MVC + ELMAH 監控並記錄你的網站錯誤資訊 4

前面幾篇文章有關 ELMAH 的文章都是把錯誤訊息記錄在「~/App_Data/Elmah.Errors」下,然後一個錯誤就是一個 XML 檔案,這樣做的原因是可以避免因為資料庫無法連線時而造成錯誤訊息無法記錄的意外,而本篇文章將會說明如何將 ELMAH 的錯誤訊息的儲存媒體改為使用 SQL Server



如果說要做資料分析的話,其實也可以把錯誤訊息給匯出,匯出的格式為CSV檔案,

image

image

用Excel開啟errorlog.csv查看內容,

image

其實也是可以用這個方式來篩選資料出來,但一旦還是有人會覺得這樣的方式太不直接,所以希望把錯誤訊息記錄到SQL Server之中,可以使用SSMS來去篩選資料,而ELMAH置換記錄媒體的方式也相當簡單,不需要去更動到程式的部份,只需要去修改Web.Config就可以了。


首先我會建議,像這樣的錯誤訊息記錄,最好是要與網站所使用的資料庫是分開的,在同一個Server是可以,但是DB絕對要分開,讓系統使用的功能與網站的Model切分開來,不要混在一起,如果可以的話,錯誤訊息記錄也最好是放至於另外一台SQL Server,必須要將各種可能發生錯誤導致無法將錯誤訊息完整記錄的機率降到最低,讓程式開發人員、維護人員能儘量掌握完整的資訊。

 

官網提供的SQL Script

我們先到 Elmah 的官網,去找官網所提供的 SQL Script

SQLServer.sql :http://elmah.googlecode.com/svn/trunk/src/Elmah/SQLServer.sql

/*
  
   ELMAH - Error Logging Modules and Handlers for ASP.NET
   Copyright (c) 2004-9 Atif Aziz. All rights reserved.
  
    Author(s):
  
        Atif Aziz, http://www.raboof.com
        Phil Haacked, http://haacked.com
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
  
      http://www.apache.org/licenses/LICENSE-2.0
  
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
  
*/
 
-- ELMAH DDL script for Microsoft SQL Server 2000 or later.
 
-- $Id$
 
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:
    
    http://code.google.com/p/elmah/issues/list
 
    ===========================================================================
'
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

這一段SQL Script會建立一個Table與三個Stored Procedure:

image

接下來就是在Web.Config中的connectionStrings區段再去新增connectionStrings:

  <connectionStrings>
    <add name="NorthwindEntities" connectionString="metadata=res://*/Northwind.csdl|res://*/Northwind.ssdl|res://*/Northwind.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=你的Data-Source;initial catalog=Northwind;persist security info=True;user id=登入帳號;password=登入密碼;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
    <add name="Elmah.Sql" connectionString="data source=你的Data-Source;initial catalog=ElmahError;persist security info=True;user id=登入帳號;password=登入密碼;Trusted_Connection=True" />
  </connectionStrings>

要記住這個connectionString的名稱「Elmah.Sql」

 

修改Web.Config

然後就是修改Web.Config的Elmah區段,

  <elmah>
      <!--<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/Elmah.Errors" />-->
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="Elmah.Sql" />
    <security allowRemoteAccess="yes" />

原本使用errorLog為XmlFileErrorLog的資料給註解起來,然後增加Elmah.SqlErrorLog的設定,而connectionStringName就是之前所增加的連結字串名稱,修改好之後,重新建置網站,然後執行網站並且執行一個錯誤,接這就是看看資料庫ElmahError有沒有確實記錄錯誤訊息?

image

可以看到確實有將錯誤訊息給記錄到資料庫之中,ELmah更換儲存媒體並不會很麻煩,應該說是相當無痛且簡單,完全不會有任何複雜的操作。

 

2013-09-12 補充:

這邊會建議各位如果可以透過 NuGet 安裝 ELMAHELMAH on MS SQLServer

image

安裝 ELMAH on MS SQL Server 後,在專案有新增 App_Readme 目錄,目錄下有「Elmah.SqlServer.sql」檔案,

image

這個 Elmah.SqlServer.sql 內容與官網所提供的 SQL Script 大致相同,不過我會建議使用官網所提供的 SQL Script,部分欄位型別有更換比較適當的型別,例如使用 NVARCHAR(MAX) 而不是使用 NTEXT 等。

 

 

延伸閱讀:

Elmah - Storing errors in SQL Server

Elmah - SQLServer.sql

Elmah - OracleErrorLog (Using ELMAH with Oracle)

Elmah - Oracle.sql

 

以上

6 則留言:

  1. sql file response 404,
    update link:
    http://elmah.googlecode.com/svn/trunk/src/Elmah/SQLServer.sql

    回覆刪除
  2. 請問改成SQL去存放錯誤訊息之後,原本的ELMAH頁面會開不起來嗎?

    回覆刪除
    回覆
    1. 如果一切按照步驟去做,是可以正常開啟的
      要是真的有這個問題的話,網路上早就一堆這樣的問題與討論,我也不會寫文章介紹啦
      你給 Elamh 的資料庫連接字串與名稱是否正確,你的 SQL Server 是否允許對外連線
      是否有自己去修改過 Elmah 給的 Sql Schema 內容
      我沒有天眼通,所以無法依據你的隻字片語去協助你解決問題

      刪除
    2. 我也覺得應該可以正常開啟才對啊啊啊啊啊啊
      感謝回覆,我再重新檢視一下我的SQL,謝謝Kevin!

      刪除
  3. ELMAH 搬到 Github 囉
    http://elmah.github.io/

    回覆刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。