# log request and response ###### tags: `C#` ## 自建 middleware middleware 掛載在最外層,可記錄到最原始request及最終 response,只是要注意掛載順序 ```csharp //RecordMiddleware.cs using System.Text; namespace test.api.Middlewares; public class RecordMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RecordMiddleware> _logger; private readonly string[] _recordStartPath = { "/api/wallet", "/wallet"}; public RecordMiddleware(RequestDelegate next, ILogger<RecordMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext httpContext) { var guid = Guid.NewGuid(); var path = httpContext.Request.Path.ToString().ToLower(); if (!IsRecordPath(path)) { await _next(httpContext); return; } var requestBody = await GetRequest(httpContext.Request); _logger.LogInformation($"guid:{guid}, path:{path}, request:{requestBody}"); var originBody = httpContext.Response.Body; var memStream = new MemoryStream(); httpContext.Response.Body = memStream; await _next(httpContext); memStream.Position = 0; var originalResponseBody = new StreamReader(memStream).ReadToEnd(); _logger.LogInformation($"guid:{guid}, response:{originalResponseBody}"); memStream.Position = 0; //不管是否有修改都需塞回原本的 Response.Body await memStream.CopyToAsync(originBody).ConfigureAwait(false); } private static async Task<string> GetRequest(HttpRequest request) { if (request.HasFormContentType) { var form = await request.ReadFormAsync(); var enumerable = form.Select(x=>$"{x.Key}:{x.Value}"); return string.Join(',', enumerable); //return JsonConvert.SerializeObject(form); } else { var body = await GetBody(request); return body; } } private static async Task<string> GetBody(HttpRequest request) { request.EnableBuffering(); using var sr = new StreamReader(request.Body, Encoding.UTF8); var body = await sr.ReadToEndAsync(); //stream 讀取後會變成 disposed。目前不知道其他作法,因此只能重新 assign request, if not will get Cannot access a disposed object var requestContent = new StringContent(body, Encoding.UTF8, "application/json"); request.Body = await requestContent.ReadAsStreamAsync(); request.ContentType = "application/json"; request.Body.Position = 0; return body; } private bool IsRecordPath(string path) { return true; return _recordStartPath.Any(s => path.StartsWith(s)); } } //startup.cs 註冊 app.UseMiddleware<RecordMiddleware>(); ``` #### 相關連結 [Asp.net core Middleware change request response](https://hackmd.io/UEm0qOIwSdmemX4CepkBxQ) ## 自建 action filter 可是自已需求掛載到需要的 controller 上,但 filter 本身運作機制的問題,會無法記錄到 middleware 及 exception filter 的運作結果 ![Request 及 Response 的運作流程](https://i.imgur.com/uTuwuBi.png) ```csharp //RecordActionFilter using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Newtonsoft.Json; namespace test.api.Middlewares; public class RecordActionFilter : IActionFilter { private readonly ILogger<RecordActionFilter> _logger; public RecordActionFilter(ILogger<RecordActionFilter> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { var readActionExcutedRequestBody = ReadActionExcutedRequestBody(context); _logger.LogInformation("test "+readActionExcutedRequestBody); } public void OnActionExecuted(ActionExecutedContext context) { var actionExcutedResponseBody = GetActionExcutedResponseBody(context); _logger.LogInformation("test "+actionExcutedResponseBody); } private string ReadActionExcutedRequestBody(ActionExecutingContext context) { string requestBody = ""; if (context != null) { var request = context.HttpContext.Request; request.Body.Position = 0; StreamReader stream = new StreamReader(request.Body); requestBody = stream.ReadToEnd(); request.Body.Position = 0; } return requestBody; } private string GetActionExcutedResponseBody(ActionExecutedContext context) { string responseBody = ""; if (context.Result != null) { if (context.Result is ObjectResult) responseBody = JsonConvert.SerializeObject(((ObjectResult)context.Result).Value); if (context.Result is JsonResult) responseBody = JsonConvert.SerializeObject(((JsonResult)context.Result).Value); } return responseBody; } } //startup.cs 中註冊 services.AddScoped<RecordActionFilter>(); //掛載到 controller 上 [ServiceFilter(typeof(RecordActionFilter))] ``` #### 相關連結 [asp.net core ActionExecutedContext 获取Request.Body内容](https://www.cnblogs.com/sunliyuan/p/14250938.html) [ASP.NET Core 2 系列 - Filters](https://blog.johnwu.cc/article/ironman-day14-asp-net-core-filters.html) ## .net core 'UseHttpLogging' middleware in .net core 有內建的 middleware, 但需用原生的 log, 用 Serilog 好像不 work ```csharp //in startup.cs builder.Services.AddHttpLogging(logging => { logging.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.ResponseBody; }); //設定記 response body app.UseHttpLogging(); //啟用 // in appsettings.Development.json LogLevel add { "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information" } ``` #### 相關連結 [HTTP Logging in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-logging/?view=aspnetcore-6.0#enabling-http-logging) [HTTP Logs With 'UseHttpLogging' Middleware .NET6 Feature](https://www.learmoreseekmore.com/2021/12/dotnet6-http-logs-with-usehttplogging-middleware.html) ## .net core 'UseSerilogRequestLogging' middleware 使用 Serilog 可用這個,但只記錄時間即回傳狀態 ```csharp app.UseSerilogRequestLogging(); ``` #### 相關連結 [Reducing log verbosity with Serilog RequestLogging](https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/)