【.NET7 】使い回せるロギングミドルウェアつくた

# 環境
- .NET7

# 仕様
APIのリクエストとレスポンスをログ上に書き出す仕様で作成
GET以外のログを書き出す
認証されているユーザ名も書き出す

# 流れ
- NuGetにてパッケージ追加
    - NLog
    - NLog.Web.AspNetCore
- `nlog.config` ファイルを作成
- `LoggingMiddleware`ファイル作成
- `Program.cs`にミドルウェア追加！


## `nlog.config` ファイルを作成
```
<?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">
	<extensions>
		<add assembly="NLog.Web.AspNetCore" />
	</extensions>
	<variable name="layoutDefine" value="${longdate} [${level:padding=-5}] ${message} ${exception:format=tostring}" />
	<targets async="true">
		<target xsi:type="File" name="log" fileName="${basedir}/../logs/log-${shortdate}.log" layout="${layoutDefine}" />
	</targets>
	<rules>
		<logger name="Microsoft.*" maxLevel="Info" final="true" />
		<logger name="*" levels="Info,Error,Fatal,Warn" writeTo="log" />
	</rules>
</nlog>
```
${basedir}ってとこがよくわからんくて調べるの大変だった

## - `LoggingMiddleware`ファイル作成
```

using System.Security.Claims;
using System.Text;
using NLog;
using NLog.Web;

namespace {プロジェクト名}.Common.Middleware;

public class LoggingMiddleware
{
	private readonly RequestDelegate _next;

	public LoggingMiddleware(RequestDelegate next)
	{
		_next = next;
	}

	public async Task Invoke(HttpContext context)
	{
		var user = context.Request.HttpContext.User.FindFirst(ClaimTypes.UserData)?.Value ?? "unknown";
		var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();

		context.Request.EnableBuffering();

		var request = context.Request;
		if (request.Method != "GET")
		{
			/*
			* リクエスト
			*/
			using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
			{
				var body = await reader.ReadToEndAsync();
				var _body = body.Replace(" ", string.Empty).Replace(Environment.NewLine, string.Empty);  //空白削除
				logger.Info(message: $"{user} {request.Method} {request.Path} {_body}");
				reader.BaseStream.Position = 0;
			}

			/*
			* レスポンス
			* MEMO: 標準のストリームでは再読み込み(バッファリング)できないため差し替える 
			*/
			//標準ストリームは一時退避
			var originBodyStream = context.Response.Body;
			try
			{
				using (var memoryStream = new MemoryStream())
				{
					//ここで差し替え
					context.Response.Body = memoryStream;
					//ここでコントローラのメソッドが実行される
					await _next(context);
					//シーク位置を最初に戻す
					memoryStream.Position = 0;

					var body = await new StreamReader(memoryStream).ReadToEndAsync();
					//シーク位置を最初に戻す
					memoryStream.Position = 0;

					if (IsSuccess(context.Response.StatusCode))
					{
						logger.Info($"{user} Success");
					}
					else
					{
						logger.Info($"{user} Failure {body}");
					}

					//ここで元のストリームを戻す
					await memoryStream.CopyToAsync(originBodyStream);
				}
			}
			catch
			{
				context.Response.Body = originBodyStream;
			}
		}
		else
		{
			//GETは何も処理なし
			await _next(context);
		}
	}

	private static bool IsSuccess(int statusCode)
	{
		return 200 <= statusCode && statusCode <= 299;
	}
}

public static class LoggingMiddlewareExtensions
{
	public static IApplicationBuilder UseLogging(this IApplicationBuilder builder)
	{
		return builder.UseMiddleware<LoggingMiddleware>();
	}
}

```
今回はGET以外のHTTPメソッドのリクエストとレスポンスをログに書き出す仕様で作った


## `Program.cs`にミドルウェア追加！
```
app.UseAuthentication();
app.UseAuthorization();

app.UseLogging();   //認証情報を取得するため認証ミドルウェアより後に追加

app.MapControllers();

app.Run();
```