using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
namespace Shared.Application.Behaviors;
public abstract class JsonLogger
{
private static readonly string[] SensitiveFieldNames = ["password", "token"];
protected static readonly JsonSerializerOptions SerializerOptions = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { RedactSensitiveProperties },
},
};
private static void RedactSensitiveProperties(JsonTypeInfo typeInfo)
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
foreach (JsonPropertyInfo property in typeInfo.Properties)
{
if (property.PropertyType != typeof(string))
continue;
if (
SensitiveFieldNames.Any(name =>
property.Name.Contains(name, StringComparison.OrdinalIgnoreCase)
)
)
property.Get = static _ => "***REDACTED***";
}
}
}
///
/// MediatR pipeline behavior that logs unhandled exceptions together with the
/// request type and
/// payload, then rethrows them unchanged for GlobalExceptionFilter to
/// map to a status code.
///
public partial class UnhandledExceptionLoggingBehavior
: JsonLogger,
IPipelineBehavior
where TRequest : IRequest
{
private readonly ILogger> _logger;
public UnhandledExceptionLoggingBehavior(
ILogger> logger
)
{
_logger = logger;
}
///
public async Task Handle(
TRequest request,
RequestHandlerDelegate next,
CancellationToken cancellationToken
)
{
try
{
return await next();
}
catch (ValidationException)
{
// Expected: ValidationBehavior already reports these as request errors
throw;
}
catch (Exception ex)
{
if (_logger.IsEnabled(LogLevel.Error))
LogUnhandledException(ex, typeof(TRequest).Name, Serialize(request));
throw;
}
}
private static string Serialize(TRequest request)
{
try
{
return JsonSerializer.Serialize(request, SerializerOptions);
}
catch (Exception)
{
return "";
}
}
[LoggerMessage(
Level = LogLevel.Error,
Message = "Unhandled exception while processing {RequestName}: {RequestPayload}"
)]
private partial void LogUnhandledException(
Exception ex,
string requestName,
string requestPayload
);
}