From 48fc639f0b34fdf61c33ec53b20b292599f13251 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 2 Feb 2026 14:43:48 +0800 Subject: [PATCH] feat: Use the generic audit log index template --- .../AbpAuditLoggingElasticsearchModule.cs | 25 ++- .../AbpAuditLoggingElasticsearchOptions.cs | 8 + ...zer.cs => AuditLoggingIndexInitializer.cs} | 179 ++++++++++++------ .../IAuditLoggingIndexInitializer.cs | 9 + .../Elasticsearch/IIndexInitializer.cs | 8 - .../Elasticsearch/IndexInitializerService.cs | 20 -- 6 files changed, 160 insertions(+), 89 deletions(-) rename aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/{IndexInitializer.cs => AuditLoggingIndexInitializer.cs} (51%) create mode 100644 aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IAuditLoggingIndexInitializer.cs delete mode 100644 aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IIndexInitializer.cs delete mode 100644 aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializerService.cs diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchModule.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchModule.cs index d25265666..e1d02371e 100644 --- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchModule.cs +++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchModule.cs @@ -1,7 +1,12 @@ using LINGYUN.Abp.Elasticsearch; using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; using Volo.Abp.Json; using Volo.Abp.Modularity; +using Volo.Abp.Threading; namespace LINGYUN.Abp.AuditLogging.Elasticsearch; @@ -11,11 +16,29 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch; typeof(AbpJsonModule))] public class AbpAuditLoggingElasticsearchModule : AbpModule { + private readonly CancellationTokenSource _cancellationTokenSource = new(); + public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); Configure(configuration.GetSection("AuditLogging:Elasticsearch")); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); + } - context.Services.AddHostedService(); + public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + var initializer = rootServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(_cancellationTokenSource.Token); + } + + public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) + { + _cancellationTokenSource.Cancel(); + return Task.CompletedTask; } } diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchOptions.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchOptions.cs index 371638b74..e3976e330 100644 --- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchOptions.cs +++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchOptions.cs @@ -7,6 +7,13 @@ public class AbpAuditLoggingElasticsearchOptions public const string DefaultIndexPrefix = "auditlogging"; public string IndexPrefix { get; set; } /// + /// 索引初始化失败抛出异常 + /// + /// + /// 默认为: true, 索引初始化失败后应用程序停止运行 + /// + public bool ThrowIfIndexInitFailed { get; set; } + /// /// 是否启用审计日志记录 /// public bool IsAuditLogEnabled { get; set; } @@ -27,6 +34,7 @@ public class AbpAuditLoggingElasticsearchOptions { IndexPrefix = DefaultIndexPrefix; IsAuditLogEnabled = true; + ThrowIfIndexInitFailed = true; AuditLogSettings = new IndexSettings() { NumberOfReplicas = 1, diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializer.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs similarity index 51% rename from aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializer.cs rename to aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs index b5e4141c1..cc269932f 100644 --- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializer.cs +++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs @@ -1,26 +1,30 @@ using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Mapping; +using Elastic.Transport.Products.Elasticsearch; using LINGYUN.Abp.Elasticsearch; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Json; namespace LINGYUN.Abp.AuditLogging.Elasticsearch; -public class IndexInitializer : IIndexInitializer, ISingletonDependency +public class AuditLoggingIndexInitializer : IAuditLoggingIndexInitializer, ISingletonDependency { private readonly AbpJsonOptions _jsonOptions; private readonly AbpAuditLoggingElasticsearchOptions _elasticsearchOptions; private readonly IIndexNameNormalizer _nameNormalizer; private readonly IElasticsearchClientFactory _clientFactory; - public ILogger Logger { protected get; set; } + public ILogger Logger { protected get; set; } - public IndexInitializer( + public AuditLoggingIndexInitializer( IOptions jsonOptions, IOptions elasticsearchOptions, IIndexNameNormalizer nameNormalizer, @@ -31,29 +35,40 @@ public class IndexInitializer : IIndexInitializer, ISingletonDependency _nameNormalizer = nameNormalizer; _clientFactory = clientFactory; - Logger = NullLogger.Instance; + Logger = NullLogger.Instance; } - public async virtual Task InitializeAsync() + public async virtual Task InitializeAsync(CancellationToken cancellationToken = default) { var client = _clientFactory.Create(); var dateTimeFormat = !_jsonOptions.OutputDateTimeFormat.IsNullOrWhiteSpace() ? $"{_jsonOptions.OutputDateTimeFormat}||strict_date_optional_time||epoch_millis" : "strict_date_optional_time||epoch_millis"; - await InitlizeAuditLogIndex(client, dateTimeFormat); - await InitlizeSecurityLogIndex(client, dateTimeFormat); + await InitlizeAuditLogIndexTemplate(client, dateTimeFormat, cancellationToken); + await InitlizeSecurityLogIndexTemplate(client, dateTimeFormat, cancellationToken); } - protected async virtual Task InitlizeAuditLogIndex(ElasticsearchClient client, string dateTimeFormat) + protected async virtual Task InitlizeAuditLogIndexTemplate(ElasticsearchClient client, string dateTimeFormat, CancellationToken cancellationToken = default) { var indexName = _nameNormalizer.NormalizeIndex("audit-log"); - var indexExists = await client.Indices.ExistsAsync(indexName); - if (!indexExists.Exists) + var indexPatterns = new[] { indexName + "-*" }; + var indexTemplateName = indexName + "-generic"; + + var indexTemplateExists = await client.Indices.ExistsIndexTemplateAsync(indexTemplateName, cancellationToken); + if (indexTemplateExists.Exists) + { + return; + } + + var putTemplateResponse = await client.Indices.PutIndexTemplateAsync(indexTemplateName, setup => { - var indexCreateResponse = await client.Indices.CreateAsync(indexName, c => + setup.IndexPatterns(indexPatterns); + setup.Priority(100); + setup.Version(1); + setup.Template(template => { - c.Settings(_elasticsearchOptions.AuditLogSettings); - c.Mappings(mp => mp + template.Settings(_elasticsearchOptions.AuditLogSettings); + template.Mappings(mp => mp .Dynamic(DynamicMapping.False) .Properties(pd => { @@ -83,31 +98,31 @@ public class IndexInitializer : IIndexInitializer, ISingletonDependency { np.Dynamic(DynamicMapping.False); np.Properties(npd => - { - npd.Text(nameof(EntityChange.Id), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - npd.Text(nameof(EntityChange.AuditLogId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - npd.Text(nameof(EntityChange.TenantId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - npd.Date(nameof(EntityChange.ChangeTime), d => d.Format(dateTimeFormat)); - npd.ByteNumber(nameof(EntityChange.ChangeType)); - npd.Text(nameof(EntityChange.EntityTenantId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - npd.Text(nameof(EntityChange.EntityId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(128)))); - npd.Text(nameof(EntityChange.EntityTypeFullName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); - npd.Nested(nameof(EntityChange.PropertyChanges), pc => - { - pc.Dynamic(DynamicMapping.False); - pc.Properties(pcn => - { - pcn.Text(nameof(EntityPropertyChange.Id), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - pcn.Text(nameof(EntityPropertyChange.TenantId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - pcn.Text(nameof(EntityPropertyChange.EntityChangeId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); - pcn.Text(nameof(EntityPropertyChange.NewValue), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); - pcn.Text(nameof(EntityPropertyChange.OriginalValue), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); - pcn.Text(nameof(EntityPropertyChange.PropertyName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); - pcn.Text(nameof(EntityPropertyChange.PropertyTypeFullName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); - }); - }); - npd.Flattened(nameof(EntityChange.ExtraProperties), f => f.DepthLimit(5).EagerGlobalOrdinals(false)); - }); + { + npd.Text(nameof(EntityChange.Id), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + npd.Text(nameof(EntityChange.AuditLogId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + npd.Text(nameof(EntityChange.TenantId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + npd.Date(nameof(EntityChange.ChangeTime), d => d.Format(dateTimeFormat)); + npd.ByteNumber(nameof(EntityChange.ChangeType)); + npd.Text(nameof(EntityChange.EntityTenantId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + npd.Text(nameof(EntityChange.EntityId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(128)))); + npd.Text(nameof(EntityChange.EntityTypeFullName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); + npd.Nested(nameof(EntityChange.PropertyChanges), pc => + { + pc.Dynamic(DynamicMapping.False); + pc.Properties(pcn => + { + pcn.Text(nameof(EntityPropertyChange.Id), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + pcn.Text(nameof(EntityPropertyChange.TenantId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + pcn.Text(nameof(EntityPropertyChange.EntityChangeId), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(36)))); + pcn.Text(nameof(EntityPropertyChange.NewValue), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); + pcn.Text(nameof(EntityPropertyChange.OriginalValue), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); + pcn.Text(nameof(EntityPropertyChange.PropertyName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); + pcn.Text(nameof(EntityPropertyChange.PropertyTypeFullName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256)))); + }); + }); + npd.Flattened(nameof(EntityChange.ExtraProperties), f => f.DepthLimit(5).EagerGlobalOrdinals(false)); + }); }); pd.Nested(n => n.Actions, np => { @@ -128,30 +143,57 @@ public class IndexInitializer : IIndexInitializer, ISingletonDependency pd.Flattened(f => f.ExtraProperties, f => f.DepthLimit(5).EagerGlobalOrdinals(false)); })); }); + }, cancellationToken); - if (!indexCreateResponse.IsValidResponse) + if (!putTemplateResponse.IsValidResponse) + { + var errorBuilder = new StringBuilder(); + if (putTemplateResponse.TryGetOriginalException(out var ex)) { - if (indexCreateResponse.TryGetOriginalException(out var ex)) - { - Logger.LogWarning(ex, "Failed to initialize index and audit log may not be retrieved."); - return; - } - Logger.LogWarning("Failed to initialize index and audit log may not be retrieved."); - Logger.LogWarning(indexCreateResponse.DebugInformation); + errorBuilder.AppendLine(ex.Message); + Logger.LogWarning(ex, "Failed to initialize index and audit log may not be retrieved."); + return; } + else if (putTemplateResponse.TryGetElasticsearchServerError(out var error)) + { + errorBuilder.AppendLine(error.ToString()); + } + else + { + errorBuilder.AppendLine(putTemplateResponse.DebugInformation); + } + + if (_elasticsearchOptions.ThrowIfIndexInitFailed) + { + throw new AbpInitializationException($"Failed to initialize audit log index template, the error: {errorBuilder.ToString()}"); + } + + Logger.LogWarning("Failed to initialize index and audit log may not be retrieved."); + Logger.LogWarning("The error: {error}", errorBuilder.ToString()); } } - protected async virtual Task InitlizeSecurityLogIndex(ElasticsearchClient client, string dateTimeFormat) + protected async virtual Task InitlizeSecurityLogIndexTemplate(ElasticsearchClient client, string dateTimeFormat, CancellationToken cancellationToken = default) { var indexName = _nameNormalizer.NormalizeIndex("security-log"); - var indexExists = await client.Indices.ExistsAsync(indexName); - if (!indexExists.Exists) + var indexPatterns = new[] { indexName + "-*" }; + var indexTemplateName = indexName + "-generic"; + + var indexTemplateExists = await client.Indices.ExistsIndexTemplateAsync(indexTemplateName, cancellationToken); + if (indexTemplateExists.Exists) + { + return; + } + + var putTemplateResponse = await client.Indices.PutIndexTemplateAsync(indexTemplateName, setup => { - var indexCreateResponse = await client.Indices.CreateAsync(indexName, c => + setup.IndexPatterns(indexPatterns); + setup.Priority(100); + setup.Version(1); + setup.Template(template => { - c.Settings(_elasticsearchOptions.SecurityLogSettings); - c.Mappings(mp => + template.Settings(_elasticsearchOptions.SecurityLogSettings); + template.Mappings(mp => { mp.Dynamic(DynamicMapping.False); mp.Properties(pd => @@ -173,16 +215,33 @@ public class IndexInitializer : IIndexInitializer, ISingletonDependency }); }); }); - if (!indexCreateResponse.IsValidResponse) + }, cancellationToken); + + if (!putTemplateResponse.IsValidResponse) + { + var errorBuilder = new StringBuilder(); + if (putTemplateResponse.TryGetOriginalException(out var ex)) { - if (indexCreateResponse.TryGetOriginalException(out var ex)) - { - Logger.LogWarning(ex, "Failed to initialize index and audit log may not be retrieved."); - return; - } - Logger.LogWarning("Failed to initialize index and audit log may not be retrieved."); - Logger.LogWarning(indexCreateResponse.DebugInformation); + errorBuilder.AppendLine(ex.Message); + Logger.LogWarning(ex, "Failed to initialize index and security log may not be retrieved."); + return; } + else if (putTemplateResponse.TryGetElasticsearchServerError(out var error)) + { + errorBuilder.AppendLine(error.ToString()); + } + else + { + errorBuilder.AppendLine(putTemplateResponse.DebugInformation); + } + + if (_elasticsearchOptions.ThrowIfIndexInitFailed) + { + throw new AbpInitializationException($"Failed to initialize security log index template, the error: {errorBuilder.ToString()}"); + } + + Logger.LogWarning("Failed to initialize index and security log may not be retrieved."); + Logger.LogWarning("The error: {error}", errorBuilder.ToString()); } } } diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IAuditLoggingIndexInitializer.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IAuditLoggingIndexInitializer.cs new file mode 100644 index 000000000..844df85ca --- /dev/null +++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IAuditLoggingIndexInitializer.cs @@ -0,0 +1,9 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AuditLogging.Elasticsearch; + +public interface IAuditLoggingIndexInitializer +{ + Task InitializeAsync(CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IIndexInitializer.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IIndexInitializer.cs deleted file mode 100644 index 9b364543d..000000000 --- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IIndexInitializer.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Threading.Tasks; - -namespace LINGYUN.Abp.AuditLogging.Elasticsearch; - -public interface IIndexInitializer -{ - Task InitializeAsync(); -} diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializerService.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializerService.cs deleted file mode 100644 index e75d762c2..000000000 --- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/IndexInitializerService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.Hosting; -using System.Threading; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.AuditLogging.Elasticsearch; - -public class IndexInitializerService : BackgroundService -{ - private readonly IIndexInitializer _indexInitializer; - - public IndexInitializerService(IIndexInitializer indexInitializer) - { - _indexInitializer = indexInitializer; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - await _indexInitializer.InitializeAsync(); - } -}