針對IIS7以上的ASP.NET網站自定義錯誤頁面與異常日志總結
455 Reads?1 Comments
自定義錯誤頁面和異常記錄是個很古老的話題了,但依舊可以讓人爆到現在。在我做了無數次試驗并總結經驗和原則后,寫下本文,已警后人。
本文的范圍和限制
- 本文僅僅適用于部署在IIS7或以上版本中的ASP.NET 4.0集成模式應用程序。IIS7以上的意思是Windows Server 2008以上服務器適用。我已在WS2012R2,IIS8上測過。
- 本文的方法均適用于ASP.NET WebForm和MVC應用程序。
本文針對的問題
- 靜態錯誤頁面好還是動態錯誤頁面好?我該如何設計ASP.NET網站錯誤處理?
- 我不希望錯誤頁面后面跟上aspxerrorpath=…這個小尾巴。
- 我的自定義錯誤頁面在VS里調試是好的,為什么部署到服務器上就出不來了?
- 我的自定義錯誤頁面可以正常顯示,但為什么返回的Http狀態碼不正確?
- 異常日志該怎樣記錄,有沒有比較好的實踐?
asp.net web開發框架、一、錯誤頁面的選擇
選擇靜態html頁面作為錯誤頁,還是使用.aspx或是MVC View來顯示錯誤頁面好?這個問題許多人的偏好都不一樣。我強烈建議大家用html靜態頁面作為錯誤頁。請看分析:
首先,用動態頁面的人,無非是為了解決三個問題:顯示錯誤摘要、記錄日志、返回正確的狀態碼。但是,動態頁面最大的問題在于,它們本身是要經過ASP.NET和后臺代碼處理的,萬一你的后臺代碼是爆的,或者ASP.NET自己爆了,那你的錯誤頁面一旦被請求,本身就會引發另一個錯誤。而靜態html是不會有這個問題的。至于在MVC里專門建一個ErrorController的做法就更不可取了。MVC是基于ASP.NET之上的,MVC的Controller只能抓MVC自己的錯誤,抓不到ASP.NET的錯誤,意思就是說,當你的錯誤并不在MVC層面上的時候,ErrorController沒有任何用武之地。例如,當你部署的MVC應用程序缺少MVC的dll時候,MVC框架本身都跑不起來,這個錯誤如何去抓?
另外,我認為在一個Internet站點上,絕不應該向訪客顯示任何的錯誤摘要。這是非常不安全的。所以完全沒有必要用動態頁面。
ASPNET程序開發招聘?至于返回正確狀態碼的問題是這樣的:不少小伙伴發現自己的錯誤頁面能夠顯示,但返回的狀態碼是200OK,所以無奈之下用動態頁面,寫上Response.StatusCode = 500這樣的代碼來強擼。這個在稍后的文章里有解決方案,所以不要為了狀態碼而冒險用動態頁面。有的小伙伴會問,我只寫一筆Response.StatusCode=500能有什么風險?呵呵,想想這種情況:404.cshtml,razor引擎的dll不見了……
另外,一個原則是:錯誤頁面應當是獨立的。如果你的靜態頁面要用到圖片和CSS等外部資源,建議嵌入在頁面里邊,做成單文件的,以免顯示錯誤頁面時請求相關資源再次引發異常。小伙伴又要問了,請求個CSS和圖片什么的,腫么會引發異常呢?那你看看這個:resources.axd?image=…,呵呵。
那么用了靜態頁面以后,記錄日志怎么辦呢?正確的方法應該是交給Global.asax中的“Application_Error”事件處理。稍后會有翔解。
二、配置錯誤頁面:customErrors VS httpErrors
javascript錯誤解決方案,首先,錯誤頁面的顯示方式有兩種。如果你打開IIS,會看到兩個配錯誤頁的地方:.NET Error Pages和Error Pages。
站在開發者的角度來說,ASP.NET Section下的“.NET Error Pages”就是web.confg/system.web/customErrors節點。而IIS Section下的”Error Pages”是web.config/system.webServer/httpErrors節點,這個是IIS7以上特有的。
任何在IIS上對這兩處的更改其實都是在修改web.config文件。
ASP和JSP。那么我們該用哪一個來配置自定義錯誤頁呢?我的一個原則是:讓IIS向用戶展示錯誤頁面,而不要依賴于ASP.NET。 猿因如下:
CustomError的問題:
a)? 在默認情況下,CustomError會采用302重定向的方式來展示錯誤頁面,如果客戶端請求了一個404的頁面,那它得到的將會是一個302緊接著一個200OK,對于人類來說這是OK的,畢竟用戶看到了友好的錯誤頁面,但搜索引擎會認為這個不存在的地址是正常的頁面,并把你的404頁面收入搜索結果。并且每次引發錯誤,URL后邊都會跟上aspxerrorpath這個小尾巴,比如:
http://yoursite.com/404.html?aspxerrorpath=/somethingnotexsit
iis錯誤日志在哪查看,b) 在配置了redirectMode=“ResponseRewrite”后,會引發一個問題,09年就有人當bug提交到MS Connect上了,但目前微軟不打算fix這個bug。即該模式在VS自帶的ASP.NET Development Server上是有效的,瀏覽器顯示的是解析后的HTML頁面,而部署到IIS上之后,瀏覽器顯示是raw html,即你的錯誤頁面的HTML代碼。并且該行為不具有確定性,天曉得你的網站換個環境部署又會爆成什么樣。
c)? IIS7以上版本的特殊性:在集成模式下,IIS的錯誤頁面會優先于ASP.NET錯誤頁面,即IIS的默認錯誤頁會覆蓋你配置的CustomErrors頁面,這就是為什么你在VS下調試是好的,部署到服務器上就爆了。
在IIS7之后,取決于你的配置,用戶的請求并不都一定在ASP.NET管線上處理。如果引發異常的地方不在ASP.NET,那就不會顯示CustomErrors的錯誤頁面。
所以,我強烈建議大家用httpErrors節點替代CustomErrors來配置錯誤頁面。
三、File還是ExecuteURL:httpErrors的正確配法
先貼一個正確的配置樣例:是用File配的。
注意兩處地方:
- 斜杠是Windows文件路徑的反斜杠“\”而不是網址URL的斜杠“/”
- Path不要以“~”或“\”開頭
不要問我為什么,我也不知道,反正這么弄就是好的。
如果你不幸用了ExecuteURL,你會發現URL路徑只能以“/”開頭:
并且你的錯誤頁會返回200OK而不是正確的404、500等錯誤碼。這在我以前的文章里寫過:
http://diaosbook.com/Post/2012/6/24/correct-way-to-implement-custom-error-page-return-statecode-instead-of-http-200
而用File的意思是,當錯誤被爆出來之后,IIS會把目標File,比如404.html的內容,塞到當前的Response流里面,保持Http狀態碼依舊是404。這就是我們想要的。
四、異常日志記錄
文章在一開始就提了個問題,木有了動態頁面,怎么記錄錯誤日志?我的做法是在Global.asax里處理。
Application_Error事件樣例代碼如下:
var exception = HttpContext.Current.Server.GetLastError(); if (null != exception) { // by default 500 var statusCode = (int)HttpStatusCode.InternalServerError; if (exception is HttpException) { statusCode = new HttpException(null, exception).GetHttpCode(); } else if (exception is UnauthorizedAccessException) { // to prevent login prompt in IIS // which will appear when returning 401. statusCode = (int)HttpStatusCode.Forbidden; } if ((LogHttpNotFound && statusCode == 404) || statusCode != 404) { if (null != _loggerFunc) { LoggerFunc(string.Format("ASP.NET Error Captured, Request URL: {0}, Exception:", HttpContext.Current.Request.Url), exception); } } ExceptionInfo = exception.Message; _statusCode = statusCode; }
一個小小的建議是不要記錄404錯誤,因為你的網站在Internet上會被各種搜索引擎、掃描工具和黑客菊爆,有一種攻擊手段是通過字典猜解目錄,返回404就是不存在,返回403就是存在。所以,當你碰到這種無聊黑客的時候,如果記錄了404請求,那你的日志會非常的大……
我記日志用的組件是NLog,配置簡單,使用容易。Log4net這貨已經好久沒更新了。
如果你決定自己設計日志模塊,那要記住兩個原則:
日志模塊本身并不能因為自己爆掉而影響整個系統運行,即任何在日志模塊里的異常不應該向外冒泡。
考慮并發寫日志的情況,日志操作應該是Fire and Forget的,不能讓應用程序等待日志寫入。
五、總結
- 采用靜態html頁面作為錯誤頁。
- 采用httpErrors配置錯誤頁面,目的是兼容IIS7以上環境、返回正確錯誤碼、去掉aspxerrorpath小尾巴。
- 日志記錄寫在Global.asax里,在Application_Error事件中記錄。