異常信息處理是任何網站必不可少的一個環節,怎麼有效顯示,記錄,傳遞異常信息又成為重中之重的問題。本篇將基於上篇介紹的html2cancas截圖功能,實現mvc自定義全局異常處理。先看一下最終實現效果:http://yanweidie.myscloud.cn/Home/Index 閱讀目錄 我理解中好
異常信息處理是任何網站必不可少的一個環節,怎麼有效顯示,記錄,傳遞異常信息又成為重中之重的問題。本篇將基於上篇介紹的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 == 404 || statusCode == 500) { 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 ? "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。
基礎異常信息定義完後,剩下的是異常記錄和頁面跳轉了,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全局的頁面,介面異常信息處理。通過結合上面的前端截圖插件,快速截圖留證,方便後續程式員分析異常信息。 回到頂部
總結
通過一點小小的改造,我們完成了一個既美觀又方便拓展的錯誤處理方式。看到上面萌萌的圖片你是否心動了,想馬上下載代碼體驗一把呢。下麵就給出本文所有的源代碼:
svn代碼瀏覽:http://code.taobao.org/p/MyCustomGlobalError/src/trunk svn工具代碼checkout地址:http://code.taobao.org/svn/MyCustomGlobalErro
預告一下,下一篇將會對之前的TaskManager管理平臺進行升級,主要實現管理界面方便查看當前運行的所有任務和管理任務。講解管理平臺運用到的技術,敬請期待!
如果,您認為閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新博客,不妨點擊一下綠色通道的【關註我】。
因為,我的寫作熱情也離不開您的肯定支持。
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,請繼續關註我的後續博客,我是焰尾迭 。