You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
5.7 KiB
C#

using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Haoliang.Core.Services;
namespace Haoliang.Api.Middleware
{
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<LoggingMiddleware> _logger;
private readonly ILoggingService _loggingService;
public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger, ILoggingService loggingService)
{
_next = next;
_logger = logger;
_loggingService = loggingService;
}
public async Task Invoke(HttpContext context)
{
var originalBodyStream = context.Response.Body;
try
{
// Log request
await LogRequestAsync(context);
// Capture response
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
// Log response
await LogResponseAsync(context, responseBody);
// Copy the response body to the original stream
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBodyStream);
}
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Unhandled exception in logging middleware: {ex.Message}", ex);
throw;
}
}
private async Task LogRequestAsync(HttpContext context)
{
try
{
var request = context.Request;
// Don't log request body for sensitive endpoints like login
var shouldLogBody = !request.Path.ToString().Contains("/login") &&
!request.Path.ToString().Contains("/auth");
var requestBody = shouldLogBody ? await GetRequestBodyAsync(request) : "[REDACTED]";
var logData = new
{
Method = request.Method,
Path = request.Path,
QueryString = request.QueryString.ToString(),
Headers = GetSanitizedHeaders(request.Headers),
Body = requestBody,
UserAgent = request.Headers["User-Agent"].ToString(),
RemoteIpAddress = context.Connection.RemoteIpAddress?.ToString(),
Timestamp = DateTime.Now
};
await _loggingService.LogInfoAsync($"Incoming request: {JsonSerializer.Serialize(logData)}");
_logger.LogInformation("Incoming request: {Request}", JsonSerializer.Serialize(logData));
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Error logging request: {ex.Message}", ex);
}
}
private async Task LogResponseAsync(HttpContext context, MemoryStream responseBody)
{
try
{
var response = context.Response;
var responseBodyContent = await GetResponseBodyAsync(responseBody);
var logData = new
{
StatusCode = response.StatusCode,
Headers = GetSanitizedHeaders(response.Headers),
Body = responseBodyContent,
Timestamp = DateTime.Now
};
await _loggingService.LogInfoAsync($"Outgoing response: {JsonSerializer.Serialize(logData)}");
_logger.LogInformation("Outgoing response: {Response}", JsonSerializer.Serialize(logData));
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Error logging response: {ex.Message}", ex);
}
}
private async Task<string> GetRequestBodyAsync(HttpRequest request)
{
request.EnableBuffering();
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var body = await reader.ReadToEndAsync();
request.Body.Seek(0, SeekOrigin.Begin);
return body;
}
}
private async Task<string> GetResponseBodyAsync(MemoryStream responseBody)
{
responseBody.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(responseBody, Encoding.UTF8))
{
var body = await reader.ReadToEndAsync();
return body;
}
}
private object GetSanitizedHeaders(IHeaderDictionary headers)
{
var sensitiveHeaders = new[] { "authorization", "cookie", "set-cookie", "password", "token" };
var sanitizedHeaders = new Dictionary<string, string>();
foreach (var header in headers)
{
if (sensitiveHeaders.Contains(header.Key.ToLower()))
{
sanitizedHeaders[header.Key] = "[REDACTED]";
}
else
{
sanitizedHeaders[header.Key] = header.Value.ToString();
}
}
return sanitizedHeaders;
}
}
public static class LoggingMiddlewareExtensions
{
public static IApplicationBuilder UseLoggingMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoggingMiddleware>();
}
}
}