mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
108 lines
2.9 KiB
C#
108 lines
2.9 KiB
C#
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***";
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// MediatR pipeline behavior that logs unhandled exceptions together with the
|
|
/// request type and
|
|
/// payload, then rethrows them unchanged for <c>GlobalExceptionFilter</c> to
|
|
/// map to a status code.
|
|
/// </summary>
|
|
public partial class UnhandledExceptionLoggingBehavior<TRequest, TResponse>
|
|
: JsonLogger,
|
|
IPipelineBehavior<TRequest, TResponse>
|
|
where TRequest : IRequest<TResponse>
|
|
{
|
|
private readonly ILogger<UnhandledExceptionLoggingBehavior<TRequest, TResponse>> _logger;
|
|
|
|
public UnhandledExceptionLoggingBehavior(
|
|
ILogger<UnhandledExceptionLoggingBehavior<TRequest, TResponse>> logger
|
|
)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<TResponse> Handle(
|
|
TRequest request,
|
|
RequestHandlerDelegate<TResponse> 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 "<unable to serialize request payload>";
|
|
}
|
|
}
|
|
|
|
[LoggerMessage(
|
|
Level = LogLevel.Error,
|
|
Message = "Unhandled exception while processing {RequestName}: {RequestPayload}"
|
|
)]
|
|
private partial void LogUnhandledException(
|
|
Exception ex,
|
|
string requestName,
|
|
string requestPayload
|
|
);
|
|
}
|