首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

ASP.NET 全域例外處理機制實作:HttpModule 攔截、記錄入庫、郵件通知、錯誤編號追蹤

     編輯:關於ASP.NET

未處理的例外若直接顯示給使用者,洩漏堆疊追蹤、資料庫結構、伺服器路徑等敏感資訊,構成安全風險;若僅顯示友善錯誤頁,開發團隊又難以第一時間發現問題。本文示範透過 `IHttpModule` 掛載 `HttpApplication.Error` 事件,建立**全域例外處理管線**:記錄詳細資訊至資料庫、產生唯一錯誤編號、導向友善錯誤頁、可選發送郵件通知,達成安全與可觀測性雙贏。

核心架構

HTTP 請求管線
  → ... (正常處理)
  → 發生未捕獲例外
  → HttpApplication.Error 事件觸發
  → IHttpModule.Init() 註冊的處理器介入
  → 執行自訂邏輯:
      1. 取得例外物件 (Server.GetLastError())
      2. 過濾已知雜訊 (404、HttpException 等)
      3. 擷取關鍵資訊 (型別、訊息、堆疊、URL、使用者、參數)
      4. 寫入資料庫 / 串流日誌 / 訊息佇列
      5. 產生錯誤編號 (GUID / 流水號)
      6. ClearError() 清除例外
      7. Response.Redirect 錯誤頁 + 帶入錯誤編號
      8. (可選) 發送郵件/Slack/Teams 通知值班人員

完整 HttpModule 實作 (C#)

using System;
using System.Data.SqlClient;
using System.Web;

namespace MyApp.Modules
{
    public class GlobalErrorModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.Error += OnError;
        }

        private void OnError(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            var context = app.Context;
            var ex = context.Server.GetLastError();

            if (ex == null) return;

            // 1. 過濾雜訊:404、靜態檔案不存在等
            if (ex is HttpException httpEx && httpEx.GetHttpCode() == 404)
            {
                // 可選:記錄 404 供 SEO 分析,但不觸發告警
                return;
            }

            // 2. 取得真實例外 (解開 HttpUnhandledException)
            var realEx = ex.GetBaseException();

            // 3. 產生錯誤編號
            string errorId = Guid.NewGuid().ToString("N").Substring(0, 12).ToUpper();

            // 4. 建立錯誤記錄物件
            var errorLog = new ErrorLogEntry
            {
                ErrorId = errorId,
                Timestamp = DateTime.UtcNow,
                ExceptionType = realEx.GetType().FullName,
                Message = realEx.Message,
                StackTrace = realEx.StackTrace,
                Source = realEx.Source,
                TargetSite = realEx.TargetSite?.ToString(),
                Url = context.Request.RawUrl,
                HttpMethod = context.Request.HttpMethod,
                UserAgent = context.Request.UserAgent,
                UserHostAddress = context.Request.UserHostAddress,
                UserIdentity = context.User?.Identity?.Name ?? "Anonymous",
                FormData = GetFormData(context.Request),
                QueryString = context.Request.QueryString.ToString(),
                ServerVariables = GetServerVars(context.Request)
            };

            // 5. 非同步寫入資料庫 (不阻塞請求執行緒)
            Task.Run(() => PersistErrorLog(errorLog)).ConfigureAwait(false);

            // 6. 發送即時通知 (高嚴重級才發送,避免風暴)
            if (IsCritical(realEx))
            {
                Task.Run(() => NotifyOnCall(errorLog)).ConfigureAwait(false);
            }

            // 7. 清除例外,避免預設黃色死機畫面
            context.Server.ClearError();

            // 8. 導向友善錯誤頁,攜帶錯誤編號
            context.Response.Redirect($"/Error/Friendly?errorId={errorId}");
        }

        // ... 輔助方法:GetFormData, GetServerVars, PersistErrorLog, NotifyOnCall, IsCritical
    }

    public class ErrorLogEntry
    {
        public string ErrorId { get; set; }
        public DateTime Timestamp { get; set; }
        public string ExceptionType { get; set; }
        public string Message { get; set; }
        public string StackTrace { get; set; }
        public string Source { get; set; }
        public string TargetSite { get; set; }
        public string Url { get; set; }
        public string HttpMethod { get; set; }
        public string UserAgent { get; set; }
        public string UserHostAddress { get; set; }
        public string UserIdentity { get; set; }
        public string FormData { get; set; }
        public string QueryString { get; set; }
        public string ServerVariables { get; set; }
    }
}

Web.config 註冊

<system.webServer>
  <modules>
    <add name="GlobalErrorModule" 
         type="MyApp.Modules.GlobalErrorModule, MyApp" 
         preCondition="managedHandler" />
  </modules>
</system.webServer>

<!-- 關閉預設 customErrors,交由模組處理 -->
<system.web>
  <customErrors mode="Off" />
</system.web>

錯誤編號追蹤頁面 (/Error/Friendly)

public partial class FriendlyError : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string errorId = Request.QueryString["errorId"];
        if (!string.IsNullOrEmpty(errorId))
        {
            lblErrorId.Text = errorId;
            // 可選:查詢資料庫顯示發生時間、簡略分類
        }
    }
}
<div class="error-page">
  <h1>系統發生錯誤,我們已記錄並通知工程團隊處理</h1>
  <p>錯誤參考編號:<strong><asp:Label ID="lblErrorId" runat="server" /></strong></p>
  <p>請將此編號提供給客服或管理員,以利快速追蹤。</p>
  <a href="/" class="btn">返回首頁</a>
</div>

關鍵細節與最佳實踐

細節處理方式
取得真實例外`ex.GetBaseException()` 解開 `HttpUnhandledException` 包裝
HRESULT 取得`Marshal.GetHRForException(ex)` (私有屬性 `HResult` 不可直接存取)
過濾 404/靜態檔案`ex is HttpException && httpEx.GetHttpCode() == 404`
非同步寫入日誌`Task.Run(() => ...)` 避免阻塞 IIS 執行緒池
防止通知風暴錯誤去重 (同類例外 5 分鐘內只通知一次)、熔斷機制
敏感資料遮罩FormData/QueryString 中的密碼、Token、信用卡號自動遮罩
資料表結構建議分區表 (按月)、堆疊追蹤壓縮 (GZip)、全文檢索索引

現代化替代方案

  • ASP.NET Core:`app.UseExceptionHandler("/Error")` + `IExceptionFilter` / `IExceptionHandler` (8.0+)
  • Serilog / NLog + Seq / ELK:結構化日誌、自動擷取上下文、儀表板查詢
  • Application Insights / Sentry / Raygun:APM 級錯誤追蹤、分組、趨勢、釋出關聯
  • Polly / Polly.Contrib:重試、熔斷、逾時、Bulkhead 整合在管線中

遷移檢查清單 (Legacy → Core)

  1. 將 `IHttpModule` 改為 `IMiddleware` / `IExceptionHandler`
  2. `HttpContext.Current` → `HttpContext` (DI 注入)
  3. `Server.GetLastError()` → `context.Features.Get()?.Error`
  4. `Response.Redirect` → `context.Response.Redirect`
  5. 設定檔 `system.webServer/modules` → `Program.cs` `builder.Services.AddTransient<...>()` + `app.UseMiddleware<...>()`

全域例外處理是應用程式的**最後一道防線**,也是**可觀測性的起點**。建立「記錄 → 編號 → 通知 → 友善回應」的標準化流程,讓每個例外都可追蹤、可審計、可改進,是從「能跑」邁向「可託付」的關鍵里程碑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved