C#組件系列——又一款日誌組件:Elmah的學習和分享

前言:很久沒動筆了,都有點生疏,12月都要接近尾聲,但是這月連一篇的產出都沒有,不能壞了「規矩」,今天仍是來寫一篇。最近個把月確實很忙,不過天天早上仍是會抽空來園子裏逛逛。一如既往,園子裏每一年這個時候都有大把的年終總結、回憶過去展望將來之類的文章。博主是沒時間寫總結了,要學的東西太多。關於Vue的系列必定要抽時間補上。最近剛用了一個日誌組件Elmah,比較適合開發階段異常信息的快速定位與追溯,有興趣的跟着博主一塊兒來看看吧。javascript

本文原創地址:http://www.cnblogs.com/landeanfen/p/6221403.htmlhtml

 1、組件介紹

ELMAH的全稱是The Error Logging Modules And Handlers,翻譯過來是錯誤日誌模塊和處理。顧名思義,就是一個日誌的攔截和處理組件,說到.net的日誌組件,你們的第一反應該是Log4Net、NLog等這些東西,關於Log4Net和NLog,能夠說是.net日誌組件裏面使用最爲普遍的組件了,它們功能強大、使用方便。相比它們:前端

一、ELMAH的使用更加簡單,它甚至不用寫一句代碼,只須要引入dll,而後在Web.config裏面配置相應的節點便可;java

二、按照網上的說法,ELMAH是一種「可拔插式」的組件,即在一個運行的項目裏面咱們能夠隨意輕鬆加入日誌功能,或者移除日誌功能;jquery

三、ELMAH組件自帶界面,不用寫任何代碼,便可查看異常日誌的界面,輕鬆找到當前異常的詳細信息;和web的結合更加緊密;web

四、組件提供了一個用於集中記錄和通知錯誤日誌的機制,經過郵件的機制通知錯誤信息給相關人員。ajax

2、組件安裝使用

一、安裝組件

 Elmah的安裝使用一樣也很簡單,咱們萬能的Nuget幫咱們一鍵搞定。sql

安裝如上圖的兩個組件便可在MVC項目裏面應用起來。注意這裏有一個依賴關係Elmah.MVC依賴Elmah.corelibrary組件。數據庫

安裝成功後,項目中會添加以下兩個dll的引用。安全

二、配置組件

組件安裝成功以後,會自動在咱們MVC項目的Web.config裏面添加以下節點:

<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>

......

<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> ...... <httpModules> <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" /> <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" /> <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" /> </httpModules> ...... <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>

除此以外,還須要手動在Web.config裏面加入以下配置節點:

<elmah>
    <security allowRemoteAccess="false" />
    <!--三種存儲方式:內存、xml、數據庫。存儲到xml裏面格式以下行-->
    <!--<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/Static/Log/" />-->
    <!--<errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ElmahConn" />-->
</elmah>
  <location path="elmah.axd" inheritInChildApplications="false">
    <system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>
    </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>

須要說明的是在elmah節點裏面能夠配置組件支持的三種日誌存儲方式:

  • 若是elmah節點不配置任何任何東西,表示默認組件是將日誌信息保存到內存中,這種方式的弊端在於一旦系統重啓,全部的異常信息都會丟失;
  •  <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/Static/Log/" /> 表示日誌保存到xml文件裏面,第二個屬性配置保存xml的路徑;
  •  <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ElmahConn" /> 表示日誌保存到數據庫裏面,第二個參數對應數據庫的連接字符串的name,也就是說若是配置保存到數據庫,則必需要在web.config裏面配置鏈接字符串,這裏的connectionStringName就對應咱們配置的鏈接字符串的name,而且這個時候還須要使用腳本在數據庫裏面新建須要的表和存儲過程,這個放在後面說。

三、測試效果

經過以上安裝和配置,咱們便可記錄異常。首先咱們採用內存的方式來記錄異常,先來看看效果:

public class HomeController : Controller
{
        public ActionResult Index()
        {
            return View();
        }
        
        //測試異常
        public JsonResult Get()
        {
            var a = 0;
            var b = 5;
            var c = b / a;
            return Json(a, JsonRequestBehavior.AllowGet);
        }
}

而後咱們前端ajax來調用

<html>
<head>
    <title>Index</title>
    <script src="~/Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#btn").click(function () {
                $.ajax({
                    type: 'get',
                    url: '/Home/Get',
                    data: {},
                });
            });
        });
    </script>
</head>
<body>
    <h1>首頁</h1>
    <div> 
        <button type="button" id="btn">測試異常</button>
    </div>
</body>
</html>

點擊按鈕出現異常,而後咱們經過地址http://localhost:51230/elmah.axd來查看異常信息。

點擊展開詳細信息以下

 到這一步,咱們的組件就能夠生效了。可是看到這個elmah.axd這個路徑太噁心了,咱們想要改一下,用咱們本身的路徑,呵呵,這個確實是能夠配置的。咱們再來看看web.config裏面的location節點,在location節點裏面其實就配置一個東西——HttpHandler,雖然有 system.web 和 system.webServer 兩個節點,若是你仔細觀察,其實它們是一個東西,只不過是爲了兼容不一樣的IIS版本而寫了兩個配置,這一點和咱們HttpHandler的配置是相同的,對於這個配置不熟悉的,能夠看看博主以前的文章http://www.cnblogs.com/landeanfen/p/6000978.html。咱們將location的節點改爲這樣:

  <location path="log.axd" inheritInChildApplications="false">
    <!--<system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="log.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>
    </system.web>-->
    <system.webServer>
      <handlers>
        <add name="ELMAH" verb="POST,GET,HEAD" path="log.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
      </handlers>
    </system.webServer>
  </location>

而後咱們經過http://localhost:51230/log.axd這個地址來訪問,能達到一樣的效果。博主本地使用的是IIS經典模式,因此使用的是system.webServer裏面的配置,註釋掉或者刪掉system.web節點都不會有任何問題。

3、功能介紹

一、將日誌信息保存到數據庫

上述使用在內存中保存日誌信息的方式,在實際項目中基本上不多會用到。除此以外,還有保存到xml和數據庫兩種方式。保存到xml文件這個沒什麼好說的,就是配置一下保存路徑便可。下面就以保存到數據庫的方式來看看想一想介紹下。

首先咱們在web.config裏面加入鏈接字符串節點。

  <connectionStrings>
    <add name="ElmahConn" connectionString="Data source=127.0.0.1;Initial Catalog=PowerMangent;Persist Security Info=True;User ID=sa;Password=123456" providerName="System.Data.EntityClient" />
    <add name="ElmahContainer" connectionString="metadata=res://*/Elmah.csdl|res://*/Elmah.ssdl|res://*/Elmah.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=127.0.0.1;initial catalog=PowerMangent;user id=sa;password=123456;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>

而後配置elmah節點

  <elmah>
    <security allowRemoteAccess="false" />
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ElmahConn" />
  </elmah>

最後就是在數據庫建立須要的表和存儲過程,官方提供了腳本,這個不用咱們擔憂,腳本以下

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
CreateElmah.sql

 作了以上三步以後,程序裏面的異常就能記錄到數據庫裏面,程序再次啓動的時候會自動從數據庫裏面去取對應的信息。

二、程序「吃掉」異常

若是你反編譯elmah組件,你會它的原理其實就是經過配置的方式經過HttpModule註冊應用程序的Error事件,而後統一處理記錄。既然是註冊的Application_Error事件,那麼確定就存在某些狀況會吃掉異常。

(1)在程序裏面try...catch...

這種狀況很好理解,若是你再代碼裏面顯示的聲明瞭try...catch那麼異常確定不會進到Application_Error事件裏面去,組件也不會記錄異常。

(2)在異常捕獲器裏面處理了異常

除了使用try吃掉異常以外,不少系統裏面都會使用異常捕獲器去統一捕獲異常,若是再異常捕獲器裏面設置過異常已經處理,組件也不會記錄異常。好比:

    protected override void OnException(ExceptionContext filterContext)
        {
            //若是加了這一句,表示異常已經處理,不會盡到應用程序的Error事件裏面去
            filterContext.ExceptionHandled = true;
 
           //........

            base.OnException(filterContext);
        }

這就是異常捕獲器統一處理異常,既然這裏標識了異常已經處理過,那麼組件確定不會再次處理。但是有些狀況下,咱們須要處理某些自定義異常,而對於系統異常咱們仍是但願組件可以記錄,這種狀況下怎麼辦呢?其實很簡單,這裏只須要判斷一下,若是是自定義異常信息,這裏就加上 filterContext.ExceptionHandled = true; 這一句,而對於其餘系統異常,則統一加上這一句便可。這裏仍是作一個簡單的演示供須要的園友參考。

    public class MyException : Exception
    {
        public MyException(string message) : base(message)
        { }
    }

知足必定條件則拋出自定義異常

        public JsonResult Get()
        {
            if (DateTime.Now > Convert.ToDateTime("2016-12-15 10:00:00"))
            {
                //若是知足某些條件則拋出異常
                throw new MyException("當前時間已過時");
            }

            return Json("OK", JsonRequestBehavior.AllowGet);
        }

而後再全局異常處理裏面

        protected override void OnException(ExceptionContext filterContext)
        {
            if (filterContext.Exception is MyException)
            {
                //若是加了這一句,表示異常已經處理,不會進到應用程序的Error事件裏面去
                filterContext.ExceptionHandled = true;
            }

            base.OnException(filterContext);
        }

這樣就能達到咱們系統異常記錄,自定義異常不記錄的目的了。

三、組件權限問題

 關於elmah組件,被人詬病的一個重要緣由就是其安全問題,若是控制很差很容易招到他人入侵。大神湯姆大叔有篇文章記錄這個,有興趣能夠看看。關於咱們/elmah.axd路徑,咱們確定是須要作一些限制,不能容許每一個人都去查看,下面從如下幾個方面來完善。

(1)拒絕遠程訪問

在web.config裏面有一個節點配置不容許遠程訪問。

  <elmah>
    <security allowRemoteAccess="false" />
  </elmah>

(2)拒絕匿名用戶訪問

對於沒有登陸到系統的用戶,拒絕訪問。這個能夠在IIS上面配置,固然,咱們在web.config裏面也要作相應的配置

<location path="log.axd" inheritInChildApplications="false">
    <system.web>
      <authorization>
           <deny users="?"/>
        </authorization>
    </system.web>
  </location>

(3)指定角色或用戶訪問

 能夠在應用程序全局配置文件Global.asax裏面的認證事件裏面去作判斷。

protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            // 處理日誌權限問題
            if (Request.Url.ToString().Contains("elmah.axd"))
            {
                var user = app.Context.User;
                if (user == null)
                {
                    Response.Write("無權限訪問");
                    Response.End();
                }
                var userData = (UserInfo)user;
                //管理員角色才能查看
                if (!userData.UserData.IsAdmin)
                {
                    Response.Write("無權限訪問");
                    Response.End();
                }
  
                //或者指定用戶才能訪問
               //if (userData.UserData.UserName!="administrator")
                //{
                   // Response.Write("無權限訪問");
                   //Response.End();
                //}
            }
        }

4、總結

以上總結了組件Elmah組件的使用和一些常見問題的處理。有興趣的能夠看看。歡迎推薦

本文原創出處:http://www.cnblogs.com/landeanfen/

歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索