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(cancellationToken); } 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 ); }