前提:git
須要nuget Microsoft.Extensions.Logging.Log4Net.AspNetCore 2.2.6;github
Swashbuckle.AspNetCore 我暫時用的是 4.01;json
描述:經過 Filters 攔截器獲取 Api 請求內容及響應內容,並記錄到日誌文件;api
有文中代碼記錄接口每次請求及響應狀況以下圖:app
解決辦法:async
步驟1 配置 Swagger 接口文檔ide
對startup.cs 進行修改代碼以下:函數
ConfigureServices 中增長Swagger 配置測試
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Version = "v1", Title = "Filters 過濾器測試Api", Description = @"經過 IActionFilter, IAsyncResourceFilter 攔截器攔截請求及響應上下文並記錄到log4日誌" }); c.IncludeXmlComments(this.GetType().Assembly.Location.Replace(".dll", ".xml"), true); //是須要設置 XML 註釋文件的完整路徑 });
對 Configure Http管道增長 SwaggerUiui
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); app.UseSwagger(); app.UseSwaggerUI(o => { o.SwaggerEndpoint("/swagger/v1/swagger.json", "Filters 過濾器測試Api"); }); }
步驟2 建立Log4net 日誌幫助類 LogHelper.cs
/// <summary> /// 日誌幫助類 /// </summary> public static class LogHelper { /// <summary> /// 日誌提供者 /// </summary> private static ILogger logger; /// <summary> /// 靜太方法構造函數 /// </summary> static LogHelper() { logger = new LoggerFactory().AddConsole().AddDebug().AddLog4Net().CreateLogger("Logs"); } /// <summary> /// 打印提示 /// </summary> /// <param name="message">日誌內容</param> public static void Info(object message) { logger.LogInformation(message?.ToString()); } /// <summary> /// 打印錯誤 /// </summary> /// <param name="message">日誌內容</param> public static void Error(object message) { logger.LogError(message?.ToString()); } /// <summary> /// 打印錯誤 /// </summary> /// <param name="ex">異常信息</param> /// <param name="message">日誌內容</param> public static void Error(Exception ex, string message) { logger.LogError(ex, message); } /// <summary> /// 調試信息打印 /// </summary> /// <param name="message"></param> public static void Debug(object message) { logger.LogDebug(message?.ToString()); } }
步驟3 定義可讀寫的Http 上下文流接口 IReadableBody.cs 及 http 請求上下文中間件 HttpContextMiddleware.cs
/// <summary> /// 定義可讀Body的接口 /// </summary> public interface IReadableBody { /// <summary> /// 獲取或設置是否可讀 /// </summary> bool IsRead { get; set; } /// <summary> /// 讀取文本內容 /// </summary> /// <returns></returns> Task<string> ReadAsStringAsync(); }
/// <summary> /// Http 請求中間件 /// </summary> public class HttpContextMiddleware { /// <summary> /// 處理HTTP請求 /// </summary> private readonly RequestDelegate next; /// <summary> /// 構造 Http 請求中間件 /// </summary> /// <param name="next"></param> public HttpContextMiddleware(RequestDelegate next) { this.next = next; } /// <summary> /// 執行響應流指向新對象 /// </summary> /// <param name="context"></param> /// <returns></returns> public Task Invoke(HttpContext context) { context.Response.Body = new ReadableResponseBody(context.Response.Body); return this.next.Invoke(context); } /// <summary> /// 可讀的Response Body /// </summary> private class ReadableResponseBody : MemoryStream, IReadableBody { /// <summary> /// 流內容 /// </summary> private readonly Stream body; /// <summary> /// 獲取或設置是否可讀 /// </summary> public bool IsRead { get; set; } /// <summary> /// 構造自定義流 /// </summary> /// <param name="body"></param> public ReadableResponseBody(Stream body) { this.body = body; } /// <summary> /// 寫入響應流 /// </summary> /// <param name="buffer"></param> /// <param name="offset"></param> /// <param name="count"></param> public override void Write(byte[] buffer, int offset, int count) { this.body.Write(buffer, offset, count); if (this.IsRead) { base.Write(buffer, offset, count); } } /// <summary> /// 寫入響應流 /// </summary> /// <param name="source"></param> public override void Write(ReadOnlySpan<byte> source) { this.body.Write(source); if (this.IsRead) { base.Write(source); } } /// <summary> /// 刷新響應流 /// </summary> public override void Flush() { this.body.Flush(); if (this.IsRead) { base.Flush(); } } /// <summary> /// 讀取響應內容 /// </summary> /// <returns></returns> public Task<string> ReadAsStringAsync() { if (this.IsRead == false) { throw new NotSupportedException(); } this.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(this)) { return reader.ReadToEndAsync(); } } protected override void Dispose(bool disposing) { this.body.Dispose(); base.Dispose(disposing); } } }
步驟4 配置Http管通增長 Http請求上下文中件間
打開 Startup.cs ,對管通 Configure 增長以下中間件代碼:
app.UseMiddleware<HttpContextMiddleware>();
步驟5 增長Api 過濾器 ApiFilterAttribute
/// <summary> /// Api 過濾器,記錄請求上下文及響應上下文 /// </summary> public class ApiFilterAttribute : Attribute, IActionFilter, IAsyncResourceFilter { /// <summary> /// 請求Api 資源時 /// </summary> /// <param name="context"></param> /// <param name="next"></param> /// <returns></returns> public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { // 執行前 try { await next.Invoke(); } catch { } // 執行後 await OnResourceExecutedAsync(context); } /// <summary> /// 記錄Http請求上下文 /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task OnResourceExecutedAsync(ResourceExecutingContext context) { var log = new HttpContextMessage { RequestMethod = context.HttpContext.Request.Method, ResponseStatusCode = context.HttpContext.Response.StatusCode, RequestQurey = context.HttpContext.Request.QueryString.ToString(), RequestContextType = context.HttpContext.Request.ContentType, RequestHost = context.HttpContext.Request.Host.ToString(), RequestPath = context.HttpContext.Request.Path, RequestScheme = context.HttpContext.Request.Scheme, RequestLocalIp = (context.HttpContext.Request.HttpContext.Connection.LocalIpAddress.MapToIPv4().ToString() + ":" + context.HttpContext.Request.HttpContext.Connection.LocalPort), RequestRemoteIp = (context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString() + ":" + context.HttpContext.Request.HttpContext.Connection.RemotePort) }; //獲取請求的Body //數據流倒帶 context.HttpContext.Request.EnableRewind(); if (context.HttpContext.Request.Body.CanSeek) { using (var requestSm = context.HttpContext.Request.Body) { requestSm.Position = 0; var reader = new StreamReader(requestSm, Encoding.UTF8); log.RequestBody = reader.ReadToEnd(); } } //將當前 http 響應Body 轉換爲 IReadableBody if (context.HttpContext.Response.Body is IReadableBody body) { if (body.IsRead) { log.ResponseBody = await body.ReadAsStringAsync(); } } if (string.IsNullOrEmpty(log.ResponseBody) == false && log.ResponseBody.Length > 200) { log.ResponseBody = log.ResponseBody.Substring(0, 200) + "......"; } LogHelper.Debug(log); } /// <summary> /// Action 執行前 /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { //設置 Http請求響應內容設爲可讀 if (context.HttpContext.Response.Body is IReadableBody responseBody) { responseBody.IsRead = true; } } /// <summary> /// Action 執行後 /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { } }
步驟6 對須要記錄請求上下文日誌的接口加上特性 [ApiFilter]
[ApiFilter] [Route("api/[controller]/[Action]")] [ApiController] public class DemoController : ControllerBase { ....... }
Demo 地址:https://github.com/intotf/netCore/tree/master/WebFilters