Browse Source

Merge pull request #1428 from colinin/auditlogging-queue

feat: use auditlogging queue
dev
yx lin 3 days ago
committed by GitHub
parent
commit
f22d5d1652
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      Directory.Packages.props
  2. 7
      aspnet-core/LINGYUN.MicroService.Aspire.sln
  3. 1
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN.Abp.AuditLogging.Elasticsearch.csproj
  4. 12
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchOptions.cs
  5. 89
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs
  6. 114
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogWriter.cs
  7. 56
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchSecurityLogManager.cs
  8. 122
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchSecurityLogWriter.cs
  9. 53
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/AuditLogManager.cs
  10. 62
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogWriter.cs
  11. 57
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreSecurityLogWriter.cs
  12. 34
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/SecurityLogManager.cs
  13. 7
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.IP.Location/LINGYUN/Abp/AuditLogging/IP/Location/IPLocationAuditingStore.cs
  14. 8
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.IP.Location/LINGYUN/Abp/AuditLogging/IP/Location/IPLocationSecurityLogStore.cs
  15. 1
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN.Abp.AuditLogging.csproj
  16. 67
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingModule.cs
  17. 54
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingOptions.cs
  18. 0
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs
  19. 50
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditLogQueue.cs
  20. 196
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditLoggingQueue.cs
  21. 33
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditingStore.cs
  22. 0
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogInfoToAuditLogConverter.cs
  23. 5
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogManager.cs
  24. 9
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogQueue.cs
  25. 12
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogWriter.cs
  26. 5
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/ISecurityLogManager.cs
  27. 9
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/ISecurityLogQueue.cs
  28. 12
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/ISecurityLogWriter.cs
  29. 33
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/LoggerAuditLogWriter.cs
  30. 33
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/LoggerSecurityLogWriter.cs
  31. 50
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/SecurityLogQueue.cs
  32. 36
      aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/SecurityLogStore.cs
  33. 23
      aspnet-core/framework/auditing/README.md
  34. 6
      aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestBase.cs
  35. 6
      aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestModule.cs
  36. 1
      aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogManagerTests.cs

1
Directory.Packages.props

@ -365,6 +365,7 @@
<PackageVersion Include="Spire.XLS" Version="15.12.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.4" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.0" />
<PackageVersion Include="Tencent.QCloud.Cos.Sdk" Version="5.4.37" />
<PackageVersion Include="TencentCloudSDK" Version="3.0.1273" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />

7
aspnet-core/LINGYUN.MicroService.Aspire.sln

@ -925,6 +925,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.MicroService.Ap
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.BackgroundTasks.Quartz", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Quartz\LINGYUN.Abp.BackgroundTasks.Quartz.csproj", "{ABCAB030-29ED-8219-F1DB-39D16098805F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Elasticsearch.Jobs", "modules\task-management\LINGYUN.Abp.Elasticsearch.Jobs\LINGYUN.Abp.Elasticsearch.Jobs.csproj", "{C4F5372D-3127-A2CA-3B22-B343D5E54EB5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -2439,6 +2441,10 @@ Global
{ABCAB030-29ED-8219-F1DB-39D16098805F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABCAB030-29ED-8219-F1DB-39D16098805F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABCAB030-29ED-8219-F1DB-39D16098805F}.Release|Any CPU.Build.0 = Release|Any CPU
{C4F5372D-3127-A2CA-3B22-B343D5E54EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4F5372D-3127-A2CA-3B22-B343D5E54EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4F5372D-3127-A2CA-3B22-B343D5E54EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4F5372D-3127-A2CA-3B22-B343D5E54EB5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2889,6 +2895,7 @@ Global
{B856BFF0-8D0E-4C4A-97C7-5406E1B7613C} = {FAB71536-FCDB-4135-B61A-732B72FDA6A6}
{410E0FFF-705D-4471-9E52-FF495096A945} = {B856BFF0-8D0E-4C4A-97C7-5406E1B7613C}
{ABCAB030-29ED-8219-F1DB-39D16098805F} = {77ED7922-BF30-4436-8A85-78F812583913}
{C4F5372D-3127-A2CA-3B22-B343D5E54EB5} = {77ED7922-BF30-4436-8A85-78F812583913}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}

1
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN.Abp.AuditLogging.Elasticsearch.csproj

@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.Json" />
<PackageReference Include="System.Threading.Channels" />
</ItemGroup>
<ItemGroup>

12
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchOptions.cs

@ -14,18 +14,10 @@ public class AbpAuditLoggingElasticsearchOptions
/// </remarks>
public bool ThrowIfIndexInitFailed { get; set; }
/// <summary>
/// 是否启用审计日志记录
/// </summary>
public bool IsAuditLogEnabled { get; set; }
/// <summary>
/// 审计日志索引设置
/// </summary>
public IndexSettings AuditLogSettings { get; set; }
/// <summary>
/// 是否启用安全日志记录
/// </summary>
public bool IsSecurityLogEnabled { get; set; }
/// <summary>
/// 安全日志索引设置
/// </summary>
public IndexSettings SecurityLogSettings { get; set; }
@ -33,8 +25,8 @@ public class AbpAuditLoggingElasticsearchOptions
public AbpAuditLoggingElasticsearchOptions()
{
IndexPrefix = DefaultIndexPrefix;
IsAuditLogEnabled = true;
ThrowIfIndexInitFailed = true;
AuditLogSettings = new IndexSettings()
{
NumberOfReplicas = 1,
@ -55,7 +47,7 @@ public class AbpAuditLoggingElasticsearchOptions
},
}
};
IsSecurityLogEnabled = true;
SecurityLogSettings = new IndexSettings();
}
}

89
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs

@ -8,11 +8,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
@ -21,12 +18,9 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
[Dependency(ReplaceServices = true)]
public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependency
{
private readonly AbpAuditingOptions _auditingOptions;
private readonly AbpElasticsearchOptions _elasticsearchOptions;
private readonly AbpAuditLoggingElasticsearchOptions _loggingEsOptions;
private readonly IIndexNameNormalizer _indexNameNormalizer;
private readonly IElasticsearchClientFactory _clientFactory;
private readonly IAuditLogInfoToAuditLogConverter _converter;
private readonly IClock _clock;
public ILogger<ElasticsearchAuditLogManager> Logger { protected get; set; }
@ -35,18 +29,12 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
IClock clock,
IIndexNameNormalizer indexNameNormalizer,
IOptions<AbpElasticsearchOptions> elasticsearchOptions,
IElasticsearchClientFactory clientFactory,
IOptions<AbpAuditingOptions> auditingOptions,
IAuditLogInfoToAuditLogConverter converter,
IOptionsMonitor<AbpAuditLoggingElasticsearchOptions> loggingEsOptions)
IElasticsearchClientFactory clientFactory)
{
_clock = clock;
_converter = converter;
_clientFactory = clientFactory;
_auditingOptions = auditingOptions.Value;
_elasticsearchOptions = elasticsearchOptions.Value;
_indexNameNormalizer = indexNameNormalizer;
_loggingEsOptions = loggingEsOptions.CurrentValue;
Logger = NullLogger<ElasticsearchAuditLogManager>.Instance;
}
@ -221,81 +209,6 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
cancellationToken);
}
public async virtual Task<string> SaveAsync(
AuditLogInfo auditInfo,
CancellationToken cancellationToken = default)
{
if (!_loggingEsOptions.IsAuditLogEnabled)
{
Logger.LogInformation(auditInfo.ToString());
return "";
}
if (!_auditingOptions.HideErrors)
{
return await SaveLogAsync(auditInfo, cancellationToken);
}
try
{
return await SaveLogAsync(auditInfo, cancellationToken);
}
catch (Exception ex)
{
Logger.LogWarning("Could not save the audit log object: " + Environment.NewLine + auditInfo.ToString());
Logger.LogException(ex, LogLevel.Error);
}
return "";
}
protected async virtual Task<string> SaveLogAsync(
AuditLogInfo auditLogInfo,
CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var auditLog = await _converter.ConvertAsync(auditLogInfo);
//var response = await client.IndexAsync(
// auditLog,
// (x) => x.Index(CreateIndex())
// .Id(auditLog.Id),
// cancellationToken);
// 使用 Bulk 命令传输可能存在参数庞大的日志结构
var response = await client.BulkAsync(
dsl => dsl.Index(CreateIndex())
.Create(auditLog, ct => ct.Id(auditLog.Id)));
if (!response.IsValidResponse)
{
if (response.TryGetOriginalException(out var ex))
{
throw ex;
}
else if (response.ElasticsearchServerError != null)
{
throw new AbpException(response.ElasticsearchServerError.ToString());
}
else if (response.ItemsWithErrors.Any())
{
var reasonBuilder = new StringBuilder();
foreach (var itemError in response.ItemsWithErrors)
{
if (itemError.Error?.Reason.IsNullOrWhiteSpace() == false)
{
reasonBuilder.AppendLine(itemError.Error.Reason);
}
}
if (reasonBuilder.Length > 0)
{
throw new AbpException(reasonBuilder.ToString());
}
}
}
return response.Items?.FirstOrDefault()?.Id;
}
protected virtual List<Query> BuildQueryDescriptor(
DateTime? startTime = null,
DateTime? endTime = null,

114
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogWriter.cs

@ -0,0 +1,114 @@
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Core.Bulk;
using LINGYUN.Abp.Elasticsearch;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
[Dependency(ReplaceServices = true)]
public class ElasticsearchAuditLogWriter : IAuditLogWriter, ITransientDependency
{
private readonly IAuditLogInfoToAuditLogConverter _auditLogConverter;
private readonly IElasticsearchClientFactory _clientFactory;
private readonly IIndexNameNormalizer _indexNameNormalizer;
private readonly ILogger<ElasticsearchAuditLogWriter> _logger;
public ElasticsearchAuditLogWriter(
IAuditLogInfoToAuditLogConverter auditLogConverter,
IElasticsearchClientFactory clientFactory,
IIndexNameNormalizer indexNameNormalizer,
ILogger<ElasticsearchAuditLogWriter> logger)
{
_auditLogConverter = auditLogConverter;
_clientFactory = clientFactory;
_indexNameNormalizer = indexNameNormalizer;
_logger = logger;
}
public async virtual Task WriteAsync(AuditLogInfo auditLogInfo, CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var auditLog = await _auditLogConverter.ConvertAsync(auditLogInfo);
var response = await client.IndexAsync(
auditLog,
dsl => dsl.Index(CreateIndex())
.Id(auditLog.Id),
cancellationToken);
if (!response.IsValidResponse)
{
_logger.LogWarning("Could not save the audit log object: " + Environment.NewLine + auditLog.ToString());
if (response.TryGetOriginalException(out var ex))
{
_logger.LogWarning(ex, ex.Message);
}
else if (response.ElasticsearchServerError != null)
{
_logger.LogWarning(response.ElasticsearchServerError.ToString());
}
}
}
public async virtual Task BulkWriteAsync(IEnumerable<AuditLogInfo> auditLogInfos, CancellationToken cancellationToken = default)
{
if (!auditLogInfos.Any())
{
return;
}
var client = _clientFactory.Create();
var indexName = CreateIndex();
var bulkOperations = new List<BulkOperation>();
foreach (var auditLogInfo in auditLogInfos)
{
var auditLog = await _auditLogConverter.ConvertAsync(auditLogInfo);
bulkOperations.Add(new BulkCreateOperation<AuditLog>(auditLog)
{
Id = auditLog.Id.ToString()
});
}
var bulkRequest = new BulkRequest
{
Operations = [.. bulkOperations],
Index = indexName
};
var response = await client.BulkAsync(bulkRequest, cancellationToken);
if (!response.IsValidResponse)
{
await HandleBulkErrorsAsync(response, auditLogInfos);
}
}
private Task HandleBulkErrorsAsync(BulkResponse response, IEnumerable<AuditLogInfo> auditLogInfos)
{
foreach (var itemWithError in response.ItemsWithErrors)
{
_logger.LogError($"Failed to write audit log: {itemWithError.Error?.Reason}");
}
foreach (var auditLog in auditLogInfos)
{
_logger.LogInformation(auditLog.ToString());
}
return Task.CompletedTask;
}
protected virtual string CreateIndex()
{
return _indexNameNormalizer.NormalizeIndex("audit-log");
}
}

56
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchSecurityLogManager.cs

@ -10,8 +10,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.SecurityLog;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
@ -19,11 +17,8 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
[Dependency(ReplaceServices = true)]
public class ElasticsearchSecurityLogManager : ISecurityLogManager, ITransientDependency
{
private readonly AbpSecurityLogOptions _securityLogOptions;
private readonly AbpElasticsearchOptions _elasticsearchOptions;
private readonly AbpAuditLoggingElasticsearchOptions _loggingEsOptions;
private readonly IIndexNameNormalizer _indexNameNormalizer;
private readonly IGuidGenerator _guidGenerator;
private readonly IElasticsearchClientFactory _clientFactory;
private readonly IClock _clock;
@ -31,67 +26,18 @@ public class ElasticsearchSecurityLogManager : ISecurityLogManager, ITransientDe
public ElasticsearchSecurityLogManager(
IClock clock,
IGuidGenerator guidGenerator,
IIndexNameNormalizer indexNameNormalizer,
IOptions<AbpSecurityLogOptions> securityLogOptions,
IOptions<AbpElasticsearchOptions> elasticsearchOptions,
IElasticsearchClientFactory clientFactory,
IOptionsMonitor<AbpAuditLoggingElasticsearchOptions> loggingEsOptions)
IElasticsearchClientFactory clientFactory)
{
_clock = clock;
_guidGenerator = guidGenerator;
_clientFactory = clientFactory;
_indexNameNormalizer = indexNameNormalizer;
_securityLogOptions = securityLogOptions.Value;
_elasticsearchOptions = elasticsearchOptions.Value;
_loggingEsOptions = loggingEsOptions.CurrentValue;
Logger = NullLogger<ElasticsearchSecurityLogManager>.Instance;
}
public async virtual Task SaveAsync(
SecurityLogInfo securityLogInfo,
CancellationToken cancellationToken = default)
{
// TODO: 框架不把这玩意儿放在 ISecurityLogManager?
if (!_securityLogOptions.IsEnabled)
{
return;
}
if (!_loggingEsOptions.IsSecurityLogEnabled)
{
Logger.LogInformation(securityLogInfo.ToString());
return;
}
var client = _clientFactory.Create();
var securityLog = new SecurityLog(
_guidGenerator.Create(),
securityLogInfo);
var response = await client.IndexAsync(
securityLog,
(x) => x.Index(CreateIndex())
.Id(securityLog.Id),
cancellationToken);
if (!response.IsValidResponse)
{
Logger.LogWarning("Could not save the security log object: " + Environment.NewLine + securityLogInfo.ToString());
if (response.TryGetOriginalException(out var ex))
{
Logger.LogWarning(ex, ex.Message);
}
else if (response.ElasticsearchServerError != null)
{
Logger.LogWarning(response.ElasticsearchServerError.ToString());
}
}
}
public async virtual Task<SecurityLog> GetAsync(
Guid id,
bool includeDetails = false,

122
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchSecurityLogWriter.cs

@ -0,0 +1,122 @@
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Core.Bulk;
using LINGYUN.Abp.Elasticsearch;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.SecurityLog;
namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
[Dependency(ReplaceServices = true)]
public class ElasticsearchSecurityLogWriter : ISecurityLogWriter, ITransientDependency
{
private readonly IElasticsearchClientFactory _clientFactory;
private readonly IIndexNameNormalizer _indexNameNormalizer;
private readonly IGuidGenerator _guidGenerator;
private readonly ILogger<ElasticsearchSecurityLogWriter> _logger;
public ElasticsearchSecurityLogWriter(
IElasticsearchClientFactory clientFactory,
IIndexNameNormalizer indexNameNormalizer,
IGuidGenerator guidGenerator,
ILogger<ElasticsearchSecurityLogWriter> logger)
{
_clientFactory = clientFactory;
_indexNameNormalizer = indexNameNormalizer;
_guidGenerator = guidGenerator;
_logger = logger;
}
public async virtual Task WriteAsync(SecurityLogInfo securityLogInfo, CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var securityLog = new SecurityLog(
_guidGenerator.Create(),
securityLogInfo);
var response = await client.IndexAsync(
securityLog,
dsl => dsl.Index(CreateIndex())
.Id(securityLog.Id),
cancellationToken);
if (!response.IsValidResponse)
{
_logger.LogWarning("Could not save the security log object: " + Environment.NewLine + securityLog.ToString());
if (response.TryGetOriginalException(out var ex))
{
_logger.LogWarning(ex, ex.Message);
}
else if (response.ElasticsearchServerError != null)
{
_logger.LogWarning(response.ElasticsearchServerError.ToString());
}
}
}
public async virtual Task BulkWriteAsync(IEnumerable<SecurityLogInfo> securityLogInfos, CancellationToken cancellationToken = default)
{
if (!securityLogInfos.Any())
{
return;
}
var client = _clientFactory.Create();
var indexName = CreateIndex();
var bulkOperations = new List<BulkOperation>();
foreach (var securityLogInfo in securityLogInfos)
{
var securityLog = new SecurityLog(
_guidGenerator.Create(),
securityLogInfo);
bulkOperations.Add(new BulkCreateOperation<SecurityLog>(securityLog)
{
Id = securityLog.Id.ToString()
});
}
var bulkRequest = new BulkRequest
{
Operations = [.. bulkOperations],
Index = indexName
};
var response = await client.BulkAsync(bulkRequest, cancellationToken);
if (!response.IsValidResponse)
{
await HandleBulkErrorsAsync(response, securityLogInfos);
}
}
private Task HandleBulkErrorsAsync(BulkResponse response, IEnumerable<SecurityLogInfo> securityLogInfos)
{
foreach (var itemWithError in response.ItemsWithErrors)
{
_logger.LogError($"Failed to write security log: {itemWithError.Error?.Reason}");
}
foreach (var auditLog in securityLogInfos)
{
_logger.LogInformation(auditLog.ToString());
}
return Task.CompletedTask;
}
protected virtual string CreateIndex()
{
return _indexNameNormalizer.NormalizeIndex("security-log");
}
}

53
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/AuditLogManager.cs

@ -1,12 +1,8 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.AuditLogging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
@ -20,25 +16,15 @@ public class AuditLogManager : IAuditLogManager, ITransientDependency
protected IObjectMapper<AbpAuditLoggingEntityFrameworkCoreModule> ObjectMapper { get; }
protected IAuditLogRepository AuditLogRepository { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditLogInfoToAuditLogConverter Converter { get; }
public ILogger<AuditLogManager> Logger { protected get; set; }
public AuditLogManager(
IAuditLogRepository auditLogRepository,
IUnitOfWorkManager unitOfWorkManager,
IOptions<AbpAuditingOptions> options,
IAuditLogInfoToAuditLogConverter converter,
IObjectMapper<AbpAuditLoggingEntityFrameworkCoreModule> objectMapper)
{
ObjectMapper = objectMapper;
AuditLogRepository = auditLogRepository;
UnitOfWorkManager = unitOfWorkManager;
Converter = converter;
Options = options.Value;
Logger = NullLogger<AuditLogManager>.Instance;
}
@ -149,41 +135,4 @@ public class AuditLogManager : IAuditLogManager, ITransientDependency
await uow.CompleteAsync();
}
}
public async virtual Task<string> SaveAsync(
AuditLogInfo auditInfo,
CancellationToken cancellationToken = default)
{
if (!Options.HideErrors)
{
return await SaveLogAsync(auditInfo, cancellationToken);
}
try
{
return await SaveLogAsync(auditInfo, cancellationToken);
}
catch (Exception ex)
{
Logger.LogWarning("Could not save the audit log object: " + Environment.NewLine + auditInfo.ToString());
Logger.LogException(ex, LogLevel.Error);
}
return "";
}
protected async virtual Task<string> SaveLogAsync(
AuditLogInfo auditInfo,
CancellationToken cancellationToken = default)
{
using (var uow = UnitOfWorkManager.Begin(true))
{
var auditLog = await AuditLogRepository.InsertAsync(
await Converter.ConvertAsync(auditInfo),
false,
cancellationToken);
await uow.CompleteAsync();
return auditLog.Id.ToString();
}
}
}

62
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogWriter.cs

@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.AuditLogging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Uow;
using IVoloAuditLogInfoToAuditLogConverter = Volo.Abp.AuditLogging.IAuditLogInfoToAuditLogConverter;
using VoloAuditLog = Volo.Abp.AuditLogging.AuditLog;
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
[Dependency(ReplaceServices = true)]
public class EfCoreAuditLogWriter : IAuditLogWriter, ITransientDependency
{
protected IVoloAuditLogInfoToAuditLogConverter AuditLogConverter { get; }
protected IAuditLogRepository AuditLogRepository { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IGuidGenerator GuidGenerator { get; }
public EfCoreAuditLogWriter(
IVoloAuditLogInfoToAuditLogConverter auditLogConverter,
IAuditLogRepository auditLogRepository,
IUnitOfWorkManager unitOfWorkManager,
IGuidGenerator guidGenerator)
{
AuditLogConverter = auditLogConverter;
AuditLogRepository = auditLogRepository;
UnitOfWorkManager = unitOfWorkManager;
GuidGenerator = guidGenerator;
}
public async virtual Task WriteAsync(AuditLogInfo auditLogInfo, CancellationToken cancellationToken = default)
{
using (var uow = UnitOfWorkManager.Begin(true))
{
var auditLog = await AuditLogConverter.ConvertAsync(auditLogInfo);
await AuditLogRepository.InsertAsync(auditLog);
await uow.CompleteAsync();
}
}
public async virtual Task BulkWriteAsync(IEnumerable<AuditLogInfo> auditLogInfos, CancellationToken cancellationToken = default)
{
using (var uow = UnitOfWorkManager.Begin(true))
{
var auditLogs = new List<VoloAuditLog>();
foreach (var auditLogInfo in auditLogInfos)
{
var auditLog = await AuditLogConverter.ConvertAsync(auditLogInfo);
auditLogs.Add(auditLog);
}
await AuditLogRepository.InsertManyAsync(auditLogs);
await uow.CompleteAsync();
}
}
}

57
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreSecurityLogWriter.cs

@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.SecurityLog;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
[Dependency(ReplaceServices = true)]
public class EfCoreSecurityLogWriter : ISecurityLogWriter, ITransientDependency
{
protected IIdentitySecurityLogRepository IdentitySecurityLogRepository { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IGuidGenerator GuidGenerator { get; }
public EfCoreSecurityLogWriter(
IIdentitySecurityLogRepository identitySecurityLogRepository,
IUnitOfWorkManager unitOfWorkManager,
IGuidGenerator guidGenerator)
{
IdentitySecurityLogRepository = identitySecurityLogRepository;
UnitOfWorkManager = unitOfWorkManager;
GuidGenerator = guidGenerator;
}
public async virtual Task BulkWriteAsync(IEnumerable<SecurityLogInfo> securityLogInfos, CancellationToken cancellationToken = default)
{
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var securityLogs = securityLogInfos.Select(securityLogInfo =>
new IdentitySecurityLog(GuidGenerator, securityLogInfo));
await IdentitySecurityLogRepository.InsertManyAsync(
securityLogs,
false,
cancellationToken);
await uow.CompleteAsync();
}
}
public async virtual Task WriteAsync(SecurityLogInfo securityLogInfo, CancellationToken cancellationToken = default)
{
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
await IdentitySecurityLogRepository.InsertAsync(
new IdentitySecurityLog(GuidGenerator, securityLogInfo),
false,
cancellationToken);
await uow.CompleteAsync();
}
}
}

34
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/SecurityLogManager.cs

@ -1,14 +1,10 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.SecurityLog;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
@ -16,26 +12,17 @@ namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
[Dependency(ReplaceServices = true)]
public class SecurityLogManager : ISecurityLogManager, ITransientDependency
{
public ILogger<SecurityLogManager> Logger { get; set; }
protected IObjectMapper<AbpAuditLoggingEntityFrameworkCoreModule> ObjectMapper { get; }
protected AbpSecurityLogOptions SecurityLogOptions { get; }
protected IIdentitySecurityLogRepository IdentitySecurityLogRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
public SecurityLogManager(
IObjectMapper<AbpAuditLoggingEntityFrameworkCoreModule> objectMapper,
ILogger<SecurityLogManager> logger,
IOptions<AbpSecurityLogOptions> securityLogOptions,
IIdentitySecurityLogRepository identitySecurityLogRepository,
IGuidGenerator guidGenerator,
IUnitOfWorkManager unitOfWorkManager)
{
Logger = logger;
ObjectMapper = objectMapper;
SecurityLogOptions = securityLogOptions.Value;
IdentitySecurityLogRepository = identitySecurityLogRepository;
GuidGenerator = guidGenerator;
UnitOfWorkManager = unitOfWorkManager;
}
@ -49,25 +36,6 @@ public class SecurityLogManager : ISecurityLogManager, ITransientDependency
}
}
public async virtual Task SaveAsync(
SecurityLogInfo securityLogInfo,
CancellationToken cancellationToken = default)
{
if (!SecurityLogOptions.IsEnabled)
{
return;
}
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
await IdentitySecurityLogRepository.InsertAsync(
new IdentitySecurityLog(GuidGenerator, securityLogInfo),
false,
cancellationToken);
await uow.CompleteAsync();
}
}
public async virtual Task<SecurityLog> GetAsync(
Guid id,
bool includeDetails = false,

7
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.IP.Location/LINGYUN/Abp/AuditLogging/IP/Location/IPLocationAuditingStore.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.IP.Location;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
@ -12,8 +13,10 @@ public class IPLocationAuditingStore : AuditingStore
public IPLocationAuditingStore(
IOptionsMonitor<AbpAuditLoggingIPLocationOptions> options,
IIPLocationResolver iPLocationResolver,
IAuditLogManager manager)
: base(manager)
IOptionsMonitor<AbpAuditLoggingOptions> loggingOptions,
IAuditLogQueue auditLogQueue,
ILogger<AuditingStore> logger)
: base(loggingOptions, auditLogQueue, logger)
{
_options = options.CurrentValue;
_iPLocationResolver = iPLocationResolver;

8
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.IP.Location/LINGYUN/Abp/AuditLogging/IP/Location/IPLocationSecurityLogStore.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.IP.Location;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
@ -13,8 +14,11 @@ public class IPLocationSecurityLogStore : SecurityLogStore
public IPLocationSecurityLogStore(
IOptionsMonitor<AbpAuditLoggingIPLocationOptions> options,
IIPLocationResolver iPLocationResolver,
ISecurityLogManager manager)
: base(manager)
IOptionsMonitor<AbpSecurityLogOptions> securityLogOptions,
IOptionsMonitor<AbpAuditLoggingOptions> loggingOptions,
ISecurityLogQueue securityLogQueue,
ILogger<SecurityLogStore> logger)
: base(securityLogOptions, loggingOptions, securityLogQueue, logger)
{
_options = options.CurrentValue;
_iPLocationResolver = iPLocationResolver;

1
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN.Abp.AuditLogging.csproj

@ -19,6 +19,7 @@
<PackageReference Include="Volo.Abp.Auditing" />
<PackageReference Include="Volo.Abp.Guids" />
<PackageReference Include="Volo.Abp.ExceptionHandling" />
<PackageReference Include="System.Threading.Channels" />
</ItemGroup>
</Project>

67
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingModule.cs

@ -1,9 +1,15 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.AuditLogging;
@ -13,12 +19,71 @@ namespace LINGYUN.Abp.AuditLogging;
typeof(AbpExceptionHandlingModule))]
public class AbpAuditLoggingModule : AbpModule
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpAuditLoggingOptions>(configuration.GetSection("AuditLogging"));
Configure<AbpAuditingOptions>(options =>
{
options.IgnoredTypes.AddIfNotContains(typeof(CancellationToken));
options.IgnoredTypes.AddIfNotContains(typeof(CancellationTokenSource));
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context));
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpAuditLoggingOptions>>();
if (options.Value.UseAuditLogQueue)
{
var auditLogQueue = rootServiceProvider.GetRequiredService<IAuditLogQueue>();
if (auditLogQueue is AuditLogQueue queue1)
{
await queue1.StartAsync(_cancellationTokenSource.Token);
}
}
if (options.Value.UseSecurityLogQueue)
{
var securityLogQueue = rootServiceProvider.GetRequiredService<ISecurityLogQueue>();
if (securityLogQueue is SecurityLogQueue queue2)
{
await queue2.StartAsync(_cancellationTokenSource.Token);
}
}
}
public async override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpAuditLoggingOptions>>();
if (options.Value.UseAuditLogQueue)
{
var auditLogQueue = rootServiceProvider.GetRequiredService<IAuditLogQueue>();
if (auditLogQueue is AuditLogQueue queue1)
{
await queue1.StopAsync(_cancellationTokenSource.Token);
}
}
if (options.Value.UseSecurityLogQueue)
{
var securityLogQueue = rootServiceProvider.GetRequiredService<ISecurityLogQueue>();
if (securityLogQueue is SecurityLogQueue queue2)
{
await queue2.StopAsync(_cancellationTokenSource.Token);
}
}
_cancellationTokenSource.Cancel();
}
}

54
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingOptions.cs

@ -0,0 +1,54 @@
namespace LINGYUN.Abp.AuditLogging;
public class AbpAuditLoggingOptions
{
/// <summary>
/// 是否启用审计日志记录
/// </summary>
public bool IsAuditLogEnabled { get; set; }
/// <summary>
/// 使用审计日志队列
/// </summary>
/// <remarks>
/// 不启用队列则直接写入到持久化设施
/// </remarks>
public bool UseAuditLogQueue { get; set; }
/// <summary>
/// 审计日志最大队列大小
/// </summary>
public int MaxAuditLogQueueSize { get; set; }
/// <summary>
/// 一次处理审计日志队列大小
/// </summary>
public int BatchAuditLogSize { get; set; }
/// <summary>
/// 是否启用安全日志记录
/// </summary>
public bool IsSecurityLogEnabled { get; set; }
/// <summary>
/// 使用安全日志队列
/// </summary>
/// <remarks>
/// 不启用队列则直接写入到持久化设施
/// </remarks>
public bool UseSecurityLogQueue { get; set; }
/// <summary>
/// 安全日志最大队列大小
/// </summary>
public int MaxSecurityLogQueueSize { get; set; }
/// <summary>
/// 一次处理安全日志队列大小
/// </summary>
public int BatchSecurityLogSize { get; set; }
public AbpAuditLoggingOptions()
{
IsAuditLogEnabled = true;
UseAuditLogQueue = true;
BatchAuditLogSize = 100;
MaxAuditLogQueueSize = 10000;
IsSecurityLogEnabled = true;
UseSecurityLogQueue = true;
BatchSecurityLogSize = 100;
MaxSecurityLogQueueSize = 10000;
}
}

0
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogInfoToAuditLogConverter.cs → aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs

50
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditLogQueue.cs

@ -0,0 +1,50 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.AuditLogging;
public class AuditLogQueue : AuditLoggingQueue<AuditLogInfo>, IAuditLogQueue, ISingletonDependency
{
private readonly IAuditLogWriter _auditLogWriter;
private readonly ICurrentTenant _currentTenant;
public AuditLogQueue(
IOptions<AbpAuditLoggingOptions> options,
IAuditLogWriter auditLogWriter,
ICurrentTenant currentTenant,
ILogger<AuditLogQueue> logger)
: base(
"AuditLog",
options.Value.MaxAuditLogQueueSize,
options.Value.BatchAuditLogSize,
logger)
{
_auditLogWriter = auditLogWriter;
_currentTenant = currentTenant;
}
protected async override Task BulkWriteAsync(IEnumerable<AuditLogInfo> auditLogInfos, CancellationToken cancellationToken = default)
{
var tenantAuditlogGroup = auditLogInfos.GroupBy(x => x.ImpersonatorTenantId ?? x.TenantId);
foreach (var tenantAuditlogs in tenantAuditlogGroup)
{
using (_currentTenant.Change(tenantAuditlogs.Key))
{
await _auditLogWriter.BulkWriteAsync(tenantAuditlogs, cancellationToken);
}
}
}
protected async override Task WriteAsync(AuditLogInfo auditLogInfo, CancellationToken cancellationToken = default)
{
using (_currentTenant.Change(auditLogInfo.ImpersonatorTenantId ?? auditLogInfo.TenantId))
{
await _auditLogWriter.WriteAsync(auditLogInfo, cancellationToken);
}
}
}

196
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditLoggingQueue.cs

@ -0,0 +1,196 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace LINGYUN.Abp.AuditLogging;
public abstract class AuditLoggingQueue<TLog>
{
private readonly ILogger<AuditLoggingQueue<TLog>> _logger;
private readonly Channel<TLog> _channel;
private readonly string _logName;
private readonly int _batchSize;
private readonly int _maxConcurrency;
private volatile Task _consumerTask;
private readonly SemaphoreSlim _flushSemaphore;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
protected AuditLoggingQueue(
string logName,
int maxQueueSize,
int batchSize,
ILogger<AuditLoggingQueue<TLog>> logger)
{
_logName = logName;
_batchSize = batchSize;
_logger = logger;
var channelOptions = new BoundedChannelOptions(maxQueueSize)
{
FullMode = BoundedChannelFullMode.Wait,
SingleWriter = false,
SingleReader = true,
};
_channel = Channel.CreateBounded<TLog>(channelOptions);
var calculatedConcurrency = (int)Math.Ceiling(Math.Max(1, maxQueueSize) / (double)Math.Max(1, batchSize));
var defaultConcurrency = Environment.ProcessorCount * 2;
_maxConcurrency = Math.Min(calculatedConcurrency, defaultConcurrency);
_flushSemaphore = new SemaphoreSlim(_maxConcurrency, _maxConcurrency);
}
public virtual Task EnqueueAsync(TLog log, CancellationToken cancellationToken = default)
{
try
{
if (!_channel.Writer.TryWrite(log))
{
_logger.LogWarning("{logName} channel is full; {logName} is recorded in the log.", _logName, _logName);
_logger.LogInformation(log!.ToString());
}
return Task.CompletedTask;
}
catch (ChannelClosedException)
{
_logger.LogWarning("{logName} channel is closed; dropping it.", _logName);
}
catch (OperationCanceledException)
{
_logger.LogDebug("EnqueueAsync was canceled while waiting to write to {logName} channel.", _logName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected exception while enqueuing {logName}.", _logName);
}
return Task.CompletedTask;
}
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_consumerTask = Task.Factory.StartNew(
() => ConsumeBatchesAsync(),
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default)
.Unwrap();
return Task.CompletedTask;
}
public async virtual Task StopAsync(CancellationToken cancellationToken)
{
_channel.Writer.TryComplete();
if (_consumerTask != null)
{
try
{
await Task.WhenAny(_consumerTask, Task.Delay(TimeSpan.FromSeconds(5), cancellationToken));
_cts.Cancel();
_logger.LogInformation("Stopped consumer loop: {logName}.", _logName);
}
catch (OperationCanceledException)
{
_logger.LogWarning("StopAsync was canceled while waiting for consumer loop: {logName} to finish.", _logName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception while stopping consumer loop: {logName}.", _logName);
}
finally
{
_cts.Dispose();
}
}
}
private async Task ConsumeBatchesAsync()
{
var reader = _channel.Reader;
var batchSize = Math.Max(1, _batchSize);
var batchSendTaskList = new List<Task>(_maxConcurrency * 2);
_logger.LogInformation("Starting {logName} consumer loop. BatchSize={batchSize}", _logName, batchSize);
try
{
while (await reader.WaitToReadAsync(_cts.Token))
{
var buffer = new List<TLog>(batchSize);
while (buffer.Count < batchSize && reader.TryRead(out var item))
{
buffer.Add(item);
}
if (buffer.Count > 0)
{
batchSendTaskList.Add(SendBatchAsync(buffer, _cts.Token));
if (batchSendTaskList.Count >= _maxConcurrency)
{
try
{
var completedTask = await Task.WhenAny(batchSendTaskList);
batchSendTaskList.Remove(completedTask);
}
catch (Exception ex)
{
_logger.LogError(ex, "Background send {logName} task failed.", _logName);
}
}
}
}
await Task.WhenAll(batchSendTaskList);
}
catch (OperationCanceledException)
{
// ignore
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected exception in {logName} consumer loop.", _logName);
}
}
private async Task SendBatchAsync(List<TLog> batch, CancellationToken cancellationToken)
{
if (batch == null || batch.Count == 0)
{
return;
}
await _flushSemaphore.WaitAsync(cancellationToken);
try
{
if (batch.Count == 1)
{
await WriteAsync(batch[0], cancellationToken);
}
else
{
await BulkWriteAsync(batch, cancellationToken);
}
}
catch (OperationCanceledException)
{
_logger.LogDebug("SendBatchAsync canceled while writing {logName}.", _logName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to write {logName} batch to Elasticsearch. Items={count}", _logName, batch.Count);
}
finally
{
_flushSemaphore.Release();
}
}
protected abstract Task WriteAsync(TLog log, CancellationToken cancellationToken = default);
protected abstract Task BulkWriteAsync(IEnumerable<TLog> logs, CancellationToken cancellationToken = default);
}

33
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AuditingStore.cs

@ -1,4 +1,6 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
@ -7,16 +9,37 @@ namespace LINGYUN.Abp.AuditLogging;
[Dependency(ReplaceServices = true)]
public class AuditingStore : IAuditingStore, ITransientDependency
{
private readonly IAuditLogManager _manager;
private readonly AbpAuditLoggingOptions _loggingOptions;
private readonly IAuditLogWriter _auditLogWriter;
private readonly IAuditLogQueue _auditLogQueue;
private readonly ILogger<AuditingStore> _logger;
public AuditingStore(
IAuditLogManager manager)
IOptionsMonitor<AbpAuditLoggingOptions> loggingOptions,
IAuditLogWriter auditLogWriter,
IAuditLogQueue auditLogQueue,
ILogger<AuditingStore> logger)
{
_manager = manager;
_loggingOptions = loggingOptions.CurrentValue;
_auditLogWriter = auditLogWriter;
_auditLogQueue = auditLogQueue;
_logger = logger;
}
public async virtual Task SaveAsync(AuditLogInfo auditInfo)
{
await _manager.SaveAsync(auditInfo);
if (!_loggingOptions.IsAuditLogEnabled)
{
_logger.LogInformation(auditInfo.ToString());
return;
}
if (_loggingOptions.UseAuditLogQueue)
{
await _auditLogQueue.EnqueueAsync(auditInfo);
}
else
{
await _auditLogWriter.WriteAsync(auditInfo);
}
}
}

0
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IAuditLogInfoToAuditLogConverter.cs → aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogInfoToAuditLogConverter.cs

5
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogManager.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.AuditLogging;
@ -22,10 +21,6 @@ public interface IAuditLogManager
List<Guid> ids,
CancellationToken cancellationToken = default);
Task<string> SaveAsync(
AuditLogInfo auditInfo,
CancellationToken cancellationToken = default);
Task<long> GetCountAsync(
DateTime? startTime = null,
DateTime? endTime = null,

9
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogQueue.cs

@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.AuditLogging;
public interface IAuditLogQueue
{
Task EnqueueAsync(AuditLogInfo auditLogInfo, CancellationToken cancellationToken = default);
}

12
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/IAuditLogWriter.cs

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.AuditLogging;
public interface IAuditLogWriter
{
Task WriteAsync(AuditLogInfo auditLogInfo, CancellationToken cancellationToken = default);
Task BulkWriteAsync(IEnumerable<AuditLogInfo> auditLogInfos, CancellationToken cancellationToken = default);
}

5
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/ISecurityLogManager.cs

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.SecurityLog;
namespace LINGYUN.Abp.AuditLogging;
@ -21,10 +20,6 @@ public interface ISecurityLogManager
List<Guid> ids,
CancellationToken cancellationToken = default);
Task SaveAsync(
SecurityLogInfo securityLogInfo,
CancellationToken cancellationToken = default);
Task<List<SecurityLog>> GetListAsync(
string? sorting = null,
int maxResultCount = 50,

9
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/ISecurityLogQueue.cs

@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.SecurityLog;
namespace LINGYUN.Abp.AuditLogging;
public interface ISecurityLogQueue
{
Task EnqueueAsync(SecurityLogInfo securityLogInfo, CancellationToken cancellationToken = default);
}

12
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/ISecurityLogWriter.cs

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.SecurityLog;
namespace LINGYUN.Abp.AuditLogging;
public interface ISecurityLogWriter
{
Task WriteAsync(SecurityLogInfo securityLogInfo, CancellationToken cancellationToken = default);
Task BulkWriteAsync(IEnumerable<SecurityLogInfo> securityLogInfos, CancellationToken cancellationToken = default);
}

33
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/LoggerAuditLogWriter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.AuditLogging;
[Dependency(TryRegister = true)]
public class LoggerAuditLogWriter : IAuditLogWriter, ISingletonDependency
{
private readonly ILogger<LoggerAuditLogWriter> _logger;
public LoggerAuditLogWriter(ILogger<LoggerAuditLogWriter> logger)
{
_logger = logger;
}
public async virtual Task BulkWriteAsync(IEnumerable<AuditLogInfo> auditLogInfos, CancellationToken cancellationToken = default)
{
foreach (var auditLogInfo in auditLogInfos)
{
await WriteAsync(auditLogInfo, cancellationToken);
}
}
public virtual Task WriteAsync(AuditLogInfo auditLogInfo, CancellationToken cancellationToken = default)
{
_logger.LogInformation(auditLogInfo.ToString());
return Task.CompletedTask;
}
}

33
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/LoggerSecurityLogWriter.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.SecurityLog;
namespace LINGYUN.Abp.AuditLogging;
[Dependency(TryRegister = true)]
public class LoggerSecurityLogWriter : ISecurityLogWriter, ISingletonDependency
{
private readonly ILogger<LoggerSecurityLogWriter> _logger;
public LoggerSecurityLogWriter(ILogger<LoggerSecurityLogWriter> logger)
{
_logger = logger;
}
public async virtual Task BulkWriteAsync(IEnumerable<SecurityLogInfo> securityLogInfos, CancellationToken cancellationToken = default)
{
foreach (var securityLogInfo in securityLogInfos)
{
await WriteAsync(securityLogInfo, cancellationToken);
}
}
public virtual Task WriteAsync(SecurityLogInfo securityLogInfo, CancellationToken cancellationToken = default)
{
_logger.LogInformation(securityLogInfo.ToString());
return Task.CompletedTask;
}
}

50
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/SecurityLogQueue.cs

@ -0,0 +1,50 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.SecurityLog;
namespace LINGYUN.Abp.AuditLogging;
public class SecurityLogQueue : AuditLoggingQueue<SecurityLogInfo>, ISecurityLogQueue, ISingletonDependency
{
private readonly ISecurityLogWriter _securityLogWriter;
private readonly ICurrentTenant _currentTenant;
public SecurityLogQueue(
IOptions<AbpAuditLoggingOptions> options,
ISecurityLogWriter securityLogWriter,
ICurrentTenant currentTenant,
ILogger<SecurityLogQueue> logger)
: base(
"SecurityLog",
options.Value.MaxSecurityLogQueueSize,
options.Value.BatchSecurityLogSize,
logger)
{
_securityLogWriter = securityLogWriter;
_currentTenant = currentTenant;
}
protected async override Task BulkWriteAsync(IEnumerable<SecurityLogInfo> securityLogInfos, CancellationToken cancellationToken = default)
{
var tenantSecurityLogGroup = securityLogInfos.GroupBy(x => x.TenantId);
foreach (var tenantSecurityLogs in tenantSecurityLogGroup)
{
using (_currentTenant.Change(tenantSecurityLogs.Key))
{
await _securityLogWriter.BulkWriteAsync(tenantSecurityLogs, cancellationToken);
}
}
}
protected async override Task WriteAsync(SecurityLogInfo securityLogInfo, CancellationToken cancellationToken = default)
{
using (_currentTenant.Change(securityLogInfo.TenantId))
{
await _securityLogWriter.WriteAsync(securityLogInfo, cancellationToken);
}
}
}

36
aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/SecurityLogStore.cs

@ -1,4 +1,6 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.SecurityLog;
@ -7,16 +9,40 @@ namespace LINGYUN.Abp.AuditLogging;
[Dependency(ReplaceServices = true)]
public class SecurityLogStore : ISecurityLogStore, ITransientDependency
{
private readonly ISecurityLogManager _manager;
private readonly AbpSecurityLogOptions _securityLogOptions;
private readonly AbpAuditLoggingOptions _loggingOptions;
private readonly ISecurityLogWriter _securityLogWriter;
private readonly ISecurityLogQueue _securityLogQueue;
private readonly ILogger<SecurityLogStore> _logger;
public SecurityLogStore(
ISecurityLogManager manager)
IOptionsMonitor<AbpSecurityLogOptions> securityLogOptions,
IOptionsMonitor<AbpAuditLoggingOptions> loggingOptions,
ISecurityLogWriter securityLogWriter,
ISecurityLogQueue securityLogQueue,
ILogger<SecurityLogStore> logger)
{
_manager = manager;
_securityLogOptions = securityLogOptions.CurrentValue;
_loggingOptions = loggingOptions.CurrentValue;
_securityLogWriter = securityLogWriter;
_securityLogQueue = securityLogQueue;
_logger = logger;
}
public async virtual Task SaveAsync(SecurityLogInfo securityLogInfo)
{
await _manager.SaveAsync(securityLogInfo);
if (!_loggingOptions.IsAuditLogEnabled || !_securityLogOptions.IsEnabled)
{
_logger.LogInformation(securityLogInfo.ToString());
return;
}
if (_loggingOptions.UseSecurityLogQueue)
{
await _securityLogQueue.EnqueueAsync(securityLogInfo);
}
else
{
await _securityLogWriter.WriteAsync(securityLogInfo);
}
}
}

23
aspnet-core/framework/auditing/README.md

@ -15,8 +15,11 @@
### 存储支持
- EntityFrameworkCore 实现
- Elasticsearch 实现
- EntityFrameworkCore 实现
- Elasticsearch 实现
> 注意: Elastic库限制, 兼容8.x; 9.x版本, 如需使用10.x版本, 请切换 **Elastic.Clients.Elasticsearch** 为9.x版本
> 参考: https://www.nuget.org/packages/Elastic.Clients.Elasticsearch#readme-body-tab
## 模块引用
@ -62,6 +65,17 @@ public class YouProjectModule : AbpModule
"IsEnabledForAnonymousUsers": true, // 是否为匿名用户启用审计日志
"IsEnabledForGetRequests": false, // 是否为GET请求启用审计日志
"ApplicationName": null // 应用程序名称
},
// 审计日志增强配置
"AuditLogging": {
"IsAuditLogEnabled": true, // 是否启用审计日志记录
"UseAuditLogQueue": true, // 使用审计日志队列, 不启用队列则直接写入到持久化设施
"MaxAuditLogQueueSize": 10000, // 审计日志最大队列大小, 默认10000
"BatchAuditLogSize": 100, // 一次处理审计日志队列大小, 队列中同时写入100条记录后立即写入到持久化设施中
"IsSecurityLogEnabled": true, // 是否启用安全日志记录
"UseSecurityLogQueue": true, // 使用安全日志队列, 不启用队列则直接写入到持久化设施
"MaxSecurityLogQueueSize": 10000, // 安全日志最大队列大小, 默认10000
"BatchSecurityLogSize": 100 // 一次处理安全日志队列大小, 队列中同时写入100条记录后立即写入到持久化设施中
}
}
```
@ -72,7 +86,8 @@ public class YouProjectModule : AbpModule
{
"AuditLogging": {
"Elasticsearch": {
"IndexPrefix": "auditlogging" // 索引前缀
"IndexPrefix": "auditlogging", // 索引前缀
"ThrowIfIndexInitFailed": true // 索引初始化失败抛出异常, 默认为: true, 索引初始化失败后应用程序停止运行
}
}
}
@ -110,4 +125,4 @@ Configure<AbpAuditingOptions>(options =>
## 特殊说明
- Elasticsearch 实现支持跨租户,将根据租户自动切换索引
- EntityFrameworkCore 实现主要作为桥梁,具体实现交由 Abp 官方模块管理
- EntityFrameworkCore 实现主要作为桥梁,具体实现交由 Abp 官方模块管理

6
aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestBase.cs

@ -1,10 +1,4 @@
using LINGYUN.Abp.Tests;
using Moq.AutoMock;
using Nest;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.AuditLogging.Elasticsearch
{

6
aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestModule.cs

@ -29,10 +29,10 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpAuditLoggingElasticsearchOptions>>().Value;
var clientFactory = context.ServiceProvider.GetRequiredService<IElasticsearchClientFactory>();
var client = clientFactory.Create();
var indicesResponse = client.Cat.Indices(i => i.Index($"{options.IndexPrefix}-security-log"));
foreach (var index in indicesResponse.Records)
var indicesResponse = client.Indices.Get($"{options.IndexPrefix}-security-log");
foreach (var index in indicesResponse.Indices)
{
client.Indices.Delete(index.Index);
client.Indices.Delete(index.Key);
}
}
}

1
aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogManagerTests.cs

@ -17,6 +17,7 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch
_manager = GetRequiredService<IAuditLogManager>();
}
[Fact]
public async Task Save_Audit_Log_Should_Be_Find_By_Id()
{
var mock = new AutoMocker();

Loading…
Cancel
Save