在業務系統,異常處理是全部開發人員必須面對的問題,在必定程度上,異常處理的能力反映出開發者對業務的駕馭水平;本章將着重介紹如何在 WebApi 程序中對異常進行捕獲,而後利用 Nlog 組件進行記錄;同時,還將介紹兩種不一樣的
異常捕獲方式:管道捕獲/服務過濾;經過本練習,將學習到如何捕獲異常、處理異常跳轉、記錄異常信息。git
首先,建立一個 WebApi 項目,選擇 Asp.Net Core Web 應用程序;github
[HttpGet] public ActionResult<IEnumerable<string>> Get() { throw new Exception("出錯了....."); return new string[] { "value1", "value2" }; }
若是你把環境變量設置爲 ASPNETCORE_ENVIRONMENT=Production ,你會發現,在異常發生的時候,你獲得了一個空白頁。web
在傳統的 Asp.Net MVC 應用程序中,咱們通常都使用服務過濾的方式去捕獲和處理異常,這種方式很是常見,並且可用性來講,體驗也不錯,幸運的是 Asp.Net Core 也完整的支持該方式,接下來建立一個全局異常處理類 CustomerExceptionFilterjson
public class CustomerExceptionFilter : Attribute, IExceptionFilter { private readonly ILogger logger = null; private readonly IHostingEnvironment environment = null; public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment) { this.logger = logger; this.environment = environment; } public void OnException(ExceptionContext context) { Exception exception = context.Exception; string error = string.Empty; void ReadException(Exception ex) { error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException); if (ex.InnerException != null) { ReadException(ex.InnerException); } } ReadException(context.Exception); logger.LogError(error); ContentResult result = new ContentResult { StatusCode = 500, ContentType = "text/json;charset=utf-8;" }; if (environment.IsDevelopment()) { var json = new { message = exception.Message, detail = error }; result.Content = JsonConvert.SerializeObject(json); } else { result.Content = "抱歉,出錯了"; } context.Result = result; context.ExceptionHandled = true; } }
構造方法中,定義了兩個參數,用於記錄異常日誌和獲取程序運行環境變量api
private readonly ILogger logger = null; private readonly IHostingEnvironment environment = null; public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment) { this.logger = logger; this.environment = environment; }
須要引用 Nuget 包 NLog.Extensions.Logging/NLog.Web.AspNetCore ,並在 Startup.cs 文件的 Configure 方法中添加擴展安全
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory) { // 將 NLog factory.AddConsole(Configuration.GetSection("Logging")) .AddNLog() .AddDebug(); var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config"); env.ConfigureNLog(nlogFile); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); }
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info"> <!-- Load the ASP.NET Core plugin --> <extensions> <add assembly="NLog.Web.AspNetCore"/> </extensions> <!-- Layout: https://github.com/NLog/NLog/wiki/Layout%20Renderers --> <targets> <target xsi:type="File" name="errorfile" fileName="/data/logs/logfilter/error-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}| ${message} ${exception}|${aspnet-Request-Url}" /> <target xsi:type="Null" name="blackhole" /> </targets> <rules> <logger name="Microsoft.*" minlevel="Error" writeTo="blackhole" final="true" /> <logger name="*" minlevel="Error" writeTo="errorfile" /> </rules> </nlog>
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // 將異常過濾器注入到容器中 services.AddScoped<CustomerExceptionFilter>(); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
[ServiceFilter(typeof(CustomerExceptionFilter))] [Route("api/[controller]"), ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { throw new Exception("出錯了....."); return new string[] { "value1", "value2" }; } }
接下來利用 .NetCore 的管道模式,在中間件中對異常進行捕獲,首先,建立一箇中間件app
public class ExceptionMiddleware { private readonly RequestDelegate next; private readonly ILogger logger; private IHostingEnvironment environment; public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostingEnvironment environment) { this.next = next; this.logger = logger; this.environment = environment; } public async Task Invoke(HttpContext context) { try { await next.Invoke(context); var features = context.Features; } catch (Exception e) { await HandleException(context, e); } } private async Task HandleException(HttpContext context, Exception e) { context.Response.StatusCode = 500; context.Response.ContentType = "text/json;charset=utf-8;"; string error = ""; void ReadException(Exception ex) { error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException); if (ex.InnerException != null) { ReadException(ex.InnerException); } } ReadException(e); if (environment.IsDevelopment()) { var json = new { message = e.Message, detail = error }; error = JsonConvert.SerializeObject(json); } else error = "抱歉,出錯了"; await context.Response.WriteAsync(error); } }
建立 HandleException(HttpContext context, Exception e) 處理異常,判斷是 Development 環境下,輸出詳細的錯誤信息,非 Development 環境僅提示調用者「抱歉,出錯了」,同時使用 NLog 組件將日誌寫入硬盤;一樣,在 Startup.cs 中將 ExceptionMiddleware 加入管道中框架
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory) { // 將 NLog factory.AddConsole(Configuration.GetSection("Logging")) .AddNLog() .AddDebug(); var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config"); env.ConfigureNLog(nlogFile); // ExceptionMiddleware 加入管道 app.UseMiddleware<ExceptionMiddleware>(); //if (env.IsDevelopment()) //{ // app.UseDeveloperExceptionPage(); //} app.UseMvc(); }
在本例中,經過依賴注入和管道中間件的方式,演示了兩種不一樣的全局捕獲異常處理的過程;值得注意到是,兩種方式對於 NLog 的使用,都是同樣的,沒有任何差異,代碼無需改動;實際項目中,也是應當區分不一樣的業務場景,輸出不一樣的日誌信息,無論是從安全或者是用戶體驗友好性上面來講,都是很是值得推薦的方式,全局異常捕獲處理,徹底和業務剝離。async
https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.LogFilter學習