1.繼承IExceptionFilter只是用于記錄全局異常異常日志,現(xiàn)在我想記錄每個請求的日志并且入庫。
需要用到IAsyncActionFilter,繼承該接口,用于記錄每一個action方法的請求信息,作用是記錄每個操作的記錄,簡單點來講就是記錄哪個人調(diào)用了哪個方法。
添加一個繼承該接口的過濾器,并添加所需操作,這里就是記錄每一個請求的操作記錄
public class LogActionFilter(ILoggingService loggingService) : IAsyncActionFilter { private readonly ILoggingService _loggingService = loggingService; /// <summary> /// 日志記錄-Action層級 /// </summary> public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var result = await next(); var log = new LogModel { Content = result.Exception == null ? "操作記錄" : result.Exception.Message, CreateTime = DateTime.Now, Level = result.Exception == null ? 0 : 1, Controller = result.HttpContext.Request.RouteValues["controller"] as string ?? "", Action = result.HttpContext.Request.RouteValues["action"] as string ?? "", Source = result.HttpContext.Request.Path, Name = "測試用戶" }; await _loggingService.InsertAsync(log); } }
注入該過濾器到全局。這里我是為了簡潔program,把注入的操作添加到靜態(tài)類,也可以在program里面build.services…注入到全局控制器…
/// <summary> /// 注冊記錄日志過濾器 /// </summary> public static void AddActionFilterModule(this IServiceCollection services) { services.AddControllers(option => { option.Filters.Add(typeof(LogActionFilter)); }); }
本身并沒有太多操作,但是沒用過難免就不知道,比如我…以前都是哪里記錄日志就單獨寫一下,沒有使用過全局的。
分割線上面的可以記錄到正常的操作記錄,后面我添加了jwt登錄驗證,就引發(fā)了另外的問題。
假如token錯誤,或者未輸入,我做了處理,讓錯誤異常返回統(tǒng)一的格式,而不是單純的把異常拋出到前端。但是因為此時還沒有進入控制器,屬于是中間件級別的異常,上面的IExceptionFilter和IAsyncActionFilter過濾器只是過濾action級別的。因此異常和日志無法記錄。
解決方式是通過中間件!
不知道大家是不是經(jīng)常使用中間件,不算難,但是不經(jīng)常用,就總會忘。
中間件的添加是app.UseMiddleware<>()
也可以直接
app.Use(async (context, next)=>{});
中間件的執(zhí)行順序是執(zhí)行完第一個再執(zhí)行第二個。依次執(zhí)行
添加一個類,RequestDelegate 是必要的一個委托條件,作用就是執(zhí)行下一個中間件。ILoggingService是我記錄日志入庫的接口,注入進來
要添加一個Invoke方法。參數(shù)為:HttpContext。 這個參數(shù)就是HTTP請求的一些信息。
通過代碼可知:_next(context) 就是把http請求內(nèi)容放到委托,用于執(zhí)行下一個中間件操作。
因為中間件異常通過過濾器獲取不到,這里就通過trycatch來在中間件里攔截,進行處理。
public class LogAopFilter(ILoggingService loggingService, RequestDelegate next) { private readonly RequestDelegate _next = next; private readonly ILoggingService _loggingService = loggingService; public async Task Invoke(HttpContext context) { try { await _next(context); } catch (HttpException ex) { #region 過濾中間件異常并自定義返回結(jié)果 context.Response.StatusCode = ex.StatusCode; context.Response.ContentType = "application/json"; var errorResponse = new R { Code = StateCode.ERROR, Data = null, Msg = ex.Message }; var jsonErrorResponse = JsonConvert.SerializeObject(errorResponse); await context.Response.WriteAsync(jsonErrorResponse); #endregion 過濾中間件異常并自定義返回結(jié)果 #region 異常日志記錄 var log = new LogModel { Content = ex.Message, CreateTime = DateTime.Now, Level = 1, Source = ex.Source ?? "", Name = "測試用戶" }; await _loggingService.InsertAsync(log); #endregion 異常日志記錄 } } }
最后就是注入中間件即可。還是為了簡潔program。做一個靜態(tài)類注入直接調(diào)用即可
/// <summary> /// 統(tǒng)一添加中間件 /// 1.捕捉中間件級別異常 /// </summary> /// <param name="builder"></param> public static void UseAopModule(this IApplicationBuilder builder) { builder.UseMiddleware<LogAopFilter>(); }
以下是另一種寫法:在program里面直接使用app.use ,一開始是在program里面寫的,嫌棄太長了,搞得program看著不好看,就沒這么寫,看個人喜歡吧
app.Use(async (context, next) => { try { await next.Invoke(context); } catch (HttpException ex) { #region 過濾中間件異常 context.Response.StatusCode = ex.StatusCode; context.Response.ContentType = "application/json"; var errorResponse = new R { Code = StateCode.ERROR, Data = null, Msg = ex.Message }; var jsonErrorResponse = JsonConvert.SerializeObject(errorResponse); await context.Response.WriteAsync(jsonErrorResponse); #endregion 過濾中間件異常 } });
下面就是效果截圖。不輸入token讓他報錯,一般不處理他就是401。因為我想更人性化,會分辨出是token沒有或者是token錯誤,或者是token過期,我認證的部分有處理,丟出相應的錯誤信息,為的就是方便好處理,故此我的這個接口正常情況下是401+錯誤信息。
錯誤信息我經(jīng)過中間件的處理,和正常接口返回的結(jié)構(gòu)是一樣的,這樣方便前端去處理,只記住一種返回結(jié)構(gòu)就行了


日志內(nèi)容截圖

大致就是如此啦
追加一部分,原本token認證那邊我是手動拋異常來讓程序的全局異常來捕捉。調(diào)試的時候總是會中斷程序,雖然實際發(fā)布了并不會影響什么,但是目前太影響了。所以稍微改了一下,大體上沒什么變化。代碼如下 。
這是token認證里面的代碼,OnChallenge 就是token異常的時候會執(zhí)行,在此修改狀態(tài)碼,和返回內(nèi)容即可。不懂這塊兒可以搜一下jwt相關內(nèi)容,或者用不到可以不看
這里的處理是用于返回自定義內(nèi)容。
//當JWT Bearer認證失敗時,即請求未包含有效的JWT令牌或令牌驗證失敗,該事件會被觸發(fā) OnChallenge = context => { context.HandleResponse(); context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; var errMsg = $"Token無效: {(string.IsNullOrEmpty(context.ErrorDescription) ? "請輸入正確的Token" : context.ErrorDescription)}"; var errSource = "Token認證失敗"; var errorResponse = new R { Code = StateCode.ERROR, Data = null, Msg = errMsg }; context.HttpContext.Items.Add("errror", errMsg); context.HttpContext.Items.Add("source", errSource); var jsonErrorResponse = JsonConvert.SerializeObject(errorResponse); return context.Response.WriteAsync(jsonErrorResponse); }
前文說過了, Token認證這一部分的異常算是中間件級別,不會觸發(fā)全局異常過濾器。只能在中間件處理(也許有別的方法),但取消掉了手動異常,日志可能就無法完整記錄,因此…看代碼!
其實就是加上finally,最后判斷下是不是200狀態(tài)碼,如果不是就記錄日志,200的狀態(tài)碼那就是成功的請求,自有異常過濾器處理,這里主要針對中間件級別的異常處理的。
try { await _next(context); } catch (HttpException ex) { } finally { if (context.Response.StatusCode != 200) { #region 異常日志記錄 var log = new LogModel { Content = context.Items["errror"] as string ?? "操作失敗", CreateTime = DateTime.Now, Level = 1, Source = context.Items["source"] as string ?? "中間件異常", Name = "測試用戶" }; await _loggingService.InsertAsync(log); #endregion 異常日志記錄 } }
真的拜拜了
浙公網(wǎng)安備 33010602011771號