Last active
January 26, 2024 08:20
-
-
Save mckabue/f661b15fe0c59ee29b2d4541d38de50f to your computer and use it in GitHub Desktop.
An ASP.NET CORE Request - Response Logger Middleware
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Http; | |
using Microsoft.AspNetCore.Http.Internal; | |
using Microsoft.IO; | |
// https://exceptionnotfound.net/using-middleware-to-log-requests-and-responses-in-asp-net-core/ | |
// https://gist.github.com/elanderson/c50b2107de8ee2ed856353dfed9168a2 | |
// https://stackoverflow.com/a/52328142/3563013 | |
// https://stackoverflow.com/a/43404745/3563013 | |
// https://gist.github.com/elanderson/c50b2107de8ee2ed856353dfed9168a2#gistcomment-2319007 | |
public class RequestResponseLoggingMiddleware | |
{ | |
private readonly RequestDelegate _next; | |
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; | |
private readonly Action<RequestProfilerModel> _requestResponseHandler; | |
private const int ReadChunkBufferLength = 4096; | |
public RequestResponseLoggingMiddleware(RequestDelegate next, Action<RequestProfilerModel> requestResponseHandler) | |
{ | |
_next = next; | |
_recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); | |
_requestResponseHandler = requestResponseHandler; | |
} | |
public async Task Invoke(HttpContext context) | |
{ | |
var model = new RequestProfilerModel | |
{ | |
RequestTime = new DateTimeOffset(), | |
Context = context, | |
Request = await FormatRequest(context) | |
}; | |
Stream originalBody = context.Response.Body; | |
using (MemoryStream newResponseBody = _recyclableMemoryStreamManager.GetStream()) | |
{ | |
context.Response.Body = newResponseBody; | |
await _next(context); | |
newResponseBody.Seek(0, SeekOrigin.Begin); | |
await newResponseBody.CopyToAsync(originalBody); | |
newResponseBody.Seek(0, SeekOrigin.Begin); | |
model.Response = FormatResponse(context, newResponseBody); | |
model.ResponseTime = new DateTimeOffset(); | |
_requestResponseHandler(model); | |
} | |
} | |
private string FormatResponse(HttpContext context, MemoryStream newResponseBody) | |
{ | |
HttpRequest request = context.Request; | |
HttpResponse response = context.Response; | |
return $"Http Response Information: {Environment.NewLine}" + | |
$"Schema:{request.Scheme} {Environment.NewLine}" + | |
$"Host: {request.Host} {Environment.NewLine}" + | |
$"Path: {request.Path} {Environment.NewLine}" + | |
$"QueryString: {request.QueryString} {Environment.NewLine}" + | |
$"StatusCode: {response.StatusCode} {Environment.NewLine}" + | |
$"Response Body: {ReadStreamInChunks(newResponseBody)}"; | |
} | |
private async Task<string> FormatRequest(HttpContext context) | |
{ | |
HttpRequest request = context.Request; | |
return $"Http Request Information: {Environment.NewLine}" + | |
$"Schema:{request.Scheme} {Environment.NewLine}" + | |
$"Host: {request.Host} {Environment.NewLine}" + | |
$"Path: {request.Path} {Environment.NewLine}" + | |
$"QueryString: {request.QueryString} {Environment.NewLine}" + | |
$"Request Body: {await GetRequestBody(request)}"; | |
} | |
public async Task<string> GetRequestBody(HttpRequest request) | |
{ | |
request.EnableBuffering(); | |
request.EnableRewind(); | |
using (var requestStream = _recyclableMemoryStreamManager.GetStream()) | |
{ | |
await request.Body.CopyToAsync(requestStream); | |
request.Body.Seek(0, SeekOrigin.Begin); | |
return ReadStreamInChunks(requestStream); | |
} | |
} | |
private static string ReadStreamInChunks(Stream stream) | |
{ | |
stream.Seek(0, SeekOrigin.Begin); | |
string result; | |
using (var textWriter = new StringWriter()) | |
using (var reader = new StreamReader(stream)) | |
{ | |
var readChunk = new char[ReadChunkBufferLength]; | |
int readChunkLength; | |
//do while: is useful for the last iteration in case readChunkLength < chunkLength | |
do | |
{ | |
readChunkLength = reader.ReadBlock(readChunk, 0, ReadChunkBufferLength); | |
textWriter.Write(readChunk, 0, readChunkLength); | |
} while (readChunkLength > 0); | |
result = textWriter.ToString(); | |
} | |
return result; | |
} | |
public class RequestProfilerModel | |
{ | |
public DateTimeOffset RequestTime { get; set; } | |
public HttpContext Context { get; set; } | |
public string Request { get; set; } | |
public string Response { get; set; } | |
public DateTimeOffset ResponseTime { get; set; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// USAGE in `Startup.cs` | |
Action<RequestProfilerModel> requestResponseHandler = requestProfilerModel => | |
{ | |
Debug.Print(requestProfilerModel.Request); | |
Debug.Print(Environment.NewLine); | |
Debug.Print(requestProfilerModel.Response); | |
}; | |
app.UseMiddleware<RequestResponseLoggingMiddleware>(requestResponseHandler); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment