August 3, 2016

ASP.NET MVC5使用Elmah日记记录组件

ASP.NET MVC5使用Elmah日记记录组件

使用错误日志记录组件能方便的记录在系统运行时发生的一些错误。

快速使用

  1. 快速新建一个空的MVC5项目,用作Demo项目;
  2. 使用“NuGet”所有并安装Elmah;
  3. 安装完成后,最外层的Web.config会自动添加如下信息:
<code class="language-XML"><configSections>
    <sectionGroup name="elmah">
        <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" />
    </sectionGroup>
</configSections>

<appSettings>
    //...
    <add key="elmah.mvc.disableHandler" value="false" />
    <add key="elmah.mvc.disableHandleErrorFilter" value="false" />
    <add key="elmah.mvc.requiresAuthentication" value="false" />
    <add key="elmah.mvc.IgnoreDefaultRoute" value="false" />
    <add key="elmah.mvc.allowedRoles" value="*" />
    <add key="elmah.mvc.allowedUsers" value="*" />
    <add key="elmah.mvc.route" value="elmah" />
    <add key="elmah.mvc.UserAuthCaseSensitive" value="true" />
</appSettings>

<system.web>
    <httpModules>
    <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
    <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
    <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
</system.web>

<system.webServer>
    //...
    <modules>
        <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>
</system.webServer>
<elmah>
</elmah>

  1. 启动网站项目,这是英文没有添加Controller,所以有“未找到视图”的错误:
  2. 这时访问“http://host[:port]/elmah”可以查看相关的错误。

高级设置

配置该节点可以设置“是否允许远程访问”

<code class="language-XML"><elmah>
    <!--
    See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
    more information on remote access and securing ELMAH.
    -->
    <security allowRemoteAccess="false" />
</elmah>

Elmah也提供多种存储方式[参考]

  • Microsoft SQL Server
  • Oracle (OracleErrorLog)
  • SQLite (version 3) database file
  • VistaDB (VistaDBErrorLog); 在1.2版,不再推荐使用
  • Microsoft Access (AccessErrorLog)
  • SQL Server Compact Edition (1.2支持)
  • MySQL (1.2支持)
  • PostgreSQL (1.2支持)
  1. 修改存储方式为文件存储,添加或修改如下的配置项,确保logPath参数中的路径是存在的
<code class="language-XML"><elmah>
    <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/Static/Log/" />
</elmah>
  1. 数据库存储方式,以SQl Server为例
<code class="language-XML"><!-- 添加数据库链接字符串 -->
<connectionStrings>
    <add name="elmah-sqlserver" connectionString="server=.;database=MvcTest;user id=sa;password=111111@a" providerName="System.Data.SqlClient" />
</connectionStrings>

<!-- 告诉Elmah使用数据库链接字符串 -->
<elmah>
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="elmah-sqlserver" />
</elmah>

创建数据库表,参考官方的连接https://code.google.com/p/elmah/source/browse/src/Elmah/SQLServer.sql

<code class="language-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 
) 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

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

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

SET @StartRowIndex = @PageIndex * @PageSize + 1
SET ROWCOUNT @StartRowIndex

SELECT  
    @FirstTimeUTC = TimeUTC,
    @FirstSequence = Sequence
FROM 
    ELMAH_Error
WHERE   
    Application = @Application
ORDER BY 
    TimeUTC DESC, 
    Sequence DESC

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

SET ROWCOUNT @PageSize

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

SELECT 
    errorId, 
    application,
    host, 
    type,
    source,
    message,
    [user],
    statusCode, 
    CONVERT(VARCHAR(50), TimeUtc, 126) + 'Z' time
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 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
    )

GO
SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

简单总结一下各种方式:

  • 数据库存储方式,配置相对麻烦,但对于大规模日志的记录,效率最好;
  • 文件存储方式,配置相对简单,每日志一个文件,当数据量很大后,可能会导致巨量文件带来的效率问题;
  • 内存存储,配置最简单,但是鉴于以上原因,不应使用于生产环境。

补充

在使用Elmah过程发一下一些特点.这里需要说明一下.

Elmah是通过Http Modules 和Http Handler来记录和展示程序捕获的异常. 但是如果你在应用程序中添加异常处理模块.Try-Catch Elmah是无法记录到的.或是在Catch后在Throw出来. 在整个应用程序异常链上. 只有最终的异常抛给了Asp.net运行时Elmah组件才能捕获到并记录.

有很多人认为加入Elmah组件后能够处理应用异常.其实本质上Elmah本质上是一个日志记录工具.并没有处理异常的能力.所以如果异常发生.不会改变原来应用程序给用户体验.依然还会出现黄色页面.

在官方Note明确提到一个例外:

ELMAH捕获异常是基于HttpApplication对象的Error事件。

如果软件项目中的一些处理导致了HttpApplication事件无法被触发(比如在发生异常后,还没来得及执行Application_Error,就执行了Server.ClearError()方法,会阻止Error事件的触发,再比如,如果一个异常被try-catch捕获到,并且没有再次throw,那么异常也是不会最终触发Error事件)