How to use Serilog to log all log messages in one request to single entry

Viewed 379

Want to check if it is possible to log all messages in one request as single entry.

I was looking at this https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/ but not I am after.

For instance, I have a .Net Core web app with

public MyController(IService myService, ILogger<MyController> logger)
        {
            _myService = myService ?? throw new ArgumentNullException(nameof(myService));
            _logger = logger;
        }

[HttpGet]
        public async Task<IActionResult> Get([FromQuery( Name = "Id")] List<string> ids)
        {          
            _myService.DoSomething();
            _logger.Log(LogLevel.Information, $"Some controller logs here");
            return new OkObjectResult();
        }

And for IService:

public MyService(ILogger<MyService> logger)
        {
            _logger = logger;
        }

public DoSomething()
{
_logger.Log(LogLevel.Information, $"Some service logs here");
}

Is Serilog able to log "Some controller logs here" & "Some service logs here" under one entry? instead of two entries?

Thanks

1 Answers

You can add middleware on your app. It will capture incoming and outgoing top requests.

public class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        //First, get the incoming request
        var request = await FormatRequest(context.Request);

        //Copy a pointer to the original response body stream
        var originalBodyStream = context.Response.Body;

        //Create a new memory stream...
        using (var responseBody = new MemoryStream())
        {
            //...and use that for the temporary response body
            context.Response.Body = responseBody;

            //Continue down the Middleware pipeline, eventually returning to this class
            await _next(context);

            //Format the response from the server
            var response = await FormatResponse(context.Response);

            //TODO: Save log to chosen datastore

            //Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
            await responseBody.CopyToAsync(originalBodyStream);
        }
    }

    private async Task<string> FormatRequest(HttpRequest request)
    {
        var body = request.Body;

        //This line allows us to set the reader for the request back at the beginning of its stream.
        request.EnableBuffering();

        //We now need to read the request stream.  First, we create a new byte[] with the same length as the request stream...
        var buffer = new byte[Convert.ToInt32(request.ContentLength)];

        //...Then we copy the entire request stream into the new buffer.
        await request.Body.ReadAsync(buffer, 0, buffer.Length);

        //We convert the byte[] into a string using UTF8 encoding...
        var bodyAsText = Encoding.UTF8.GetString(buffer);

        //..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
        request.Body = body;

        return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
    }

    private async Task<string> FormatResponse(HttpResponse response)
    {
        //We need to read the response stream from the beginning...
        response.Body.Seek(0, SeekOrigin.Begin);

        //...and copy it into a string
        string text = await new StreamReader(response.Body).ReadToEndAsync();

        //We need to reset the reader for the response so that the client can read it.
        response.Body.Seek(0, SeekOrigin.Begin);

        //Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
        return $"{response.StatusCode}: {text}";
    }
}

And do not forget inject on your startup file the middleware.

app.UseMiddleware<RequestResponseLoggingMiddleware>();

You may configure your serilog file like this:

"Serilog": {
"MinimumLevel": "Error",
"WriteTo": [
  {
    "Name": "RollingFile",
    "Args": {
      "pathFormat": "\\log-{Date}.log",
      "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
    }
  },
]

},

It will write to the file at the selected destination.

And please note, here are minimum level of serilog. Different Minimum Level Logs Serilog

Maybe it will be help you.

Related