異常信息處理是任何網站必不可少的一個環節,怎麼有效顯示,記錄,傳遞異常信息又成爲重中之重的問題。本篇將基於上篇介紹的html2cancas截圖功能,實現mvc自定義全局異常處理。先看一下最終實現效果:http://yanweidie.myscloud.cn/Home/Indexhtml
閱讀目錄前端
好的異常信息處理應該具備如下幾個優勢jquery
1.第一點顯示效果方面能夠自定義頁面,常見的包括404和500狀態碼頁面。在mvc中404頁面能夠經過如下兩種方式進行自定義git
<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 == 404 || statusCode == 500) { Response.Clear(); Response.RedirectToRoute("Default", new { controller = "Error", action = "Path404" }); } }
這裏採用mvc的過濾器進行異常處理,分別爲接口500錯誤和頁面500錯誤進行處理,接口部分異常須要記錄請求參數,方便分析異常。程序員
首先定義了異常信息實體,異常實體包含了 請求地址類型(頁面,接口),服務器相關信息(位數,CPU,操做系統,iis版本),客戶端信息(UserAgent,HttpMethod,IP)github
異常實體代碼以下web
/// <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 ? "64" : "32") + "位"; OSVersion = Environment.OSVersion.ToString(); CPUCount = Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); CPUType = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER"); OSBit = (Environment.Is64BitOperatingSystem ? "64" : "32") + "位"; 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 > 15) { if (ipAddress.IndexOf(",") > 0) { ipAddress = ipAddress.Substring(0, 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。ajax
基礎異常信息定義完後,剩下的是異常記錄和頁面跳轉了,mvc中的異常過濾器實現以下。json
/// <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組件記錄在錯誤日誌中,方便留痕。api
過濾器定義完成後,須要在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); } } });
經過一點小小的改造,咱們完成了一個既美觀又方便拓展的錯誤處理方式。看到上面萌萌的圖片你是否心動了,想立刻下載代碼體驗一把呢。下面就給出本文全部的源代碼:
git代碼地址:https://github.com/CrazyJson/CustomGlobalError
預告一下,下一篇將會對以前的TaskManager管理平臺進行升級,主要實現管理界面方便查看當前運行的全部任務和管理任務。講解管理平臺運用到的技術,敬請期待!