前言:很久没动笔了,都有点生疏,12月都要接近尾声,但是这月连一篇的产出都没有,不能坏了“规矩”,今天仍是来写一篇。最近个把月确实很忙,不过天天早上仍是会抽空来园子里逛逛。一如既往,园子里每一年这个时候都有大把的年终总结、回忆过去展望将来之类的文章。博主是没时间写总结了,要学的东西太多。关于Vue的系列必定要抽时间补上。最近刚用了一个日志组件Elmah,比较适合开发阶段异常信息的快速定位与追溯,有兴趣的跟着博主一块儿来看看吧。javascript
本文原创地址:http://www.cnblogs.com/landeanfen/p/6221403.htmlhtml
ELMAH的全称是The Error Logging Modules And Handlers,翻译过来是错误日志模块和处理。顾名思义,就是一个日志的拦截和处理组件,说到.net的日志组件,你们的第一反应该是Log4Net、NLog等这些东西,关于Log4Net和NLog,能够说是.net日志组件里面使用最为普遍的组件了,它们功能强大、使用方便。相比它们:前端
一、ELMAH的使用更加简单,它甚至不用写一句代码,只须要引入dll,而后在Web.config里面配置相应的节点便可;java
二、按照网上的说法,ELMAH是一种“可拔插式”的组件,即在一个运行的项目里面咱们能够随意轻松加入日志功能,或者移除日志功能;jquery
三、ELMAH组件自带界面,不用写任何代码,便可查看异常日志的界面,轻松找到当前异常的详细信息;和web的结合更加紧密;web
四、组件提供了一个用于集中记录和通知错误日志的机制,经过邮件的机制通知错误信息给相关人员。ajax
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节点里面能够配置组件支持的三种日志存储方式:
经过以上安装和配置,咱们便可记录异常。首先咱们采用内存的方式来记录异常,先来看看效果:
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节点都不会有任何问题。
上述使用在内存中保存日志信息的方式,在实际项目中基本上不多会用到。除此以外,还有保存到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="data source=127.0.0.1;initial catalog=PowerMangent;user id=sa;password=123456;MultipleActiveResultSets=True;App=EntityFramework"" 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
作了以上三步以后,程序里面的异常就能记录到数据库里面,程序再次启动的时候会自动从数据库里面去取对应的信息。
若是你反编译elmah组件,你会它的原理其实就是经过配置的方式经过HttpModule注册应用程序的Error事件,而后统一处理记录。既然是注册的Application_Error事件,那么确定就存在某些状况会吃掉异常。
这种状况很好理解,若是你再代码里面显示的声明了try...catch那么异常确定不会进到Application_Error事件里面去,组件也不会记录异常。
除了使用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路径,咱们确定是须要作一些限制,不能容许每一个人都去查看,下面从如下几个方面来完善。
在web.config里面有一个节点配置不容许远程访问。
<elmah> <security allowRemoteAccess="false" /> </elmah>
对于没有登陆到系统的用户,拒绝访问。这个能够在IIS上面配置,固然,咱们在web.config里面也要作相应的配置
<location path="log.axd" inheritInChildApplications="false"> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </location>
能够在应用程序全局配置文件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(); //} } }
以上总结了组件Elmah组件的使用和一些常见问题的处理。有兴趣的能够看看。欢迎推荐!
本文原创出处:http://www.cnblogs.com/landeanfen/
欢迎各位转载,可是未经做者本人赞成,转载文章以后必须在文章页面明显位置给出做者和原文链接,不然保留追究法律责任的权利