异常信息处理是任何网站必不可少的一个环节,怎么有效显示,记录,传递异常信息又成为重中之重的问题。本篇将基于上篇介绍的html2cancas截图功能,实现mvc自定义全局异常处理。先看一下最终实现效果:http://yanweidie.myscloud.cn/Home/Index

阅读目录

我理解中好的异常处理

    好的异常信息处理应该具有以下几个优点

  • 显示效果佳,而不是原生黄页
  • 能够从异常中直接分析出异常源
  • 能够记录传递异常信息给开发人员

1.第一点显示效果方面可以自定义页面,常见的包括404和500状态码页面。在mvc中404页面可以通过以下两种方式进行自定义

<system.web>
<!--添加customErrors节点 定义404跳转页面-->
<customErrors mode="On">
<error statusCode="404" redirect="/Error/Path404" />
</customErrors>
</system.web>
//Global文件的EndRequest监听Response状态码
protected void Application_EndRequest()
{
  var statusCode = Context.Response.StatusCode;
var routingData = Context.Request.RequestContext.RouteData;
if (statusCode == || statusCode == )
{
  Response.Clear();
Response.RedirectToRoute("Default", new { controller = "Error", action = "Path404" });
}
}  
      2.第二点 异常信息应该详细,能够记录下请求参数,请求地址,浏览器版本服务器和当前用户等相关信息,这就需要对异常信息记录改造加工
      3.第三点 常见的异常信息都是记录在日志文件里面,日志文件过大时也不太好分析。发生异常时要是能马上将异常信息通过邮件或者图片等方式发给开发者,可以加快分析速度。

自定义异常处理

  

  这里采用mvc的过滤器进行异常处理,分别为接口500错误和页面500错误进行处理,接口部分异常需要记录请求参数,方便分析异常。

首先定义了异常信息实体,异常实体包含了 请求地址类型(页面,接口),服务器相关信息(位数,CPU,操作系统,iis版本),客户端信息(UserAgent,HttpMethod,IP)

   异常实体代码如下

    /// <summary>
/// 系统错误信息
/// </summary>
public class ErrorMessage
{
public ErrorMessage()
{ }
public ErrorMessage(Exception ex,string type)
{
MsgType = ex.GetType().Name;
Message = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
StackTrace = ex.StackTrace;
Source = ex.Source;
Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Assembly = ex.TargetSite.Module.Assembly.FullName;
Method = ex.TargetSite.Name;
Type = type; DotNetVersion = Environment.Version.Major + "." + Environment.Version.Minor + "." + Environment.Version.Build + "." + Environment.Version.Revision;
DotNetBit = (Environment.Is64BitProcess ? "" : "") + "位";
OSVersion = Environment.OSVersion.ToString();
CPUCount = Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
CPUType = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
OSBit = (Environment.Is64BitOperatingSystem ? "" : "") + "位"; var request = HttpContext.Current.Request;
IP = GetIpAddr(request) + ":" + request.Url.Port;
IISVersion = request.ServerVariables["SERVER_SOFTWARE"];
UserAgent = request.UserAgent;
Path = request.Path;
HttpMethod = request.HttpMethod;
}
/// <summary>
/// 消息类型
/// </summary>
public string MsgType { get; set; } /// <summary>
/// 消息内容
/// </summary>
public string Message { get; set; } /// <summary>
/// 请求路径
/// </summary>
public string Path { get; set; } /// <summary>
/// 程序集名称
/// </summary>
public string Assembly { get; set; } /// <summary>
/// 异常参数
/// </summary>
public string ActionArguments { get; set; } /// <summary>
/// 请求类型
/// </summary>
public string HttpMethod { get; set; } /// <summary>
/// 异常堆栈
/// </summary>
public string StackTrace { get; set; } /// <summary>
/// 异常源
/// </summary>
public string Source { get; set; } /// <summary>
/// 服务器IP 端口
/// </summary>
public string IP { get; set; } /// <summary>
/// 客户端浏览器标识
/// </summary>
public string UserAgent { get; set; } /// <summary>
/// .NET解释引擎版本
/// </summary>
public string DotNetVersion { get; set; } /// <summary>
/// 应用程序池位数
/// </summary>
public string DotNetBit { get; set; } /// <summary>
/// 操作系统类型
/// </summary>
public string OSVersion { get; set; } /// <summary>
/// 操作系统位数
/// </summary>
public string OSBit { get; set; } /// <summary>
/// CPU个数
/// </summary>
public string CPUCount { get; set; } /// <summary>
/// CPU类型
/// </summary>
public string CPUType { get; set; } /// <summary>
/// IIS版本
/// </summary>
public string IISVersion { get; set; } /// <summary>
/// 请求地址类型
/// </summary>
public string Type { get; set; } /// <summary>
/// 是否显示异常界面
/// </summary>
public bool ShowException { get; set; } /// <summary>
/// 异常发生时间
/// </summary>
public string Time { get; set; } /// <summary>
/// 异常发生方法
/// </summary>
public string Method { get; set; } //这段代码用户请求真实IP
private static string GetIpAddr(HttpRequest request)
{
//HTTP_X_FORWARDED_FOR
string ipAddress = request.ServerVariables["x-forwarded-for"];
if (!IsEffectiveIP(ipAddress))
{
ipAddress = request.ServerVariables["Proxy-Client-IP"];
}
if (!IsEffectiveIP(ipAddress))
{
ipAddress = request.ServerVariables["WL-Proxy-Client-IP"];
}
if (!IsEffectiveIP(ipAddress))
{
ipAddress = request.ServerVariables["Remote_Addr"];
if (ipAddress.Equals("127.0.0.1") || ipAddress.Equals("::1"))
{
// 根据网卡取本机配置的IP
IPAddress[] AddressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
foreach (IPAddress _IPAddress in AddressList)
{
if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
{
ipAddress = _IPAddress.ToString();
break;
}
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.Length > )
{
if (ipAddress.IndexOf(",") > )
{
ipAddress = ipAddress.Substring(, ipAddress.IndexOf(","));
}
}
return ipAddress;
} /// <summary>
/// 是否有效IP地址
/// </summary>
/// <param name="ipAddress">IP地址</param>
/// <returns>bool</returns>
private static bool IsEffectiveIP(string ipAddress)
{
return !(string.IsNullOrEmpty(ipAddress) || "unknown".Equals(ipAddress, StringComparison.OrdinalIgnoreCase));
}
}

上面代码中用到了获取客户端请求IP的方法,用于获取请求来源的真实IP。

基础异常信息定义完后,剩下的是异常记录和页面跳转了,mvc中的异常过滤器实现如下。

 /// <summary>
/// 全局页面控制器异常记录
/// </summary>
public class CustomErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext); ErrorMessage msg = new ErrorMessage(filterContext.Exception, "页面");
msg.ShowException = MvcException.IsExceptionEnabled(); //错误记录
LogHelper.WriteLog(JsonConvert.SerializeObject(msg, Formatting.Indented), null); //设置为true阻止golbal里面的错误执行
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult() { ViewName = "/Views/Error/ISE.cshtml", ViewData = new ViewDataDictionary<ErrorMessage>(msg) }; }
} /// <summary>
/// 全局API异常记录
/// </summary>
public class ApiHandleErrorAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext filterContext)
{
base.OnException(filterContext); //异常信息
ErrorMessage msg = new ErrorMessage(filterContext.Exception, "接口");
//接口调用参数
msg.ActionArguments = JsonConvert.SerializeObject(filterContext.ActionContext.ActionArguments, Formatting.Indented);
msg.ShowException = MvcException.IsExceptionEnabled(); //错误记录
string exMsg = JsonConvert.SerializeObject(msg, Formatting.Indented);
LogHelper.WriteLog(exMsg, null); filterContext.Response = new HttpResponseMessage() { StatusCode = HttpStatusCode.InternalServerError, Content = new StringContent(exMsg) };
}
} /// <summary>
/// 异常信息显示
/// </summary>
public class MvcException
{
/// <summary>
/// 是否已经获取的允许显示异常
/// </summary>
private static bool HasGetExceptionEnabled = false; private static bool isExceptionEnabled; /// <summary>
/// 是否显示异常信息
/// </summary>
/// <returns>是否显示异常信息</returns>
public static bool IsExceptionEnabled()
{
if (!HasGetExceptionEnabled)
{
isExceptionEnabled = GetExceptionEnabled();
HasGetExceptionEnabled = true;
}
return isExceptionEnabled;
} /// <summary>
/// 根据Web.config AppSettings节点下的ExceptionEnabled值来决定是否显示异常信息
/// </summary>
/// <returns></returns>
private static bool GetExceptionEnabled()
{
bool result;
if(!Boolean.TryParse(ConfigurationManager.AppSettings["ExceptionEnabled"],out result))
{
return false;
}
return result;
}
}

值得注意的是上面的MvcException类的GetExceptionEnabled方法,该方法从web.config appsetting中读取节点"ExceptionEnabled"来控制异常信息是否初始化显示。异常信息除了显示在页面,还使用了log4net组件记录在错误日志中,方便留痕。

过滤器定义完成后,需要在filterconfig添加引用

    public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomErrorAttribute());
filters.Add(new HandleErrorAttribute());
}
}

问题拓展

  后台异常处理代码完成以后,前台还需进行相应的处理。这里主要针对api接口,因为请求页面后台可以直接转向500错误页面,而api接口一般是通过ajax或者客户端httpclient请求的,如果错误了跳转到500页面,这样对客户端来说就不友好了。基于这点所以api请求异常返回了异常的详细json对象,让客户端自己进行异常处理。我这里给出ajax处理异常的方式。

在jquery中全局ajax请求可以设置相应默认参数,比如下面代码设置了全局ajax请求为异步请求,不缓存

//ajax请求全局设置
$.ajaxSetup({
//异步请求
async: true,
//缓存设置
cache: false
});

ajax请求完成会触发Complete事件,在jquery中全局Complete事件可以通过下面代码监听

$(document).ajaxComplete(function (evt, request, settings) {
var text = request.responseText;
if (text) {
try {
//Unauthorized 登录超时或者无权限
if (request.status == "401") {
var json = $.parseJSON(text);
if (json.Message == "logout") {
//登录超时,弹出系统登录框
} else {
layer.alert(json.ExceptionMessage ? json.ExceptionMessage : "系统异常,请联系系统管理员", {
title: "错误提醒",
icon: 2
});
}
} else if (request.status == "500") {
var json = $.parseJSON(text);
$.ajax({
type: "post",
url: "/Error/Path500",
data: { "": json },
data: json,
dataType: "html",
success: function (data) {
//页面层
layer.open({
title: '异常信息',
type: 1,
shade: 0.8,
shift: -1,
area: ['100%', '100%'],
content: data,
});
}
}); }
} catch (e) {
console.log(e);
}
}
});
红色部分代码就是我用来处理500错误的代码,重新发请求到异常显示界面渲染成html后显示。其实这么做无疑增加了一次请求,最好的实现方式,直接通过异常信息json,通过js绘制出html。至此完成了mvc全局的页面,接口异常信息处理。通过结合上面的前端截图插件,快速截图留证,方便后续程序员分析异常信息。

总结

  通过一点小小的改造,我们完成了一个既美观又方便拓展的错误处理方式。看到上面萌萌的图片你是否心动了,想马上下载代码体验一把呢。下面就给出本文所有的源代码:

git代码地址:https://github.com/CrazyJson/CustomGlobalError

预告一下,下一篇将会对之前的TaskManager管理平台进行升级,主要实现管理界面方便查看当前运行的所有任务和管理任务。讲解管理平台运用到的技术,敬请期待!

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我

如果,想给予我更多的鼓励,求打

因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。

mvc自定义全局异常处理的更多相关文章

  1. ASP.NET Core 中间件自定义全局异常处理

    目录 背景 ASP.NET Core过滤器(Filter) ASP.NET Core 中间件(Middleware) 自定义全局异常处理 .Net Core中使用ExceptionFilter .Ne ...

  2. Asp.net mvc 自定义全局的错误事件HandleErrorAttribute无效

    Asp.net mvc 自定义全局的错误事件HandleErrorAttribute,结果无效, 原因: 1.没有在RegisterGlobalFilters 里面添加或者你要的位置添加. 2.你把这 ...

  3. Laravel实践-自定义全局异常处理

    在做API时,需要对一些异常进行全局处理 百牛信息技术bainiu.ltd整理发布于博客园比如添加用户执行失败时,需要返回错误信息 // 添加用户 $result = User::add($user) ...

  4. Spring MVC自定义统一异常处理类,并且在控制台中输出错误日志

    在使用SimpleMappingExceptionResolver实现统一异常处理后(参考Spring MVC的异常统一处理方法), 发现出现异常时,log4j无法在控制台输出错误日志.因此需要自定义 ...

  5. asp.net mvc 自定义全局过滤器 验证用户是否登录

    一般具有用户模块的系统都需要对用户是否登录进行验证,如果用户登录了就可以继续操作,否则退回用户的登录页面 对于这样的需求我们可以通过自定义一个独立的方法来完成验证的操作,但是这样代码的重复率就大大提高 ...

  6. ASP.NET MVC中全局异常处理

    以前不知道从哪里找到的处理全局异常的,觉得蛮好用就记下来了. 1, 建立MyExecptionAttribute.cs类,写入如下代码: using System; using System.Coll ...

  7. springMvc全局异常处理

    本文中只测试了:实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器 对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提 ...

  8. 第6章 AOP与全局异常处理6.1-6.4 慕课网微信小程序开发学习笔记

    第6章 AOP与全局异常处理 https://coding.imooc.com/learn/list/97.html 目录: 第6章 AOP与全局异常处理6-1 正确理解异常处理流程 13:236-2 ...

  9. SpringBoot优雅的全局异常处理

    前言 本篇文章主要介绍的是SpringBoot项目进行全局异常的处理. SpringBoot全局异常准备 说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码. 开发准备 环境要求 JD ...

随机推荐

  1. ember.js里的实用方法

    一款基于jQuery的插件,以下是关于数组的一些方法 var arr = ['Wang', 'Jason', '444128852@qq.com', 'i2cao.xyz', 'ubuntuvim.x ...

  2. 使用hexo搭建属于自己的博客

    如果你喜欢拥有自己的博客域名,如果你喜欢折腾,可以先点击luckykun.com,看看效果,再选择要不要进来看看--- 之前一直都在博客园写博客,不过最近在逛园子的时候不小心看到了hexo,简直有种相 ...

  3. $(function) ready onload 等区别

    新手接触javascript.jquery的时候不可避免的要接触题目所标识的相关内容,反复看过几次一到用的时候总是不踏实,写文以记之. 符号“$”是jquery对象(个人这样理解,拥有函数的用法).接 ...

  4. 配置 Oracle 11g侦听器来使用SQL操作ST_Geometry(DLL路径问题)

    注:http://resources.arcgis.com/zh-cn/help/main/10.2/index.html#/na/00qn0000001p000000/ (ArcGIS 帮助库) 1 ...

  5. CGGeometry.h 文件详解

    这些是在CGGeometry.h里的 CGPoint.CGSize.CGRect.CGRectEdge实际上都是结构体 struct CGPoint { CGFloat x; CGFloat y; } ...

  6. 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)

    声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...

  7. android性能优化练习:过度绘制

    练习:https://github.com/zhangbz/AndroidUIPorblems 查看过度绘制 在开发者选项中开启"调试GPU过度绘制" 判断标准 无色:没有过度绘制 ...

  8. 专用服务器模式&共享服务器模式

    连接ORACLE服务器一般有两种方式:专用服务器连接(dedicated server)和共享服务器连接(shared server).那么两者有啥区别和不同呢?下面我们将对这两者的区别与不同一一剖析 ...

  9. Linux LVM学习总结——放大LV容量

    本篇介绍LVM管理中的命令lvresize,我们先创建一个卷组VG VolGroup02,它建立在磁盘/dev/sdc (大小为8G)上.创建逻辑卷LV时,我们故意只使用了一小部分.具体情况如下所示 ...

  10. JVM之CMS收集器

    CMS(Concurrent Mark Sweep) 最短回收停顿,适合维持响应时间上的要求. 初始标记 Initial mark:标记GC Roots能够关联到的对象.stop-mark. 并发标记 ...