Request and response logging middleware for HTTP logging
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.IO; | |
using System.Text.Json; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Http; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.IO; | |
namespace WeatherApi.Middleware; | |
public class RequestResponseLoggingMiddleware | |
{ | |
private readonly ILogger<RequestResponseLoggingMiddleware> _logger; | |
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; | |
private readonly RequestDelegate _next; | |
public RequestResponseLoggingMiddleware( | |
RequestDelegate next, | |
ILogger<RequestResponseLoggingMiddleware> logger) | |
{ | |
_next = next; | |
_logger = logger; | |
_recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); | |
} | |
public async Task Invoke(HttpContext context) | |
{ | |
if (!_logger.IsEnabled(LogLevel.Debug)) | |
{ | |
await _next.Invoke(context); | |
return; | |
} | |
await LogRequest(context.Request); | |
var originalResponseBodyStream = context.Response.Body; | |
using var responseBody = _recyclableMemoryStreamManager.GetStream(); | |
context.Response.Body = responseBody; | |
await _next(context); | |
await LogResponse(context.Response); | |
await responseBody.CopyToAsync(originalResponseBodyStream); | |
} | |
private async Task LogRequest(HttpRequest httpRequest) | |
{ | |
var requestDictionary = await GetRequestContent(httpRequest); | |
_logger.LogDebug("Request content: {RequestContent}", requestDictionary); | |
} | |
private async Task LogResponse(HttpResponse httpResponse) | |
{ | |
var responseDictionary = await GetResponseContent(httpResponse); | |
_logger.LogDebug("Response content: {ResponseContent}", responseDictionary); | |
} | |
public async Task<Dictionary<string, string>> GetRequestContent(HttpRequest request) | |
{ | |
if (request is null) | |
{ | |
return new Dictionary<string, string>(); | |
} | |
var requestDictionary = new Dictionary<string, string> | |
{ | |
{ nameof(request.Method), request.Method }, | |
{ nameof(request.Path), request.Path }, | |
{ nameof(request.Scheme), request.Scheme } | |
}; | |
if (request.Headers != null && request.Headers.Any()) | |
{ | |
var headers = JsonSerializer.Serialize(request.Headers); | |
requestDictionary.Add(nameof(request.Headers), headers); | |
} | |
if (request.QueryString.HasValue && request.QueryString.Value != null) | |
{ | |
requestDictionary.Add(nameof(request.QueryString), request.QueryString.Value); | |
} | |
if (request.Body is null || | |
request.ContentType is null || | |
!request.ContentType.Contains("application/json")) | |
{ | |
return requestDictionary; | |
} | |
request.EnableBuffering(); | |
await using var requestBodyStream = _recyclableMemoryStreamManager.GetStream(); | |
await request.Body.CopyToAsync(requestBodyStream); | |
request.Body.Seek(0, SeekOrigin.Begin); | |
var requestBodyContent = GetContent(requestBodyStream); | |
requestDictionary.Add(nameof(request.Body), requestBodyContent); | |
return requestDictionary; | |
} | |
public async Task<Dictionary<string, string>> GetResponseContent(HttpResponse response) | |
{ | |
if (response is null) | |
{ | |
return new Dictionary<string, string>(); | |
} | |
var responseDictionary = new Dictionary<string, string> | |
{ | |
{ nameof(response.StatusCode), response.StatusCode.ToString() } | |
}; | |
if (response.Headers != null && response.Headers.Any()) | |
{ | |
var headers = JsonSerializer.Serialize(response.Headers); | |
responseDictionary.Add(nameof(response.Headers), headers); | |
} | |
response.Body.Seek(0, SeekOrigin.Begin); | |
if (response.ContentType is null || !response.ContentType.Contains("application/json")) | |
{ | |
return responseDictionary; | |
} | |
await using var responseBodyStream = _recyclableMemoryStreamManager.GetStream(); | |
await response.Body.CopyToAsync(responseBodyStream); | |
var responseBodyContent = GetContent(responseBodyStream); | |
response.Body.Seek(0, SeekOrigin.Begin); | |
if (!string.IsNullOrEmpty(responseBodyContent)) | |
{ | |
responseDictionary.Add(nameof(response.Body), responseBodyContent); | |
} | |
return responseDictionary; | |
} | |
private static string GetContent(Stream stream) | |
{ | |
const int readChunkBufferLength = 4096; | |
stream.Seek(0, SeekOrigin.Begin); | |
using var textWriter = new StringWriter(); | |
using var reader = new StreamReader(stream); | |
var readChunk = new char[readChunkBufferLength]; | |
int readChunkLength; | |
do | |
{ | |
readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength); | |
textWriter.Write(readChunk, 0, readChunkLength); | |
} | |
while (readChunkLength > 0); | |
return textWriter.ToString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment