using PostFundManagement.Domain.Attributes; namespace PostFundManagement.Application.Interceptors; public sealed class LoggingInterceptor(ILogger logger) : IAsyncInterceptor { public void InterceptSynchronous(IInvocation invocation) { invocation.Proceed(); CheckAndLogIfFailed(invocation, invocation.ReturnValue); } public void InterceptAsynchronous(IInvocation invocation) => invocation.ReturnValue = InternalInterceptAsynchronous(invocation); public void InterceptAsynchronous(IInvocation invocation) => invocation.ReturnValue = InternalInterceptAsynchronous(invocation); private static async Task InternalInterceptAsynchronous(IInvocation invocation) { invocation.Proceed(); var task = (Task)invocation.ReturnValue; await task.ConfigureAwait(false); } private async Task InternalInterceptAsynchronous(IInvocation invocation) { invocation.Proceed(); var task = (Task)invocation.ReturnValue; var resultObj = await task.ConfigureAwait(false); var method = invocation.MethodInvocationTarget ?? invocation.Method; var auditAttr = method.GetCustomAttribute() ?? invocation.Method.GetCustomAttribute(); if (auditAttr == null) return resultObj; CheckAndLogIfFailed(invocation, resultObj); return resultObj; } private void CheckAndLogIfFailed(IInvocation invocation, object? resultObj) { if (resultObj is IResultBase result && result.IsFailed) { try { var method = invocation.MethodInvocationTarget ?? invocation.Method; var parameters = BuildPayloadSummary(method.GetParameters(), invocation.Arguments); var errorMessages = new List(); if (result.Reasons != null) { foreach (var reason in result.Reasons) if (reason is IError error) errorMessages.Add(error.Message); } var errorsString = string.Join("; ", errorMessages); logger.LogError("Method {MethodName} failed. Errors: {Errors}. Parameters: {@Parameters}", method.Name, errorsString, parameters); } catch (Exception ex) { logger.LogWarning(ex, "Failed to write error log for method {MethodName}.", invocation.Method.Name); } } } private static string[] BuildPayloadSummary(ParameterInfo[] parameters, object[] args) { var entries = new List(); for (var i = 0; i < parameters.Length; i++) { var param = parameters[i]; var arg = args[i]; if (param.ParameterType == typeof(CancellationToken)) continue; if (arg == null) { entries.Add($"{param.Name}: null"); continue; } if (arg is IFormFile file) { entries.Add($"{param.Name}: [File] Name='{file.FileName}', Size={file.Length} bytes, Type='{file.ContentType}'"); continue; } if (arg is IEnumerable files) { var fileSummaries = new List(); foreach (var f in files) fileSummaries.Add($"'{f.FileName}' ({f.Length} bytes)"); entries.Add($"{param.Name}: [Files] count={fileSummaries.Count} [{string.Join(", ", fileSummaries)}]"); continue; } if (arg is Stream stream) { entries.Add($"{param.Name}: [Stream] Length={(stream.CanSeek ? stream.Length : -1)} bytes"); continue; } try { var json = JsonSerializer.Serialize(arg); entries.Add($"{param.Name}: {json}"); } catch { entries.Add($"{param.Name}: [Unserializable]"); } } return [.. entries]; } }