serilogの出力.netコアに「リクエストボディ」を追加するにはどうすればよいですか?

Aug 25 2020

.net core3.1に基づくWebAPIがあります。

SeriLogライブラリをロガーとして使用しています。

これが私のSeriLog構成です。Serilogは「appsettings.json」から構成されています。

ログの出力に「リクエストボディ」パラメータが存在する場合は追加する必要があります。これを構成する方法はありますか?また、ログ出力を共有します。

回答

1 ZhiLv Aug 25 2020 at 12:40

このスレッドとこの記事を確認してください。要求と応答の情報(要求の本文など)をログに記録するには、ミドルウェアを作成し、要求と応答の本文をキャプチャします(ストリームであるため、最初にそれらを読み取り、次にSerilogsメソッドを使用してログに記録する必要があります) 。

以下のようにコーディングします。

public class SerilogRequestLogger
{
    readonly RequestDelegate _next;

    public SerilogRequestLogger(RequestDelegate next)
    {
        if (next == null) throw new ArgumentNullException(nameof(next));
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));

        // Push the user name into the log context so that it is included in all log entries
        LogContext.PushProperty("UserName", httpContext.User.Identity.Name);

        // Getting the request body is a little tricky because it's a stream
        // So, we need to read the stream and then rewind it back to the beginning
        string requestBody = "";
        HttpRequestRewindExtensions.EnableBuffering(httpContext.Request);
        Stream body = httpContext.Request.Body;
        byte[] buffer = new byte[Convert.ToInt32(httpContext.Request.ContentLength)];
        await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length);
        requestBody = Encoding.UTF8.GetString(buffer);
        body.Seek(0, SeekOrigin.Begin);
        httpContext.Request.Body = body;

        Log.ForContext("RequestHeaders", httpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), destructureObjects: true)
           .ForContext("RequestBody", requestBody)
           .Debug("Request information {RequestMethod} {RequestPath} information", httpContext.Request.Method, httpContext.Request.Path);

        Log.Information(string.Format("Request Body: {0} ", requestBody));
        // The reponse body is also a stream so we need to:
        // - hold a reference to the original response body stream
        // - re-point the response body to a new memory stream
        // - read the response body after the request is handled into our memory stream
        // - copy the response in the memory stream out to the original response stream
        using (var responseBodyMemoryStream = new MemoryStream())
        {
            var originalResponseBodyReference = httpContext.Response.Body;
            httpContext.Response.Body = responseBodyMemoryStream;

            await _next(httpContext);

            httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
            var responseBody = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
            httpContext.Response.Body.Seek(0, SeekOrigin.Begin);

            Log.ForContext("RequestBody", requestBody)
               .ForContext("ResponseBody", responseBody)
               .Debug("Response information {RequestMethod} {RequestPath} {statusCode}", httpContext.Request.Method, httpContext.Request.Path, httpContext.Response.StatusCode);

            await responseBodyMemoryStream.CopyToAsync(originalResponseBodyReference);
        }
    }
}

ミドルウェアを登録します。

     app.UseMiddleware<SerilogRequestLogger>();

参照:ASP.NETCoreのSerilogログの使用。

AndrewE Aug 25 2020 at 07:57

私の理解ではHttpRequest Body、ログにを追加したいと思います。
このようなものは、コントローラー内でasyncメソッドを使用している限り、開始するのに役立ちます。アクセスできない場合は、スタートアップファイルにHttpRequestDI services.addhttpcontextaccessor()を使用して追加できます。

// Payload.
string payload = string.Empty;

// Create StreamReader And Starting Reading The Request Body.
using (StreamReader streamReader = new StreamReader(this.Request.Body, Encoding.UTF8, true, 1024, true))
{
    // Assign The Stream Content To The Payload Object
    payload = await streamReader.ReadToEndAsync();
}

// Check If The Payload Has Something.
if (!string.IsEmptyOrNull(payload))
{
    // LOG INFO HERE
}
matthewd98 Nov 29 2020 at 15:38

HTTPリクエストとレスポンスの両方をキャプチャするカスタムミドルウェアを作成しました。ASP.NET Core 3.Xと互換性があり、2.Xおよび.NET 5.0でも動作するはずですが、これらのフレームワークバージョンではテストしていません。

これが私のgitリポジトリへのリンクです: https://github.com/matthew-daddario/AspNetCoreRequestResponseLogger

関連するコードは次のとおりです。

    public class RequestResponseLoggerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly bool _isRequestResponseLoggingEnabled;

    public RequestResponseLoggerMiddleware(RequestDelegate next, IConfiguration config)
    {
        _next = next;
        _isRequestResponseLoggingEnabled = config.GetValue<bool>("EnableRequestResponseLogging", false);
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        // Middleware is enabled only when the EnableRequestResponseLogging config value is set.
        if (_isRequestResponseLoggingEnabled)
        {
            Console.WriteLine($"HTTP request information:\n" + $"\tMethod: {httpContext.Request.Method}\n" +
                $"\tPath: {httpContext.Request.Path}\n" + $"\tQueryString: {httpContext.Request.QueryString}\n" +
                $"\tHeaders: {FormatHeaders(httpContext.Request.Headers)}\n" + $"\tSchema: {httpContext.Request.Scheme}\n" +
                $"\tHost: {httpContext.Request.Host}\n" + $"\tBody: {await ReadBodyFromRequest(httpContext.Request)}");

            // Temporarily replace the HttpResponseStream, which is a write-only stream, with a MemoryStream to capture it's value in-flight.
            var originalResponseBody = httpContext.Response.Body;
            using var newResponseBody = new MemoryStream();
            httpContext.Response.Body = newResponseBody;

            // Call the next middleware in the pipeline
            await _next(httpContext);

            newResponseBody.Seek(0, SeekOrigin.Begin);
            var responseBodyText = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();

            Console.WriteLine($"HTTP request information:\n" + $"\tStatusCode: {httpContext.Response.StatusCode}\n" +
                $"\tContentType: {httpContext.Response.ContentType}\n" + $"\tHeaders: {FormatHeaders(httpContext.Response.Headers)}\n" +
                $"\tBody: {responseBodyText}"); newResponseBody.Seek(0, SeekOrigin.Begin); await newResponseBody.CopyToAsync(originalResponseBody); } else { await _next(httpContext); } } private static string FormatHeaders(IHeaderDictionary headers) => string.Join(", ", headers.Select(kvp => $"{{{kvp.Key}: {string.Join(", ", kvp.Value)}}}"));

    private static async Task<string> ReadBodyFromRequest(HttpRequest request)
    {
        // Ensure the request's body can be read multiple times (for the next middlewares in the pipeline).
        request.EnableBuffering();

        using var streamReader = new StreamReader(request.Body, leaveOpen: true);
        var requestBody = await streamReader.ReadToEndAsync();

        // Reset the request's body stream position for next middleware in the pipeline.
        request.Body.Position = 0;
        return requestBody;
    }
}