Last active
April 6, 2023 01:11
-
-
Save DilanLivera/aead87e60303892a8766e20eb3f8adcf to your computer and use it in GitHub Desktop.
Incoming HTTP request and response logging
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();
}
}
Microsoft Docs: HTTP Logging in ASP.NET Core
HttpLogging.cs
public class HttpLogging
{
private readonly List<string> _requestHeaders = new();
private readonly List<string> _responseHeaders = new();
private List<string> _additionalMediaTypes = new();
public List<HttpLoggingFields> HttpLoggingFields { get; init; } = new();
public List<string> AdditionalMediaTypes
{
get => _additionalMediaTypes.Where(mediaType => !string.IsNullOrWhiteSpace(mediaType)).ToList();
set => _additionalMediaTypes = value;
}
public List<string> RequestHeaders
{
get => _requestHeaders.Where(header => !string.IsNullOrWhiteSpace(header)).ToList();
init => _requestHeaders = value;
}
public List<string> ResponseHeaders
{
get => _responseHeaders.Where(header => !string.IsNullOrWhiteSpace(header)).ToList();
init => _responseHeaders = value;
}
public int RequestBodyLogLimitInBytes { get; init; } = 32000; // Default is 32KB
public int ResponseBodyLogLimitInBytes { get; init; } = 32000; // Default is 32KB
}
Program.cs
// removed for brevity
builder.Services.AddCustomHttpLoggingWithHttpLoggingOptions(builder.Configuration);
WebApplication app = builder.Build();
app.UseHttpLogging();
// removed for brevity
ServiceCollectionExtensions.cs
public static IServiceCollection AddCustomHttpLoggingWithHttpLoggingOptions(
this IServiceCollection services, IConfiguration configuration)
{
HttpLogging loggingConfiguration = services
.Configure<HttpLogging>(configuration.GetSection(nameof(HttpLogging)))
.BuildServiceProvider()
.GetRequiredService<IOptions<HttpLogging>>().Value;
services.AddHttpLogging(options =>
{
options.LoggingFields = loggingConfiguration.HttpLoggingFields.Aggregate(
HttpLoggingFields.None,
(loggingFields, loggingField) => loggingFields | loggingField);
foreach (var mediaType in loggingConfiguration.AdditionalMediaTypes)
{
options.MediaTypeOptions.AddText(mediaType);
}
foreach (var header in loggingConfiguration.RequestHeaders)
{
options.RequestHeaders.Add(header);
}
foreach (var header in loggingConfiguration.ResponseHeaders)
{
options.ResponseHeaders.Add(header);
}
options.RequestBodyLogLimit = loggingConfiguration.RequestBodyLogLimitInBytes;
options.ResponseBodyLogLimit = loggingConfiguration.ResponseBodyLogLimitInBytes;
});
return services;
}
appsettings.json
{
"HttpLogging": {
"HttpLoggingFields": [
"All"
],
"AdditionalMediaTypes": [],
"RequestHeaders": [
"x-user-id",
"x-session-id",
"x-transaction-id"
],
"ResponseHeaders": [],
"RequestBodyLogLimitInBytes": 32000,
"ResponseBodyLogLimitInBytes": 32000
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment