完整程式碼
void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError().GetBaseException();
StringBuilder log = new StringBuilder();
// 時間戳
log.AppendLine($"\r\n{DateTime.Now:yyyy.MM.dd HH:mm:ss}");
// 客戶端資訊
log.AppendLine(". 客戶端資訊:");
string ip = Request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? Request.ServerVariables["REMOTE_ADDR"];
log.AppendLine($"\tIP: {ip}");
log.AppendLine($"\t瀏覽器: {Request.Browser.Browser} {Request.Browser.MajorVersion}");
log.AppendLine($"\t作業系統: {Request.Browser.Platform}");
// 錯誤詳情
log.AppendLine(". 錯誤資訊:");
log.AppendLine($"\t頁面: {Request.Url}");
log.AppendLine($"\t訊息: {ex.Message}");
log.AppendLine($"\t來源: {ex.Source}");
log.AppendLine($"\t方法: {ex.TargetSite}");
log.AppendLine($"\t堆疊: {ex.StackTrace}");
log.AppendLine("--------------------------------------------------------------------------------------------------");
// 確保目錄存在
string logDir = Server.MapPath("~/log/");
if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir);
// 寫入按日期命名的檔案
string file = Path.Combine(logDir, $"{DateTime.Now:yyyy.MM.dd}.log");
File.AppendAllText(file, log.ToString(), Encoding.UTF8);
// 清除錯誤並導向友善頁面
Server.ClearError();
Response.Redirect("~/error.html");
}
關鍵點解析
- GetBaseException():取得最內層的真實例外,避免只記錄外層包裝的
HttpUnhandledException。
- IP 取得:優先讀
HTTP_X_FORWARDED_FOR(反向代理/負載平衡器後的真實 IP),退而求其次用 REMOTE_ADDR。
- 目錄自動建立:首次執行時自動建立
~/log/,免去部署手動建立資料夾。
- 按日期分檔:便於日誌輪轉與清理,避免單一檔案過大。
- Server.ClearError():必須呼叫,否則 ASP.NET 仍會顯示預設黃色錯誤頁(或依
customErrors 設定導向)。
- Response.Redirect:導向靜態
error.html 避免二次錯誤;若需動態頁面請確保該頁極簡且不依賴可能失效的資源。
進階建議
- 結構化日誌: 改用 Serilog/NLog 輸出 JSON,方便 ELK/Splunk 分析。
- 敏感資料過濾: 記錄前移除 QueryString/表單中的密碼、Token 等敏感欄位。
- 非同步寫入: 高流量站點建議採用非同步佇列寫入,避免阻塞請求執行緒。
- 健康檢查端點: 搭配監控系統定期檢查日誌目錄寫入權限與磁碟空間。