第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=1 });//这里的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() : 500; //这里仅仅区分两种错误
            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 404:
                    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));
            }
        }
    }
}

二、日志

  当网站发生错误时,就需要尽可能多的信息来帮助跟踪、调试错误。虽然现实错误信息是一种很好的通知用户的方式,但是它并不能帮助开发人员去处理问题,为了更好地找出网站的出错原因,可以在网站中记录一些必要的信息以及引起错粗时进行的操作,可以使用日志来记录 。

   (1)使用try catch语句来记录错误

        public ActionResult NotFound()
        {
            try
            {
                throw new Exception("find a exception");
            }
            catch (Exception ex)
            {
                Logger.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);

            Logger.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="0" endEventCode="2147483647"/>
        <!--记录应用程序开始和停止事件-->
        <add name="Application Events" type="System.Web.Management.WebApplicationLifetimeEvent" startEventCode="0" endEventCode="2147483647"/>
      </eventMappings>
      <providers>
        <clear/>
        <add connectionStringName="sqlCon" maxEventDetailsLength="1073741823" 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="1" maxLimit="Infinite" minInterval="00:00:00"/>
        <add name="Application Events Default" eventName="Application Events" provider="SqlWebEventProvider" profile="Default" minInstances="1" 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,103005,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

时间: 2024-08-03 23:23:57

第16章 ASP.NET MVC 日志篇的相关文章

《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第四章  ASP.NET MVC中使用实体框架 ASP.NET是一个免费的Web框架,它支持3种不同的技术来创建websites(网站)和Web应用:他们分别是,Web Pages,Web Forms,和MVC.虽然MVC是一种非常流行的,有完整的用于软件开发模式理论的技术,但它在ASP.NET中却是一种新的技术. 目前最新的版本是2012年发布的ASP.NET MVC4.自从2008年发布

ASP.NET MVC学前篇之扩展方法、链式编程

前言 目的没有别的,就是介绍几点在ASP.NETMVC 用到C#语言特性,还有一些其他琐碎的知识点,强行的划分一个范围的话,只能说都跟MVC有关,有的是外围的知识,有的是包含在框架内的. MVC学前篇字样?有噱头的成分也有真实的成分,所以工欲善其事,必先利其器.器是什么?基础嘛,虽然说MVC框架中涉及到的知识很多很多也不是我一篇两篇能说完的,我能做的就是知道多少就跟大家分享多少,当然了随着时间的推移会完善这个系列. 1扩展方法 扩展方法是C# 3.0特性里的知识,它用在最多的地方是在Linq中,

ASP.NET MVC学前篇之Ninject的初步了解

1.介绍 废话几句,Ninject是一种轻量级的.基础.NET的一个开源IoC框架,在对于MVC框架的学习中会用到IoC框架的,因为这种IoC开源框架有很多,本篇的主题只有一个,就是让阅读过本篇幅的朋友逗知道IoC框架在项目中的作用,以及它的重要性. 这样做的目的是以便在以后的学习工作中选择自己中意的一个IoC框架来学习.使用,或者是自己去实现一个.好了,不废话了. 2.环境准备 1.新建个4.0Framework的一个控制台应用程序项目,名称为IoCDemo 2.在http://www.nin

ASP.NET MVC学前篇之请求流程

ASP.NET MVC学前篇之请求流程 请求流程描述 对于请求的流程,文章的重点是讲HttpApplication和HttpModule之间的关系,以及一个简单的示例实现.(HttpModule又是MVC框架的入口点) 图1 在请求到达Web服务器过后进入ASP.NET的时候是通过ASP.NET来构造出一个HttpWorkerRequest对象,HttpWorkerRequest是抽象类类型,表示着一些请求处理的信息,然后由ASP.NET中的HttpRuntime类型来调用静态函数Process

ASP.NET MVC学前篇之Lambda表达式、依赖倒置

前言 随着上篇文章的阅读,可能有的朋友会有疑问,比如(A.Method(xxx=>xx>yy);)类似于这样的函数调用语句,里面的xxx=>xx>yy这些到底是怎么用的? 依赖倒置原则的实现也会在本篇幅的最后来粗略的讲解一下. 本篇没有核心的主题,如果说要强制定义的话就是这些内容都是基础知识,是为了后续学习MVC框架做铺垫. 1 Lambda Lambda表达式在日常的开发中很常见,使用Lambda表达式可以自由的定义函数体并且精简代码量,那么Lambda表达式是什么呢? Lamb

第2章 ASP.NET MVC(URL、路由及区域)

ASPNET MVC ASP.NET MVC简介 一.      简介 二.      MVC概念 三.      Asp.NET MVC 原理 四.      与ASP.NETWeb From比较 五.      安装必要软件 六.      第一个MVC应用程序 控制器向视图传递数据的方法 1.         ViewData[Key]=value 只能在当前视图中使用 2.         ViewBag.名称=value  只能在当前视图使用 3.         TempData[k

第一章 ASP.NET MVC概述

一 MVC基本概念 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面 显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑.MVC被独特的发展起来用于 映射传统的输入.处理和输出功能在一个逻辑的图形化用户界面的结构中. Model(模型)是应用程序中用于处理应用程序数据逻辑的部分. 通常模型对象负责在数据

Spring.NET教程(十九)整合NHibernate和ASP.NET MVC(基础篇)

今天带给大家的就是期待以久的ASP.net MVC与Spring.NET和NHibernate的组合,视图打造.NET版的SSH(Spring-Struts-Hibernate).是不是听到名字都很兴奋?我认为目前的ASP.NET MVC比Struts在某些功能上要好用的多,而且代码量要少,这就是我一直热衷于ASP.NET MVC的原因. 我们接着昨天的例子学习.昨天我们成功测试了带事务的业务层.接下来就是将业务层的对象注入到Controller中.我们先在Controller中写好要注入的属性

《ASP.NET MVC 5框架揭秘》样章发布

今天算是新作<ASP.NET MVC 5框架揭秘>正式上架销售的日子(目前本书在互动网已经到货),为了让更多适合的朋友们能够阅读此书,同时也避免让不适合的读者误买此书,特将此书的样章发布出来. 第1章 ASP.NET + MVC (下载地址:http://files.cnblogs.com/artech/inside-asp-net-mvc-5-framework-01.pdf) 第2章 路由 (下载地址:http://files.cnblogs.com/artech/inside-asp-n