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.
173 lines
6.0 KiB
C#
173 lines
6.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
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;
|
|
MemoryStream? responseBody = null;
|
|
|
|
try
|
|
{
|
|
// Log request
|
|
await LogRequestAsync(context);
|
|
|
|
// Capture response
|
|
responseBody = new MemoryStream();
|
|
context.Response.Body = responseBody;
|
|
|
|
await _next(context);
|
|
|
|
// Log response
|
|
await LogResponseAsync(context, responseBody);
|
|
|
|
// Copy the response body to the original stream
|
|
if (context.Response.ContentLength > 0)
|
|
{
|
|
responseBody.Seek(0, SeekOrigin.Begin);
|
|
await responseBody.CopyToAsync(originalBodyStream);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _loggingService.LogErrorAsync($"Unhandled exception in logging middleware: {ex.Message}", ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (responseBody != null)
|
|
{
|
|
await responseBody.DisposeAsync();
|
|
}
|
|
context.Response.Body = originalBodyStream;
|
|
}
|
|
}
|
|
|
|
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>();
|
|
}
|
|
}
|
|
} |