只有累積,沒有奇蹟

2021年6月13日 星期日

[NETCore] ASP.NET Core 中的例外處理方式

前言
錯誤處理一直是開發中的重要環節之一,如果在程式發生異常錯誤的當下有效的將錯誤訊息完整的記錄下來,可以大大的節省 debug 的時間與效率,反過來如果在開發時沒有考慮到異常處理的機制,可能在發生問題時要找到錯誤的原因難度就會提高,因此在開發時必須要考慮到異常處理的機制,過去公司都會將 exception 訊息透過推(push)或是拉(pull)的方式傳送到 Logstash 再搭配 Elasticsearch 與 Kibana 搜尋到相對應的錯誤訊息或是 Log。這篇就簡單分享在 ASP.NET Core 中如何使用 ExceptionFilter 以及 Middleware 捕捉異常紀錄的方式,內容若有問題歡迎提出來一起討論。

開個 Error 給他
首先在 Visual Studio 2019 建立 ASP.NET Core API 專案
為了模擬例外狀況的發生,在預設提供的 WeatherForecastController.cs 中直接加上 throw 一個訊息為 "我是醬爆,我要爆了 !" 的 exception,代碼如下
  1. [HttpGet]
  2. public IEnumerable Get()
  3. {
  4. throw new Exception("我是醬爆,我要爆了!!!");
  5. var rng = new Random();
  6. return Enumerable.Range(1, 5).Select(index => new WeatherForecast
  7. {
  8. Date = DateTime.Now.AddDays(index),
  9. TemperatureC = rng.Next(-20, 55),
  10. Summary = Summaries[rng.Next(Summaries.Length)]
  11. })
  12. .ToArray();
  13. }
接著在執行應用程式,可以看到應用程式開啟後即噴出異常訊息

使用 Exception Filter
首先為了更模擬一般開發情境,建立 API 共用回傳的 Model 與自訂客製的例外 CustomerException 等兩個 model,代碼如下
  1. public class ApiResponse
  2. {
  3. public DateTimeOffset Timestamp { get; set; }
  4. public string Message { get; set; }
  5. public string Result { get; set; }
  6. }
  7.  
  8. public class CustomerException: Exception
  9. {
  10. private readonly Exception _exception;
  11. }
在 ASP.NET Core 中如果要自訂錯誤機制 exception Filter 必須透過實作 IExceptionFilter 或是 IAsyncExceptionFilter 介面,差異只是 IAsyncExceptionFilter 實現非同步等待(看需求),接著建立例外發生時要用到的類別名為 BombExceptionFilter ,類別中實作 IExceptionFilter 中的 OnException 方法裡面定義當例外發生時要做的處理機制
  1. public class BombExceptionFilter : IExceptionFilter
  2. {
  3. private ILogger _log;
  4. public BombExceptionFilter(ILogger log)
  5. {
  6. _log = log;
  7. }
  8. public void OnException(ExceptionContext context)
  9. {
  10. var status = HttpStatusCode.InternalServerError;
  11. var message = string.Empty;
  12. var exceptionType = context.Exception.GetType();
  13. if (exceptionType == typeof(CustomerException))
  14. {
  15. message = context.Exception.Message;
  16. }
  17. else
  18. {
  19. message = "something wrong";
  20. }
  21. // log
  22. _log.LogError(context.Exception, $"Ex massage: {message}, StackTrace: {context.Exception.StackTrace}", context.Exception);
  23. // 設定 exception 已處理完畢
  24. context.ExceptionHandled = true;
  25. var response = context.HttpContext.Response;
  26. response.StatusCode = (int)status;
  27. response.ContentType = "application/json";
  28. context.Result = new ObjectResult(new ApiResponse { Timestamp = DateTimeOffset.UtcNow, Message = message, Result = "我出包了,請給我一點時間" });
  29. }
  30. }
OnException 中邏輯為當發生例外時,會根據發生例外的種類 exception Type 來取得回傳的錯誤訊息並定義再回傳的 message 屬性中;另外也在第38行將錯誤紀錄在 log 中,可搭配習慣的 log 解決方案,像是 log4net、serilog 看團隊習慣決定;使用 apiResponse 類別作為回傳的格式,其中定義了 timestamp、Message 及 result 等屬性,當發生錯誤時在 result 會定義 "我出包了,請再給我一點時間" 訊息告知呼叫 client 端。

接著在 startUp.cs 的 ConfigureServices 區塊加上 BombExceptionFilter 下列代碼
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllers();
  4.  
  5. services.AddScoped<BombExceptionFilter>();
  6. }
最後一步在 controller 中的 action 加上 BombExceptionFilter attribute
  1. [ServiceFilter(typeof(BombExceptionFilter))]
  2. [HttpGet]
  3. public IEnumerable Get()
  4. {
  5. throw new Exception("我是醬爆,我要爆了!!!");
  6. var rng = new Random();
  7. return Enumerable.Range(1, 5).Select(index => new WeatherForecast
  8. {
  9. Date = DateTime.Now.AddDays(index),
  10. TemperatureC = rng.Next(-20, 55),
  11. Summary = Summaries[rng.Next(Summaries.Length)]
  12. })
  13. .ToArray();
  14. }
接著我們 run 應用程式看當發生錯誤時自定義的 BombExceptionFilter exception Filter 是否有生效,可以看到結果如下設定成功
有關在 ASP.NET Core 中更詳細的 exception filters 可以參考 MSDN 文件

使用 Middleware
Middleware 與 exception 相比就簡單許多,在 如何在 ASP.NET Core Middleware 加上單元測試 Unititest 文章中有詳細介紹關於 middleware 基本使用方式,這裡就不在多加說明,新增 CustomerExceptionMiddleware 的中介層來處理 exception 的邏輯,代碼如下
  1. public class CustomerExceptionMiddleware
  2. {
  3. private readonly RequestDelegate _next;
  4. private readonly ILogger _logger;
  5. public CustomerExceptionMiddleware(RequestDelegate next, ILogger logger)
  6. {
  7. _next = next;
  8. _logger = logger;
  9. }
  10. public async Task Invoke(HttpContext context)
  11. {
  12. try
  13. {
  14. await _next(context);
  15. }
  16. catch (Exception ex)
  17. {
  18. await HandleException(context, ex);
  19. }
  20. }
  21. private Task HandleException(HttpContext context, Exception ex)
  22. {
  23. context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
  24. context.Response.ContentType = "application/json";
  25. var message = ex.GetType() == typeof(CustomerException) ? ex.Message : "something wrong";
  26. // log
  27. _logger.LogError(ex, $"Ex massage: {message}, StackTrace: {ex.StackTrace}", ex);
  28. var result = JsonConvert.SerializeObject(new ApiResponse
  29. {Timestamp = DateTimeOffset.UtcNow, Message = message, Result = "我出包了(Middleware)"});
  30. return context.Response.WriteAsync(result);
  31. }
  32. }
在代碼中加上 try/catch 將發生錯誤的 exception 捕捉起來,並設定回傳 statusCode 為 IIS Server Error 以及與上述 exception filter 相同將錯誤記錄在 log 中,另外將錯誤的 result 設定為 "我出包了(Middleware)" 區別是 middleware catch 到的錯誤,接著新增擴充方法 UseCustomerExceptionMiddleware 方便在 startup 使用
  1. public static class ApplicationBuilderExtension
  2. {
  3. public static IApplicationBuilder UseCustomerExceptionMiddleware(this IApplicationBuilder builder)
  4. {
  5. return builder.UseMiddleware();
  6. }
  7. }
修改 startup.cs 將稍早的 BombExceptionFilter 註解,Configure 代碼加上 UseCustomerExceptionMiddleware 方法
  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7.  
  8. ...
  9. app.UseCustomerExceptionMiddleware();
  10. ...
  11. }
重新執行應用程式,可以發現當 application 發生 error 的時候有 catch 到錯誤訊息
宣告使用 middleware 成功 !

感想
以上針對 exception 在 ASP.NET Core 的處理方式,分別是使用 exception filters 與 middleware 兩種方法,另外還可以使用內建的 UseExceptionHandler 方法達到類似的效果,詳細可以參考 Cash 大大 ASP.NET Core 使用內建的 ExceptionHandler Middleware 實作全站 Exception 處理的文章說明,這裡就不再班門弄斧的多加說明;還有在開發中有需要注意順序性的問題,可以透過 MSDN 的圖片了解到 middleware 與 filter 的執行順序,避免認知錯誤造成自己在開發上時間的浪費,希望這篇文章可以幫助到需要的朋友 :)

參考
ASP.NET Core Web API exception handling
ASP.NET Core 中的篩選條件
Asp.NetCore依赖注入和管道方式的异常处理及日志记录
ASP.NET Core 使用內建的 ExceptionHandler Middleware 實作全站 Exception 處理
[鐵人賽 Day17] ASP.NET Core 2 系列 - 例外處理 (Exception Handler)
Using ExceptionFilter for exception handling in AspNet Core Web API

Related Posts:

  • [NETCore] ASP.NET Core 中的例外處理方式前言 錯誤處理一直是開發中的重要環節之一,如果在程式發生異常錯誤的當下有效的將錯誤訊息完整的記錄下來,可以大大的節省 debug 的時間與效率,反過來如果在開發時沒有考慮到異常處理的機制,可能在發生問題時要找到錯誤的原因難度就會提高,因此在開發時必須要考慮到異常處理的機制,過去公司都會將 exception 訊息透過推(push)或是拉(pull)的方式傳送到 Logstash 再搭配 Elasticsearch 與 Kibana 搜尋到相對… Read More

0 意見:

張貼留言

Copyright © 2025 m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com