diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
index d56c29709a..b651274ed4 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
@@ -39,6 +39,10 @@ namespace Volo.Abp.AspNetCore.Auditing
try
{
await next(context).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ await scope.SaveAsync(ex).ConfigureAwait(false);
}
finally
{
@@ -49,6 +53,13 @@ namespace Volo.Abp.AspNetCore.Auditing
private bool ShouldWriteAuditLog(HttpContext httpContext)
{
+ // IF selected, save audit logs on exception on GET requests even if audit log is disabled for GET requests.
+ if (Options.AlwaysLogOnException &&
+ string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
if (!Options.IsEnabled)
{
return false;
@@ -59,7 +70,7 @@ namespace Volo.Abp.AspNetCore.Auditing
return false;
}
- if (!Options.IsEnabledForGetRequests &&
+ if (!Options.IsEnabledForGetRequests &&
string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
{
return false;
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
index da406fa490..5dcae1e097 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
@@ -29,7 +29,13 @@ namespace Volo.Abp.Auditing
///
/// Default: true.
///
- public bool IsEnabledForAnonymousUsers { get; set; }
+ public bool IsEnabledForAnonymousUsers { get; set; } = false;
+
+ ///
+ /// Audit log e xceptions.
+ /// Default: false.
+ ///
+ public bool AlwaysLogOnException { get; set; }
public List Contributors { get; }
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
index 25738843c0..1d3b912311 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
@@ -23,8 +23,8 @@ namespace Volo.Abp.Auditing
private readonly IAuditingStore _auditingStore;
public AuditingManager(
- IAmbientScopeProvider ambientScopeProvider,
- IAuditingHelper auditingHelper,
+ IAmbientScopeProvider ambientScopeProvider,
+ IAuditingHelper auditingHelper,
IAuditingStore auditingStore,
IServiceProvider serviceProvider,
IOptions options)
@@ -84,7 +84,7 @@ namespace Volo.Abp.Auditing
{
var changeGroups = auditLog.EntityChanges
.Where(e => e.ChangeType == EntityChangeType.Updated)
- .GroupBy(e => new {e.EntityTypeFullName, e.EntityId})
+ .GroupBy(e => new { e.EntityTypeFullName, e.EntityId })
.ToList();
foreach (var changeGroup in changeGroups)
@@ -141,7 +141,7 @@ namespace Volo.Abp.Auditing
public DisposableSaveHandle(
AuditingManager auditingManager,
IDisposable scope,
- AuditLogInfo auditLog,
+ AuditLogInfo auditLog,
Stopwatch stopWatch)
{
_auditingManager = auditingManager;
@@ -150,15 +150,22 @@ namespace Volo.Abp.Auditing
StopWatch = stopWatch;
}
- public async Task SaveAsync()
+ public async Task SaveAsync(Exception exception = null)
{
- await _auditingManager.SaveAsync(this).ConfigureAwait(false);
+ if (exception != null)
+ {
+ this.AuditLog.Exceptions.Add(exception);
+ }
+ else
+ {
+ await _auditingManager.SaveAsync(this).ConfigureAwait(false);
+ }
}
-
+
public void Dispose()
{
_scope.Dispose();
- }
+ }
}
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs
index 4709b745d2..6e48f3525c 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs
@@ -5,6 +5,6 @@ namespace Volo.Abp.Auditing
{
public interface IAuditLogSaveHandle : IDisposable
{
- Task SaveAsync();
+ Task SaveAsync(Exception exception = null);
}
}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs
new file mode 100644
index 0000000000..967f3269d2
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs
@@ -0,0 +1,29 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Auditing;
+
+namespace Volo.Abp.AspNetCore.Mvc.Auditing
+{
+ [Route("api/audit-test")]
+ public class AuditTestController : AbpController
+ {
+ private readonly AbpAuditingOptions _options;
+
+ public AuditTestController(IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ [Route("audit-success")]
+ public IActionResult AuditSuccessForGetRequests()
+ {
+ return Ok();
+ }
+
+ [Route("audit-fail")]
+ public IActionResult AuditFailForGetRequests()
+ {
+ throw new UserFriendlyException("Exception occurred!");
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
new file mode 100644
index 0000000000..97b211ba5a
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using System.Threading.Tasks;
+using Volo.Abp.Auditing;
+using Xunit;
+
+namespace Volo.Abp.AspNetCore.Mvc.Auditing
+{
+ public class AuditTestController_Tests : AspNetCoreMvcTestBase
+ {
+ private readonly AbpAuditingOptions _options;
+ private IAuditingStore _auditingStore;
+
+ public AuditTestController_Tests()
+ {
+ _options = ServiceProvider.GetRequiredService>().Value;
+ _auditingStore = ServiceProvider.GetRequiredService();
+ }
+
+ protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
+ {
+ _auditingStore = Substitute.For();
+ services.Replace(ServiceDescriptor.Singleton(_auditingStore));
+ base.ConfigureServices(context, services);
+ }
+
+ [Fact]
+ public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests()
+ {
+ _options.IsEnabledForGetRequests = true;
+ await GetResponseAsync("api/audit-test/audit-success");
+ //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope
+ }
+
+ [Fact]
+ public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always()
+ {
+ _options.IsEnabled = false;
+ _options.AlwaysLogOnException = true;
+ await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.BadRequest);
+ //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope
+ }
+ }
+}