# 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 的運作結果

```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/)