第16章 ASP.NET MVC 日志篇
本章主要介绍MVC中内置的错误处理、日志以及用来提升性能的监控工具
一、错误处理
当该网站忙于处理HTTP请求时,很多内容都会出错。幸运的是,MVC让错误处理工作变得相对简单了很多,因为MVC应用是运行在MVC框架之上的,所以可以访问底层框架的核心功能,包括自定义错误处理页面及显示错误状态码。
其中处理错误有三种基本的方法:
(1)第一种:配置<customErrors/>节点
也是最简单的一种,即启用MVC自定义错误处理特性;就是在web.config文件中配置<customErrors/>节点来开启MVC自定义错误处理特性。只需要将<customErrors/>节点的Mode属性设置为On和RemoteOnly即可开启。
Model属性开启了三种模式:On、Off、RemoteOnly;
On是启用自定义错误处理功能,当发生错误时可显示不同的自定义错误处理页面。
Off是关闭自定义错误处理功能,即无论发生什么样的错误,都显示默认的错误诊断页面(通常的黄底红字的错误页面)。
RemoteOnly也是启用自定义错误处理功能,但是是只针对来自远程机器的请求有效,如果是本地机器访问则还是会报黄底红字的错误页面这样有助于根据错误诊断来调试网站,用户则会看到自定义的错误页面。这里有必要说明一下本地用户和远程用户的概念。当我们访问asp.net应用程时所使用的机器和发布asp.net应用程序所使用的机器为同一台机器时成为本地用户,反之则称之为远程用户。在开发调试阶段为了便于查找错误Mode属性建议设置为Off,而在部署阶段应将Mode属性设置为On或者RemoteOnly,以避免这些详细的错误信息暴露了程序代码细节从而引来黑客的入侵。
在<customErrors>节点下还包含有<error>子节点,这个节点主要是根据服务器的HTTP错误状态代码而重定向到我们自定义的错误页面,注意要使<error>子节点下的配置生效,必须将<customErrors>节点的Mode属性设置为“On”
- <system.web>
- <customErrors mode="On" defaultRedirect="">
- <error statusCode="500" redirect="~/Error/500.html"/>
- <error statusCode="404" redirect="~/Error/404.html"/>
- <error statusCode="403" redirect="~/Error/403.html"/>
- </customErrors>
- </system.web>
(2)第二种:重写controller类的onException方法
重写controller类的onException方式,这种方式最直接了,通常用于一个项目的BaseController中,那么以后的controller都继承这个类即可,可以在cs代码中记录错误日志,但是要定义错误页的action和具体的错误页面。方法中需要设置ExceptionHandled=true,否则错误会被抛到外层,这时候只能通过传统的aspnet错误方式处理了,通常是找<customErrors>节点中配置的错误页,如果没有配置,那就出现一个大黄页了。ExceptionHandled=true这个操作会使逻辑有微妙的变化,后续提到。
- protected override void OnException(ExceptionContext filterContext)
- {
- // 标记异常已处理
- filterContext.ExceptionHandled = true;
- // 跳转到错误页
- filterContext.Result = new RedirectResult(Url.Action("Error", "Shared"));
}
(3)第三种:用过滤器HandleErrorAttribute
虽然启用自定义错误处理功能是可以在网站发生错误的时候显示自定义错误页面,但是有时候只简单的显示自定义错误信息是不够的。随着ASP.NET MVC版本的更新,提供了HandleErrorAttribute标记属性,提供了对操作级别发生错误更细粒度的控制。HandleErrorAttribute使用Filter以AOP的思想实现了针对于Action的异常处理,使用此Filter后,当程序中出现异常的时候,会去封装这些异常信息,然后路由自动转到该文件夹对应的错误页面中,如果此路径下没有改文件,则会到共享视图文件夹shared目录中寻找此文件。
需要注意的是,HandleErrorAttribute是在customErrors基础之上的,如果想使用HandleErrorAttribute,customErrors的Mode必须要设置为On或RemoteOnly. 否则,HandleErrorAttribute将不起作用
HandleErrorAttribute属性:ExceptionType、View、Order、Master。
ExceptionType是要处理异常的类型;
View是发生该异常时要显示的视图名称;
Order是执行该异常的顺序;
Master是异常信息要使用的母版视图;
HandleErrorAttribute可以作用于Action,也可以作用于Controller还可以设置为全局错误处理器,其使用方法:
- //作用于Action
- [HandleError(ExceptionType = typeof(System.Data.DataException), View = "Error.cshtml")]
- public ActionResult Index()
- {
- return View();
- }
- //作用于Controller
- [HandleError(ExceptionType = typeof(System.Data.DataException), View = "Error.cshtml")]
- public class ErrorController : Controller
- {
- //
- // GET: /Error/
- public ActionResult Index()
- {
- return View();
- }
- public ActionResult Error()
- {
- throw new Exception("find a exception");
- }
- }
为了注册全局错误处理器,打开项目下的App_Start/FilterConfig.cs文件,然后找到RegisterGlobalFilters方法,就可以看到ASP.NET MVC已经在GlobalFilterCollection全局过滤器集合中注册了HandleErrorAttribute。如果需要自定义逻辑,只要把自定义过滤器注册到全局过滤器集合中即可,默认情况下全局过滤器会按照他们的注册顺序执行,所以一定要确保在其他错误过滤器之前注册特定异常类型的错误过滤器,也可使用Order来控制器顺序。代码如下:
- public class FilterConfig
- {
- public static void RegisterGlobalFilters(GlobalFilterCollection filters)
- {
- //注册自定义全局错误
- filters.Add(new HandleErrorAttribute() { ExceptionType = typeof(System.Data.DataException), View = "Error.cshtml",Order= });//这里的View的视图文件应该放到当前请求的文件夹或共享视图文件里
- filters.Add(new HandleErrorAttribute());//初始化网站全局错误处理器
- }
- }
注:如果使用的是HandleErrorAttribute,则此时会忽略web.config里面设置的defaultRedirect和状态码重定向。
如果以某action方法标记了HandleError属性,同时期所在的controller又重写了OnException方法,最终会怎样处理呢?按照mvc中filter的执行顺序,controller重写的方法会被优先执行,不考虑action中的order顺序,执行完毕之后再执行action标记的filter的方法。ok,有了这个理论之后,再看看之前提到的情况的执行顺序。首先执行OnException中的处理方式,这时候filterContext.ExceptionHandled已经被标记为true了,再执行HandleError属性的方法时,就不会在被执行了,也就是说自定义的错误页白费了,不起作用。这是因为内置的HandleError在执行的时候会先判断filterContext.ExceptionHandled是否为true,为true就不执行了,因此会出现一些很奇怪的bug,明白这个道理就知道如何处理了。
但是总不能把filterContext.ExceptionHandled = true;这行代码去掉,因为其他action没有标记handle error属性,如果不使filterContext.ExceptionHandled为true, 那么错误还是会抛到外层,又交给CustomerError处理了,还是白搭。因此既要保持基类的OnException方法,又要有action自己个性化的错误页,是不能使用系统内置的方式处理,只能自己再去定义ExceptionFilter 了,就是方式四。
(4)第四种:在Global.asax中的protected void Application_Error(object sender, EventArgs e)方法
另外一个相关的是在Global.asax中的protected void Application_Error(object sender, EventArgs e)方法,是捕捉异常的最后一道防线,也就是说,这是最高层次的异常捕获处理逻辑。如果在使用HandleErrorAttribute后,找到了Error.cshtml,则此时异常已经被捕获处理,所以不会再次被Application_Error捕获处理,当出现异常的时候,把异常抛到最顶端,由Application_Error统一处理。这里的统一处理就包括记录日志,重新进行页面定向等。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- namespace LiBlogWebUI.Controllers
- {
- public class ErrorController : Controller
- {
- //
- // GET: /Error/
- public ActionResult Index()
- {
- return View();
- }
- public ActionResult NotFound()
- {
- throw new Exception("find a exception");
- }
- public ActionResult Error()
- {
- return View();
- }
- }
- }
- using LiBlogWebUI.Controllers;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Http;
- using System.Web.Mvc;
- using System.Web.Routing;
- namespace LiBlogWebUI
- {
- // Note: For instructions on enabling IIS6 or IIS7 classic mode,
- // visit http://go.microsoft.com/?LinkId=9394801
- public class MvcApplication : System.Web.HttpApplication
- {
- protected void Application_Start()
- {
- AreaRegistration.RegisterAllAreas();
- WebApiConfig.Register(GlobalConfiguration.Configuration);
- FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
- RouteConfig.RegisterRoutes(RouteTable.Routes);
- }
- //当出现异常的时候,把异常抛到最顶端,由Application_Error统一处理。这里的统一处理就包括,记录日志,重新进行页面定向等。
- protected void Application_Error(object sender, EventArgs e)
- {
- var ex = Server.GetLastError();
- Log.Error(ex); //记录日志信息
- var httpStatusCode = (ex is HttpException) ? (ex as HttpException).GetHttpCode() : ; //这里仅仅区分两种错误
- var httpContext = ((MvcApplication)sender).Context;
- httpContext.ClearError();
- httpContext.Response.Clear();
- httpContext.Response.StatusCode = httpStatusCode;
- var shouldHandleException = true;
- HandleErrorInfo errorModel;
- var routeData = new RouteData();
- routeData.Values["controller"] = "Error";
- switch (httpStatusCode)
- {
- case :
- routeData.Values["action"] = "NotFound";
- errorModel = new HandleErrorInfo(new Exception(string.Format("No page Found", httpContext.Request.UrlReferrer), ex), "Error", "NotFound");
- break;
- default:
- routeData.Values["action"] = "Error";
- Exception exceptionToReplace = null; //这里使用了EntLib的异常处理模块的一些功能
- shouldHandleException = ExceptionPolicy.HandleException(ex, "LogAndReplace", out exceptionToReplace);
- errorModel = new HandleErrorInfo(exceptionToReplace, "Error", "Error");
- break;
- }
- if (shouldHandleException)
- {
- var controller = new ErrorController();
- controller.ViewData.Model = errorModel; //通过代码路由到指定的路径
- ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
- }
- }
- }
- }
二、日志
当网站发生错误时,就需要尽可能多的信息来帮助跟踪、调试错误。虽然现实错误信息是一种很好的通知用户的方式,但是它并不能帮助开发人员去处理问题,为了更好地找出网站的出错原因,可以在网站中记录一些必要的信息以及引起错粗时进行的操作,可以使用日志来记录 。
首先可以定义一个LogHelper类,里面写关于记录错误日志的代码,然后需要调用的时候,直接使用就ok了。代码如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace System
- {
- public class LogHelper
- {
- private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
- public static void LoadConfig(string path)
- {
- log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(path));
- }
- /// <summary>
- /// 错误信息
- /// </summary
- public static void Error(string error)
- {
- log.Error(error);
- }
- /// <summary>
- /// 致命信息
- /// </summary>
- public static void Fatal(string fatal)
- {
- log.Fatal(fatal);
- }
- /// <summary>
- /// 一般信息
- /// </summary>
- public static void Info(string info)
- {
- log.Info(info);
- }
- /// <summary>
- /// 警告信息
- /// </summary>
- public static void Warn(string warn)
- {
- log.Warn(warn);
- }
- }
- }
定义好LogHelper类以后,还需要在Global.ascx文件中添加代码:
- LogHelper.LoadConfig(Server.MapPath("~/Config/log4net.config"));
(1)使用try catch语句来记录错误
- public ActionResult NotFound()
- {
- try
- {
- throw new Exception("find a exception");
- }
- catch (Exception ex)
- {
- LogHelper.Error(ex);//自定义的记录日志的类
- }
return View();
}
(2)重写controller类的onException方法
- protected override void OnException(ExceptionContext filterContext)
- {
- if (filterContext == null)
- base.OnException(filterContext);
- if (filterContext.HttpContext.IsCustomErrorEnabled)//如果启用了全局错误过滤器,就不执行下面的代码
- {
- filterContext.ExceptionHandled = true;
- this.View("Error").ExecuteResult(this.ControllerContext);
- }
- }
(3)自定义错误过滤器
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- namespace LiBlogWebUI.Filter
- {
- public class ExceptionFilter : HandleErrorAttribute
- {
- public override void OnException(ExceptionContext filterContext)
- {
- if (filterContext == null)
- base.OnException(filterContext);
- LogHelper.Error(filterContext.Exception);//自定义的记录日志的类
- if (filterContext.HttpContext.IsCustomErrorEnabled)//如果启用了全局错误过滤器,就不执行下面的代码
- {
- filterContext.ExceptionHandled = true;
- base.OnException(filterContext);
- }
- }
- }
- }
三、ASP.NET健康监控
虽然记录事件日志是监控网站很好的开始,但是一种更好的选择是启用ASP.NET健康监控(ASP.NET health monitoning)功能。ASP.NET健康监控(ASP.NET health monitoning)功能远远超出记录异常日志的范畴,而且还可以记录应用程序或请求生命周期内发生的事件。
ASP.NET健康监控系统监控以下事件:
(1)应用程序生命周期事件,包括应用程序开始和停止的事件
(2)安全事件,例如,登录失败、URL授权请求
(3)应用程序错误,包括未处理的异常、请求验证异常、编译错误等
ASP.NET健康监控可以在网站的配置文件web.config的<healthMonitoring/>节点配置,这个节点包含三个子节点。
eventMappings:定义要监控的事件类型
providers:定义可用的提供者
rules:定义在事件和提供者之间的用来记录事件的映射关系
- <system.web>
- <healthMonitoring>
- <eventMappings>
- <clear/>
- <!--记录所有的错误事件-->
- <add name="All Errors" type="System.Web.Management.WebBaseErrorEvent" startEventCode="" endEventCode=""/>
- <!--记录应用程序开始和停止事件-->
- <add name="Application Events" type="System.Web.Management.WebApplicationLifetimeEvent" startEventCode="" endEventCode=""/>
- </eventMappings>
- <providers>
- <clear/>
- <add connectionStringName="sqlCon" maxEventDetailsLength="" buffer="false" type="System.Web.Management.SqlWebEventProvider" name="SqlWebEventProvider"/>
- </providers>
- <rules>
- <clear/>
- <add name="All Errors Default" eventName="All Errors" provider="SqlWebEventProvider" profile="Default" minInstances="" maxLimit="Infinite" minInterval="00:00:00"/>
- <add name="Application Events Default" eventName="Application Events" provider="SqlWebEventProvider" profile="Default" minInstances="" maxLimit="Infinite" minInterval="00:00:00"/>
- </rules>
- </healthMonitoring>
- </system.web>
ASP.NET健康监控包含把记录信息保存到Microsoft SQl Server数据库、本地事件日志,以及通过Email通知管理员等几种不同的提供者。当然它也允许我们创建自定义的提供者来记录到其他的数据源上。为了使用微软的SQLServer数据库健康监控提供者,需要在网站数据库的表中添加一些表。可以直接使用.NET命令里的aspnet_regsql.exe工具来实现。
既然已经启用了ASP.NET健康监控功能,现在就来修改之前的自定义错误过滤器,把异常信息保存到健康监控提供者设置的数据源里。
因为健康监控系统的System.Web.Management.WebRequestErrorEvent类没有任何公开的构造函数,所以,必须创建一个自定义web请求错误事件类,代码如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Management;//需要引入此命名空间
- namespace LiBlogWebUI.Filter
- {
- public class WebRequestExceptionEvent : WebRequestErrorEvent
- {
- public WebRequestExceptionEvent(string message, object eventSource, int eventCode, Exception exception)
- : base(message, eventSource, eventCode, exception)
- { }
- public WebRequestExceptionEvent(string message, object eventSource, int eventCode, int eventDetailCode, Exception exception)
- : base(message, eventSource, eventCode, eventDetailCode, exception)
- { }
- }
- }
在创建完这个类后,修改ExceptionFilter类以调用这个自定义Web请求错误类:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- namespace LiBlogWebUI.Filter
- {
- public class ExceptionFilter : HandleErrorAttribute
- {
- public override void OnException(ExceptionContext filterContext)
- {
- if (filterContext.HttpContext.IsCustomErrorEnabled)//如果启用了全局错误过滤器,就不执行下面的代码
- {
- base.OnException(filterContext);
- new WebRequestExceptionEvent("An unhandled exception han occurred",this,,filterContext.Exception).Raise();//通过将事件已发生这一情况通知任何已配置的提供程序来引发事件。
- }
- }
- }
- }
代码已写完,设置好并作为全局错误过滤器进行注册后,ASP.Net MVC网站所有的异常信息都会被路由到ASP.NET健康监控系统中,并且日志被保存起来
参考文献:1、《ASP.NET MVC4 Web编程》 著:Jess Cbadwick,Todd Snyder,Hrusikesb Panda 译:徐雷 徐杨
2、http://blog.csdn.net/sundacheng1989/article/details/9000596
第16章 ASP.NET MVC 日志篇的更多相关文章
- 《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第四章 ASP.NET MVC中使用实体框架 ASP.NET是一个免费的Web框架 ...
- ASP.NET MVC学前篇之扩展方法、链式编程
ASP.NET MVC学前篇之扩展方法.链式编程 前言 目的没有别的,就是介绍几点在ASP.NETMVC 用到C#语言特性,还有一些其他琐碎的知识点,强行的划分一个范围的话,只能说都跟MVC有关,有的 ...
- ASP.NET MVC学前篇之Lambda表达式、依赖倒置
ASP.NET MVC学前篇之Lambda表达式.依赖倒置 前言 随着上篇文章的阅读,可能有的朋友会有疑问,比如(A.Method(xxx=>xx>yy);)类似于这样的函数调用语句,里面 ...
- ASP.NET MVC学前篇之Ninject的初步了解
ASP.NET MVC学前篇之Ninject的初步了解 1.介绍 废话几句,Ninject是一种轻量级的.基础.NET的一个开源IoC框架,在对于MVC框架的学习中会用到IoC框架的,因为这种IoC开 ...
- ASP.NET MVC学前篇之请求流程
ASP.NET MVC学前篇之请求流程 请求流程描述 对于请求的流程,文章的重点是讲HttpApplication和HttpModule之间的关系,以及一个简单的示例实现.(HttpModule又是M ...
- Asp.Net MVC 进阶篇:路由匹配 实现博客路径 和文章路径
Asp.Net MVC 进阶篇:路由匹配 实现博客路径 和文章路径 我们要实现 通过路由 匹配出 博客地址 和博客文章地址 例如下面的这两个地址 //http://www.cnblogs.com/ma ...
- Asp.Net Mvc日志处理
/// <summary> /// 日志处理帮助类 /// </summary> public class LogHelper { private static Queue&l ...
- Log4net入门(ASP.NET MVC 5篇)
在前4篇Log4net入门文章中,我们讲述了log4net的一些简单用法,在这一篇中我们主要讲述如何在ASP.NET MVC 5项目中将日志信息写入SQL Server数据库中. 一.创建最简单的AS ...
- 16个ASP.NET MVC扩展点【附源码】
转载于:http://www.cnblogs.com/wupeiqi/p/3570445.html 1.自定义一个HttpModule,并将其中的方法添加到HttpApplication相应的事件中! ...
随机推荐
- Java运行报错问题——Picked up JAVA_TOOL_OPTIONS: -agentlib:jvmhook
http://blog.csdn.net/xifeijian/article/details/8830933 上述这个朋友博文提醒,可能是因为其他软件添加了JAVA_HOME的路径造成冲突.但他支持删 ...
- JS实现LOGO像雪花一样落下特效
<HTML><HEAD><TITLE>LOGO从上落下</TITLE> <SCRIPT language=JavaScript> //窗口改 ...
- linux cp复制文件 直接覆盖
命令: \cp -rf aaaa/* bbbb 复制aaa下的文件到bbb目录
- 诊断:Goldengate OGG-01163 Bad column length
故障现象: OGG- Bad column length () specified . 原因:源端修改了字段长度.虽然源端和目标端的长度已经通过DDL语句修改到一致,在extract进程未重启的情况下 ...
- mysql中InnoDB与MyISAM的区别
两者的区别: 1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成 ...
- MyBatis 的基本要素—核心配置文件
MyBatis 核心配置文件( mybatis-config.xml),该文件配置了 MyBatis 的一些全局信息,包含数据库连接信息和 MyBatis 运行时所需的各种特性,以及设置和影响 MyB ...
- Effective C++ 一些记录和思考
Effective C++ Iter 3 - 尽可能使用 const 一个反逻辑的 bitwise const class Text { ... char& operator[](std::s ...
- CCF201703-2 学生排队 java(100分)
试题编号: 201703-2 试题名称: 学生排队 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 体育老师小明要将自己班上的学生按顺序排队.他首先让学生按学号从小到大的顺序排 ...
- 利用python去调用shell命令时候的踩到的坑
shell中 True的返回值是0 False的返回值是1 Python中 True的返回值是1 False的返回值是0
- kvm virsh命令详解
[root@ok home]# virsh list Id Name State ---------------------------------------------------- 1 13sv ...