diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogExpressionQueryTranslator.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogExpressionQueryTranslator.cs
deleted file mode 100644
index 2a5847043..000000000
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogExpressionQueryTranslator.cs
+++ /dev/null
@@ -1,374 +0,0 @@
-using Elastic.Clients.Elasticsearch;
-using Elastic.Clients.Elasticsearch.QueryDsl;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using Volo.Abp;
-
-namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
-
-///
-/// ES DSL表达式翻译器:把 翻译为
-/// Elastic.Clients.Elasticsearch 的 (QueryDsl)。
-///
-/// 支持的算子(超出即抛 ,fail loud):
-///
-/// - 逻辑:&&、||、!(映射为 bool filter / should+minimum_should_match / must_not)
-/// - 比较:==、!=、>、>=、<、<=(数值与日期映射为 term / range)
-/// - null 判断:字段 == null / != null(映射为 must_not exists / exists)
-/// - 字符串:Contains / StartsWith / EndsWith(映射为 wildcard)、Equals(映射为 term)
-/// - 集合:x.Actions.Any(predicate)(映射为 nested 查询或扁平字段展开)
-/// - 常量:true / false(映射为 match_all / match_none)
-///
-///
-///
-internal class AuditLogExpressionQueryTranslator
-{
- private bool _actionsIsNested;
- private bool _caseInsensitiveWildcard;
- private bool _appendKeywordForStringEquality;
-
- public AuditLogExpressionQueryTranslator(
- bool actionsIsNested = false,
- bool caseInsensitiveWildcard = true,
- bool appendKeywordForStringEquality = true)
- {
- _actionsIsNested = actionsIsNested;
- _caseInsensitiveWildcard = caseInsensitiveWildcard;
- _appendKeywordForStringEquality = appendKeywordForStringEquality;
- }
-
- public Query Translate(Expression> expression)
- {
- Check.NotNull(expression, nameof(expression));
-
- return TranslateNode(expression.Body, prefix: null);
- }
-
- private Query TranslateNode(Expression node, string? prefix)
- {
- return node switch
- {
- ConstantExpression { Value: bool value } =>
- value ? new MatchAllQuery() : new MatchNoneQuery(),
- UnaryExpression { NodeType: ExpressionType.Not } unary =>
- (!TranslateNode(unary.Operand, prefix))!,
- UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary =>
- TranslateNode(unary.Operand, prefix),
- BinaryExpression binary => TranslateBinary(binary, prefix),
- MethodCallExpression method => TranslateMethodCall(method, prefix),
- MemberExpression member when member.Type == typeof(bool) =>
- new TermQuery { Field = ResolveField(member, prefix).Path, Value = true },
- _ => throw new NotSupportedException($"Unsupported expression node {node.NodeType}: {node}"),
- };
- }
-
- private Query TranslateBinary(BinaryExpression node, string? prefix)
- {
- return node.NodeType switch
- {
- ExpressionType.AndAlso or ExpressionType.And => (Query)new BoolQuery
- {
- Filter = new Query[] { TranslateNode(node.Left, prefix), TranslateNode(node.Right, prefix) },
- }!,
- ExpressionType.OrElse or ExpressionType.Or => (Query)new BoolQuery
- {
- Should = new Query[] { TranslateNode(node.Left, prefix), TranslateNode(node.Right, prefix) },
- MinimumShouldMatch = 1,
- }!,
- ExpressionType.Equal => TranslateComparison(node, prefix),
- ExpressionType.NotEqual => (!TranslateComparison(node, prefix))!,
- ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual or ExpressionType.LessThan or ExpressionType.LessThanOrEqual => TranslateComparison(node, prefix),
- _ => throw new NotSupportedException($"Unsupported binary operator {node.NodeType}: {node}"),
- };
- }
-
- private Query TranslateComparison(BinaryExpression node, string? prefix)
- {
- var (fieldExpression, valueExpression) = ResolveOperands(node);
- var field = ResolveField(fieldExpression, prefix);
-
- if (IsNullConstant(valueExpression))
- {
- return new BoolQuery
- {
- MustNot = new Query[] { new ExistsQuery { Field = field.Path } },
- };
- }
-
- var value = Evaluate(valueExpression);
-
- return value == null
- ? throw new NotSupportedException("The null value is only supported for the == null / != null comparison.")
- : node.NodeType switch
- {
- ExpressionType.Equal => BuildEquality(field, value),
- ExpressionType.GreaterThan => BuildRange(field, greaterThan: value),
- ExpressionType.GreaterThanOrEqual => BuildRange(field, greaterThanOrEqualTo: value),
- ExpressionType.LessThan => BuildRange(field, lessThan: value),
- ExpressionType.LessThanOrEqual => BuildRange(field, lessThanOrEqualTo: value),
- _ => throw new NotSupportedException($"Unsupported comparison operator {node.NodeType}"),
- };
- }
-
- private static (Expression Field, Expression Value) ResolveOperands(BinaryExpression node)
- {
- var leftIsField = IsFieldLike(node.Left);
- var rightIsField = IsFieldLike(node.Right);
- if (leftIsField == rightIsField)
- {
- throw new NotSupportedException($"The comparison expression must have one side as a field and the other side as a value: {node}");
- }
- return leftIsField ? (node.Left, node.Right) : (node.Right, node.Left);
- }
-
- private static bool IsFieldLike(Expression expression)
- {
- var current = expression;
- while (current is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary)
- {
- current = unary.Operand;
- }
-
- while (current is MemberExpression member)
- {
- current = member.Expression!;
- }
-
- return current is ParameterExpression;
- }
-
- private static bool IsNullConstant(Expression expression)
- {
- while (expression is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary)
- {
- expression = unary.Operand;
- }
-
- return expression is ConstantExpression { Value: null };
- }
-
- private Query BuildEquality(FieldRef field, object value)
- {
- if (field.Type == typeof(string))
- {
- var fieldName = _appendKeywordForStringEquality
- ? field.Path + ".keyword"
- : field.Path;
- return new TermQuery
- {
- Field = fieldName,
- Value = (string)value,
- CaseInsensitive = _caseInsensitiveWildcard,
- };
- }
-
- if (field.Type == typeof(DateTime) || field.Type == typeof(DateTime?))
- {
- var date = (DateTime)value;
- return new DateRangeQuery
- {
- Field = field.Path,
- Gte = date,
- Lte = date,
- };
- }
-
- return new TermQuery { Field = field.Path, Value = NormalizeValue(value) };
- }
-
- private static Query BuildRange(
- FieldRef field,
- object? greaterThan = null,
- object? greaterThanOrEqualTo = null,
- object? lessThan = null,
- object? lessThanOrEqualTo = null)
- {
- if (field.Type == typeof(DateTime) || field.Type == typeof(DateTime?))
- {
- var range = new DateRangeQuery { Field = field.Path };
- if (greaterThan != null)
- {
- range.Gt = (DateTime)greaterThan;
- }
-
- if (greaterThanOrEqualTo != null)
- {
- range.Gte = (DateTime)greaterThanOrEqualTo;
- }
-
- if (lessThan != null)
- {
- range.Lt = (DateTime)lessThan;
- }
-
- if (lessThanOrEqualTo != null)
- {
- range.Lte = (DateTime)lessThanOrEqualTo;
- }
-
- return range;
- }
-
- var numberRange = new NumberRangeQuery { Field = field.Path };
- if (greaterThan != null)
- {
- numberRange.Gt = ToNumber(greaterThan);
- }
-
- if (greaterThanOrEqualTo != null)
- {
- numberRange.Gte = ToNumber(greaterThanOrEqualTo);
- }
-
- if (lessThan != null)
- {
- numberRange.Lt = ToNumber(lessThan);
- }
-
- if (lessThanOrEqualTo != null)
- {
- numberRange.Lte = ToNumber(lessThanOrEqualTo);
- }
-
- return numberRange;
- }
-
- private Query BuildWildcard(string fieldName, string pattern)
- {
- return new WildcardQuery
- {
- Field = fieldName,
- Value = pattern,
- CaseInsensitive = _caseInsensitiveWildcard,
- };
- }
-
- private Query TranslateMethodCall(MethodCallExpression node, string? prefix)
- {
- if (node.Method.DeclaringType == typeof(Enumerable) && node.Method.Name == nameof(Enumerable.Any))
- {
- var collectionField = ResolveField(node.Arguments[0], prefix);
-
- Query inner;
- if (node.Arguments.Count == 1)
- {
- inner = new ExistsQuery { Field = collectionField.Path };
- }
- else
- {
- var predicate = UnwrapLambda(node.Arguments[1]);
- inner = TranslateNode(predicate.Body, prefix: collectionField.Path);
- }
-
- return _actionsIsNested
- ? new NestedQuery(collectionField.Path, inner)
- : inner;
- }
-
- if (node.Method.DeclaringType == typeof(string) && node.Object != null)
- {
- var field = ResolveField(node.Object, prefix);
- var value = (string)Evaluate(node.Arguments[0])!;
- return node.Method.Name switch
- {
- nameof(string.Contains) => BuildWildcard(field.Path, "*" + EscapeWildcard(value) + "*"),
- nameof(string.StartsWith) => BuildWildcard(field.Path, EscapeWildcard(value) + "*"),
- nameof(string.EndsWith) => BuildWildcard(field.Path, "*" + EscapeWildcard(value)),
- _ => throw new NotSupportedException($"Unsupported string method {node.Method.Name}"),
- };
- }
-
- if (node.Method.Name == nameof(string.Equals))
- {
- var fieldExpression = node.Object ?? node.Arguments[0];
- var valueExpression = node.Object != null ? node.Arguments[0] : node.Arguments[1];
- var field = ResolveField(fieldExpression, prefix);
- return BuildEquality(field, (string)Evaluate(valueExpression)!);
- }
-
- throw new NotSupportedException(
- $"Unsupported method invocation {node.Method.DeclaringType?.Name}.{node.Method.Name}");
- }
-
- private static LambdaExpression UnwrapLambda(Expression expression)
- {
- while (expression is UnaryExpression { NodeType: ExpressionType.Quote } unary)
- {
- expression = unary.Operand;
- }
- return (LambdaExpression)expression;
- }
-
- private static object? Evaluate(Expression expression)
- {
- return expression is ConstantExpression constant
- ? constant.Value
- : Expression.Lambda(expression).Compile().DynamicInvoke();
- }
-
- private static string EscapeWildcard(string value)
- {
- return value.Replace("\\", "\\\\").Replace("*", "\\*").Replace("?", "\\?");
- }
-
- private static FieldValue NormalizeValue(object value)
- {
- return value switch
- {
- string s => s,
- bool b => b,
- int i => i,
- long l => l,
- double d => d,
- Guid g => g.ToString(),
- Enum e => Convert.ToInt64(e),
- _ => Convert.ToDouble(value),
- };
- }
-
- private static Number? ToNumber(object? value)
- {
- if (value == null)
- {
- return null;
- }
- return value is double d ? d : Convert.ToInt64(value);
- }
-
- private readonly record struct FieldRef(string Path, Type Type);
-
- private static FieldRef ResolveField(Expression expression, string? prefix)
- {
- var current = expression;
- while (current is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary)
- {
- current = unary.Operand;
- }
-
- var names = new Stack();
- while (current is MemberExpression member)
- {
- names.Push(member.Member.Name);
- current = member.Expression!;
- }
-
- if (current is not ParameterExpression)
- {
- throw new NotSupportedException($"Unable to parse as field path: {expression}");
- }
-
- var path = string.Join(".", names);
- if (!string.IsNullOrEmpty(prefix) && path.Length > 0)
- {
- path = prefix + "." + path;
- }
- else if (path.Length == 0)
- {
- path = prefix ?? string.Empty;
- }
-
- return new FieldRef(path, expression.Type);
- }
-}
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs
index 6a51f1a42..16c1c2aea 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs
+++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLoggingIndexInitializer.cs
@@ -134,7 +134,7 @@ public class AuditLoggingIndexInitializer : IAuditLoggingIndexInitializer, ISing
npd.Keyword(nameof(AuditLogAction.AuditLogId), p => p.IgnoreAbove(36));
npd.Text(nameof(AuditLogAction.ServiceName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256))));
npd.Text(nameof(AuditLogAction.MethodName), p => p.Fields(f => f.Keyword("keyword", k => k.IgnoreAbove(256))));
- npd.Text(nameof(AuditLogAction.Parameters), p => p.Norms(false).IndexOptions(IndexOptions.Docs));
+ npd.Wildcard(nameof(AuditLogAction.Parameters));
npd.Date(nameof(AuditLogAction.ExecutionTime), d => d.Format(dateTimeFormat));
npd.IntegerNumber(nameof(AuditLogAction.ExecutionDuration));
npd.Flattened(nameof(AuditLogAction.ExtraProperties), f => f.DepthLimit(5).EagerGlobalOrdinals(false));
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs
index f7e7b3f42..1cbff1dfd 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs
+++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs
@@ -1,10 +1,8 @@
using Elastic.Clients.Elasticsearch;
-using Elastic.Clients.Elasticsearch.Mapping;
using Elastic.Clients.Elasticsearch.QueryDsl;
using LINGYUN.Abp.Elasticsearch;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -20,23 +18,26 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
[Dependency(ReplaceServices = true)]
public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependency
{
- private readonly AbpElasticsearchOptions _elasticsearchOptions;
private readonly IIndexNameNormalizer _indexNameNormalizer;
private readonly IElasticsearchClientFactory _clientFactory;
+ private readonly IIndexMappingProvider _indexMappingProvider;
+ private readonly IExpressionQueryService _expressionQueryService;
private readonly IClock _clock;
public ILogger Logger { protected get; set; }
public ElasticsearchAuditLogManager(
IClock clock,
+ IElasticsearchClientFactory clientFactory,
IIndexNameNormalizer indexNameNormalizer,
- IOptions elasticsearchOptions,
- IElasticsearchClientFactory clientFactory)
+ IIndexMappingProvider indexMappingProvider,
+ IExpressionQueryService expressionQueryService)
{
_clock = clock;
_clientFactory = clientFactory;
- _elasticsearchOptions = elasticsearchOptions.Value;
_indexNameNormalizer = indexNameNormalizer;
+ _indexMappingProvider = indexMappingProvider;
+ _expressionQueryService = expressionQueryService;
Logger = NullLogger.Instance;
}
@@ -45,16 +46,12 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
ISpecification specification,
CancellationToken cancellationToken = default)
{
- var client = _clientFactory.Create();
- var actionsIsNested = await GetActionsIsNested(client, cancellationToken);
- var translator = new AuditLogExpressionQueryTranslator(actionsIsNested);
- var query = translator.Translate(specification.ToExpression());
+ var indexName = CreateIndex();
- var response = await client.CountAsync(dsl =>
- dsl.Indices(CreateIndex()).Query(query),
+ return await _expressionQueryService.GetCountAsync(
+ indexName,
+ specification.ToExpression(),
cancellationToken);
-
- return response.Count;
}
public async virtual Task> GetListAsync(
@@ -65,21 +62,41 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
- var client = _clientFactory.Create();
- var actionsIsNested = await GetActionsIsNested(client, cancellationToken);
- var translator = new AuditLogExpressionQueryTranslator(actionsIsNested);
- var query = translator.Translate(specification.ToExpression());
+ var indexName = CreateIndex();
- var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
- ? SortOrder.Asc : SortOrder.Desc;
- sorting = !sorting.IsNullOrWhiteSpace()
- ? sorting.Split()[0]
- : nameof(AuditLog.ExecutionTime);
- // ES最大支持10000, 超出这个长度后升级为使用Search_After方案
+ var sortingField = sorting;
+ if (sortingField.IsNullOrWhiteSpace())
+ {
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
+ if (indexMapping != null)
+ {
+ var sortingFieldMap = indexMapping.Fields
+ .Where(x => x.Key.Equals(sortingField, StringComparison.CurrentCultureIgnoreCase))
+ .Select(x => x.Value)
+ .FirstOrDefault();
+ if (sortingFieldMap != null)
+ {
+ sortingField = sortingFieldMap.Path;
+ }
+ }
+ }
- return skipCount >= 10000
- ? await SearchAfterAuditLogs(client, query, sorting, sortOrder, maxResultCount, skipCount, includeDetails, cancellationToken)
- : await SearchFromSizeAuditLogs(client, query, sorting, sortOrder, maxResultCount, skipCount, includeDetails, cancellationToken);
+ return await _expressionQueryService.GetListAsync(
+ indexName,
+ specification.ToExpression(),
+ sortingField,
+ maxResultCount,
+ skipCount,
+ sourceExcludes: includeDetails == true
+ ? Fields.FromFields(
+ [
+ new Field("Actions"),
+ new Field("Comments"),
+ new Field("EntityChanges"),
+ new Field("Exceptions"),
+ ])
+ : null,
+ cancellationToken: cancellationToken);
}
public async virtual Task GetCountAsync(
@@ -99,9 +116,12 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
HttpStatusCode? httpStatusCode = null,
CancellationToken cancellationToken = default)
{
+ var indexName = CreateIndex();
var client = _clientFactory.Create();
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
var querys = BuildQueryDescriptor(
+ indexMapping,
startTime,
endTime,
httpMethod,
@@ -118,7 +138,7 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
httpStatusCode);
var response = await client.CountAsync(dsl =>
- dsl.Indices(CreateIndex())
+ dsl.Indices(indexName)
.Query(new BoolQuery
{
Must = querys
@@ -149,15 +169,14 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
+ var indexName = CreateIndex();
var client = _clientFactory.Create();
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
- var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
- ? SortOrder.Asc : SortOrder.Desc;
- sorting = !sorting.IsNullOrWhiteSpace()
- ? sorting.Split()[0]
- : nameof(AuditLog.ExecutionTime);
+ var sorts = GetOrDefaultSort(indexMapping, sorting);
var querys = BuildQueryDescriptor(
+ indexMapping,
startTime,
endTime,
httpMethod,
@@ -176,9 +195,25 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
var query = new BoolQuery { Must = querys };
// ES最大支持10000, 超出这个长度后升级为使用Search_After方案
- return skipCount >= 10000
- ? await SearchAfterAuditLogs(client, query, sorting, sortOrder, maxResultCount, skipCount, includeDetails, cancellationToken)
- : await SearchFromSizeAuditLogs(client, query, sorting, sortOrder, maxResultCount, skipCount, includeDetails, cancellationToken);
+ return skipCount >= 10000 && sorts != null
+ ? await SearchAfterAuditLogs(
+ client,
+ indexName,
+ query,
+ sorts,
+ maxResultCount,
+ skipCount,
+ includeDetails,
+ cancellationToken)
+ : await SearchFromSizeAuditLogs(
+ client,
+ indexName,
+ query,
+ sorts,
+ maxResultCount,
+ skipCount,
+ includeDetails,
+ cancellationToken);
}
public async virtual Task GetAsync(
@@ -232,6 +267,7 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
}
protected virtual List BuildQueryDescriptor(
+ IndexMappingInfo indexMappingInfo,
DateTime? startTime = null,
DateTime? endTime = null,
string? httpMethod = null,
@@ -251,63 +287,63 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
if (startTime.HasValue)
{
- queries.Add(new DateRangeQuery(GetField(nameof(AuditLog.ExecutionTime)))
+ queries.Add(new DateRangeQuery(GetField(indexMappingInfo, nameof(AuditLog.ExecutionTime)))
{
Gte = _clock.Normalize(startTime.Value)
});
}
if (endTime.HasValue)
{
- queries.Add(new DateRangeQuery(GetField(nameof(AuditLog.ExecutionTime)))
+ queries.Add(new DateRangeQuery(GetField(indexMappingInfo, nameof(AuditLog.ExecutionTime)))
{
Lte = _clock.Normalize(endTime.Value)
});
}
if (!httpMethod.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.HttpMethod)), httpMethod));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.HttpMethod)), httpMethod));
}
if (!url.IsNullOrWhiteSpace())
{
- queries.Add(new WildcardQuery(GetField(nameof(AuditLog.Url)))
+ queries.Add(new WildcardQuery(GetField(indexMappingInfo, nameof(AuditLog.Url)))
{
Value = $"*{url}*"
});
}
if (userId.HasValue)
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.UserId)), userId.Value.ToString()));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.UserId)), userId.Value.ToString()));
}
if (!userName.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.UserName)), userName));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.UserName)), userName));
}
if (!applicationName.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.ApplicationName)), applicationName));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.ApplicationName)), applicationName));
}
if (!correlationId.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.CorrelationId)), correlationId));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.CorrelationId)), correlationId));
}
if (!clientId.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.ClientId)), clientId));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.ClientId)), clientId));
}
if (!clientIpAddress.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.ClientIpAddress)), clientIpAddress));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.ClientIpAddress)), clientIpAddress));
}
if (maxExecutionDuration.HasValue)
{
- queries.Add(new NumberRangeQuery(GetField(nameof(AuditLog.ExecutionDuration)))
+ queries.Add(new NumberRangeQuery(GetField(indexMappingInfo, nameof(AuditLog.ExecutionDuration)))
{
Lte = maxExecutionDuration.Value
});
}
if (minExecutionDuration.HasValue)
{
- queries.Add(new NumberRangeQuery(GetField(nameof(AuditLog.ExecutionDuration)))
+ queries.Add(new NumberRangeQuery(GetField(indexMappingInfo, nameof(AuditLog.ExecutionDuration)))
{
Gte = minExecutionDuration.Value
});
@@ -318,7 +354,7 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
{
if (hasException.Value)
{
- queries.Add(new ExistsQuery(GetField("Exceptions")));
+ queries.Add(new ExistsQuery(GetField(indexMappingInfo, nameof(AuditLog.Exceptions))));
}
else
{
@@ -326,7 +362,7 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
{
MustNot = new List
{
- new ExistsQuery(GetField("Exceptions"))
+ new ExistsQuery(GetField(indexMappingInfo, nameof(AuditLog.Exceptions)))
}
});
}
@@ -334,53 +370,32 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
if (httpStatusCode.HasValue)
{
- queries.Add(new TermQuery(GetField(nameof(AuditLog.HttpStatusCode)), ((int)httpStatusCode.Value).ToString()));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, nameof(AuditLog.HttpStatusCode)), ((int)httpStatusCode.Value).ToString()));
}
return queries;
}
- private async Task GetActionsIsNested(ElasticsearchClient client, CancellationToken cancellationToken = default)
- {
- var actionsIsNested = false;
-
- var response = await client.Indices.GetMappingAsync(
- d => d.Indices(CreateIndex()),
- cancellationToken);
-
- foreach (var mapping in response.Mappings)
- {
- if (mapping.Value.Mappings?.Properties is IDictionary properties &&
- properties.TryGetValue("Actions", out var actionsProperty))
- {
- actionsIsNested = actionsProperty is NestedProperty;
- break;
- }
- }
-
- return actionsIsNested;
- }
-
private async Task> SearchFromSizeAuditLogs(
ElasticsearchClient client,
+ string indexName,
Query query,
- string sorting,
- SortOrder sortOrder,
- int maxResultCount,
- int skipCount,
+ SortOptions[]? sorts = null,
+ int maxResultCount = 50,
+ int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var searchResponse = await client.SearchAsync(dsl =>
{
- dsl.Indices(CreateIndex())
+ dsl.Indices(indexName)
.Query(query)
- .Sort(s => s.Field(new FieldSort(GetField(sorting))
- {
- Order = sortOrder
- }))
.From(skipCount)
.Size(maxResultCount);
+ if (sorts != null)
+ {
+ dsl.Sort(sorts);
+ }
if (!includeDetails)
{
@@ -402,19 +417,19 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
private async Task> SearchAfterAuditLogs(
ElasticsearchClient client,
+ string indexName,
Query query,
- string sorting,
- SortOrder sortOrder,
- int maxResultCount,
- int skipCount,
+ SortOptions[] sorts,
+ int maxResultCount = 50,
+ int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var searchAfter = await GetSearchAfterValue(
client,
+ indexName,
query,
- sorting,
- sortOrder,
+ sorts,
skipCount,
cancellationToken);
@@ -425,12 +440,9 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
var searchResponse = await client.SearchAsync(dsl =>
{
- dsl.Indices(CreateIndex())
+ dsl.Indices(indexName)
.Query(query)
- .Sort(s => s.Field(new FieldSort(GetField(sorting))
- {
- Order = sortOrder
- }))
+ .Sort(sorts)
.Size(maxResultCount)
.SearchAfter(searchAfter);
@@ -454,9 +466,9 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
private async Task?> GetSearchAfterValue(
ElasticsearchClient client,
+ string indexName,
Query query,
- string sorting,
- SortOrder sortOrder,
+ SortOptions[] sorts,
int skipCount,
CancellationToken cancellationToken = default)
{
@@ -464,12 +476,9 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
if (skipCount < 10000)
{
var response = await client.SearchAsync(
- dsl => dsl.Indices(CreateIndex())
+ dsl => dsl.Indices(indexName)
.Query(query)
- .Sort(s => s.Field(new FieldSort(GetField(sorting))
- {
- Order = sortOrder
- }))
+ .Sort(sorts)
.SourceIncludes(x => x.Id)
.From(skipCount)
.Size(1),
@@ -486,12 +495,9 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
// 获取第9999条数据Hits作为searchAfter
var firstResponse = await client.SearchAsync(
- dsl => dsl.Indices(CreateIndex())
+ dsl => dsl.Indices(indexName)
.Query(query)
- .Sort(s => s.Field(new FieldSort(GetField(sorting))
- {
- Order = sortOrder
- }))
+ .Sort(sorts)
.SourceIncludes(x => x.Id)
.From(9999)
.Size(1),
@@ -511,12 +517,9 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
var remaining = skipCount - 10000;
// 获取skipCount最近一条数据作为searchAfter
var secondResponse = await client.SearchAsync(
- dsl => dsl.Indices(CreateIndex())
+ dsl => dsl.Indices(indexName)
.Query(query)
- .Sort(s => s.Field(new FieldSort(GetField(sorting))
- {
- Order = sortOrder
- }))
+ .Sort(sorts)
.SourceIncludes(x => x.Id)
.SearchAfter(firstHit.Sort.ToList())
.Size(remaining),
@@ -546,35 +549,45 @@ public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependen
return _indexNameNormalizer.NormalizeIndex("audit-log");
}
- protected virtual string GetField(string field)
+ private static SortOptions[]? GetOrDefaultSort(IndexMappingInfo indexMappingInfo, string? sorting = null)
{
- if (_auditLogFieldMaps.TryGetValue(field, out var mapField))
+ var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
+ ? SortOrder.Asc : SortOrder.Desc;
+ sorting = !sorting.IsNullOrWhiteSpace()
+ ? sorting.Split()[0]
+ : nameof(AuditLog.ExecutionTime);
+
+ SortOptions[]? sorts = null;
+ if (sorting.IsNullOrWhiteSpace())
{
- return _elasticsearchOptions.FieldCamelCase ? mapField.ToCamelCase() : mapField.ToPascalCase();
+ var sortingFieldMap = indexMappingInfo.Fields
+ .Where(x => x.Key.Equals(sorting, StringComparison.CurrentCultureIgnoreCase))
+ .Select(x => x.Value)
+ .FirstOrDefault();
+ if (sortingFieldMap != null)
+ {
+ sorting = sortingFieldMap.Path;
+ }
+ if (!sorting.IsNullOrWhiteSpace())
+ {
+ sorts = new SortOptions[1]
+ {
+ new SortOptions
+ {
+ Field = new FieldSort(new Field(sorting))
+ {
+ Order = sortOrder,
+ },
+ }
+ };
+ }
}
- return _elasticsearchOptions.FieldCamelCase ? field.ToCamelCase() : field.ToPascalCase();
+ return sorts;
}
- private readonly static IDictionary _auditLogFieldMaps = new Dictionary(StringComparer.InvariantCultureIgnoreCase)
+ private static string GetField(IndexMappingInfo indexMappingInfo, string fieldFullPath)
{
- { "Id", "Id.keyword" },
- { "ApplicationName", "ApplicationName.keyword" },
- { "UserId", "UserId.keyword" },
- { "UserName", "UserName.keyword" },
- { "TenantId", "TenantId.keyword" },
- { "TenantName", "TenantName.keyword" },
- { "ImpersonatorUserId", "ImpersonatorUserId.keyword" },
- { "ImpersonatorTenantId", "ImpersonatorTenantId.keyword" },
- { "ClientName", "ClientName.keyword" },
- { "ClientIpAddress", "ClientIpAddress.keyword" },
- { "ClientId", "ClientId.keyword" },
- { "CorrelationId", "CorrelationId.keyword" },
- { "BrowserInfo", "BrowserInfo.keyword" },
- { "HttpMethod", "HttpMethod.keyword" },
- { "Url", "Url.keyword" },
- { "ExecutionDuration", "ExecutionDuration" },
- { "ExecutionTime", "ExecutionTime" },
- { "HttpStatusCode", "HttpStatusCode" },
- };
+ return indexMappingInfo.GetExactFieldPath(fieldFullPath);
+ }
}
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN.Abp.AuditLogging.EntityFrameworkCore.csproj b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN.Abp.AuditLogging.EntityFrameworkCore.csproj
index 8ca477253..2a9824260 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN.Abp.AuditLogging.EntityFrameworkCore.csproj
+++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN.Abp.AuditLogging.EntityFrameworkCore.csproj
@@ -20,6 +20,7 @@
+
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogManager.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogManager.cs
index e3b99b723..d282b7e46 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogManager.cs
+++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogManager.cs
@@ -1,4 +1,5 @@
-using System;
+using LINGYUN.Linq.Dynamic.Queryable;
+using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
@@ -15,6 +16,14 @@ namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
[Dependency(ReplaceServices = true)]
public class EfCoreAuditLogManager : IAuditLogManager, ITransientDependency
{
+ private readonly static Dictionary _defaultTypeMap = new Dictionary
+ {
+ [typeof(AuditLog)] = typeof(VoloAuditLog),
+ [typeof(AuditLogAction)] = typeof(Volo.Abp.AuditLogging.AuditLogAction),
+ [typeof(EntityChange)] = typeof(Volo.Abp.AuditLogging.EntityChange),
+ [typeof(EntityPropertyChange)] = typeof(Volo.Abp.AuditLogging.EntityPropertyChange),
+ };
+
protected IObjectMapper ObjectMapper { get; }
protected IEfCoreAuditLogRepository AuditLogRepository { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
@@ -33,7 +42,7 @@ public class EfCoreAuditLogManager : IAuditLogManager, ITransientDependency
ISpecification specification,
CancellationToken cancellationToken = default)
{
- var converter = new AuditLogExpressionQueryConverter();
+ var converter = new ExpressionQueryConverter(_defaultTypeMap);
var resetSpec = new ExpressionSpecification(
converter.Convert(specification.ToExpression()));
@@ -48,7 +57,7 @@ public class EfCoreAuditLogManager : IAuditLogManager, ITransientDependency
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
- var converter = new AuditLogExpressionQueryConverter();
+ var converter = new ExpressionQueryConverter(_defaultTypeMap);
var resetSpec = new ExpressionSpecification(
converter.Convert(specification.ToExpression()));
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/SecurityLogManager.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreSecurityLogManager.cs
similarity index 97%
rename from aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/SecurityLogManager.cs
rename to aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreSecurityLogManager.cs
index c3b94df0e..fb37d5e2a 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/SecurityLogManager.cs
+++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/EfCoreSecurityLogManager.cs
@@ -10,13 +10,13 @@ using Volo.Abp.Uow;
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
[Dependency(ReplaceServices = true)]
-public class SecurityLogManager : ISecurityLogManager, ITransientDependency
+public class EfCoreSecurityLogManager : ISecurityLogManager, ITransientDependency
{
protected IObjectMapper ObjectMapper { get; }
protected IIdentitySecurityLogRepository IdentitySecurityLogRepository { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
- public SecurityLogManager(
+ public EfCoreSecurityLogManager(
IObjectMapper objectMapper,
IIdentitySecurityLogRepository identitySecurityLogRepository,
IUnitOfWorkManager unitOfWorkManager)
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingModule.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingModule.cs
index 7fcdf53f9..e0dcf12a4 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingModule.cs
+++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging/LINGYUN/Abp/AuditLogging/AbpAuditLoggingModule.cs
@@ -9,6 +9,7 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
+using Volo.Abp.Specifications;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.AuditLogging;
@@ -16,6 +17,7 @@ namespace LINGYUN.Abp.AuditLogging;
[DependsOn(
typeof(AbpAuditingModule),
typeof(AbpGuidsModule),
+ typeof(AbpSpecificationsModule),
typeof(AbpExceptionHandlingModule))]
public class AbpAuditLoggingModule : AbpModule
{
diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/AuditLogExpressionQueryConverter.cs b/aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/LINGYUN/Linq/Dynamic/Queryable/ExpressionQueryConverter.cs
similarity index 75%
rename from aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/AuditLogExpressionQueryConverter.cs
rename to aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/LINGYUN/Linq/Dynamic/Queryable/ExpressionQueryConverter.cs
index 243ad5869..c2bff1748 100644
--- a/aspnet-core/framework/auditing/LINGYUN.Abp.AuditLogging.EntityFrameworkCore/LINGYUN/Abp/AuditLogging/EntityFrameworkCore/AuditLogExpressionQueryConverter.cs
+++ b/aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/LINGYUN/Linq/Dynamic/Queryable/ExpressionQueryConverter.cs
@@ -4,38 +4,31 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
-using VoloAuditLog = Volo.Abp.AuditLogging.AuditLog;
+namespace LINGYUN.Linq.Dynamic.Queryable;
-namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore;
-
-///
-/// 审计日志表达式树转换器
-///
-internal class AuditLogExpressionQueryConverter : ExpressionVisitor
+public class ExpressionQueryConverter : ExpressionVisitor
{
private readonly IReadOnlyDictionary _typeMap;
private readonly Dictionary _parameterMap = new();
- public AuditLogExpressionQueryConverter()
- : this(BuildDefaultTypeMap())
- {
- }
-
- public AuditLogExpressionQueryConverter(IReadOnlyDictionary typeMap)
+ public ExpressionQueryConverter(IReadOnlyDictionary typeMap)
{
_typeMap = typeMap ?? throw new ArgumentNullException(nameof(typeMap));
}
- public Expression> Convert(Expression> expression)
+ public Expression> Convert(Expression> expression)
{
- ArgumentNullException.ThrowIfNull(expression);
+ if (expression == null)
+ {
+ throw new ArgumentNullException(nameof(expression));
+ }
_parameterMap.Clear();
- var rootParameter = Expression.Parameter(typeof(VoloAuditLog), expression.Parameters[0].Name);
+ var rootParameter = Expression.Parameter(typeof(TTarget), expression.Parameters[0].Name);
_parameterMap[expression.Parameters[0]] = rootParameter;
var body = Visit(expression.Body);
- return Expression.Lambda>(body, rootParameter);
+ return Expression.Lambda>(body, rootParameter);
}
protected override Expression VisitLambda(Expression node)
@@ -115,6 +108,19 @@ internal class AuditLogExpressionQueryConverter : ExpressionVisitor
var operand = Visit(node.Operand);
return Expression.Quote(operand);
}
+
+ if (node.NodeType is ExpressionType.Convert or ExpressionType.ConvertChecked)
+ {
+ var operand = Visit(node.Operand);
+ var resultType = _typeMap.TryGetValue(node.Type, out var mappedType) ? mappedType : node.Type;
+
+ if (operand.Type == resultType)
+ {
+ return operand;
+ }
+ return Expression.Convert(operand, resultType);
+ }
+
return base.VisitUnary(node);
}
@@ -126,15 +132,4 @@ internal class AuditLogExpressionQueryConverter : ExpressionVisitor
}
return base.VisitConstant(node);
}
-
- private static Dictionary BuildDefaultTypeMap()
- {
- return new Dictionary
- {
- [typeof(AuditLog)] = typeof(VoloAuditLog),
- [typeof(AuditLogAction)] = typeof(Volo.Abp.AuditLogging.AuditLogAction),
- [typeof(EntityChange)] = typeof(Volo.Abp.AuditLogging.EntityChange),
- [typeof(EntityPropertyChange)] = typeof(Volo.Abp.AuditLogging.EntityPropertyChange),
- };
- }
}
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN.Abp.Logging.Serilog.Elasticsearch.csproj b/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN.Abp.Logging.Serilog.Elasticsearch.csproj
index c7b40a454..18c69ef0e 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN.Abp.Logging.Serilog.Elasticsearch.csproj
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN.Abp.Logging.Serilog.Elasticsearch.csproj
@@ -20,6 +20,7 @@
+
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN/Abp/AuditLogging/Serilog/Elasticsearch/SerilogElasticsearchLoggingManager.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN/Abp/AuditLogging/Serilog/Elasticsearch/SerilogElasticsearchLoggingManager.cs
index 9f392e58f..c1b1005d0 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN/Abp/AuditLogging/Serilog/Elasticsearch/SerilogElasticsearchLoggingManager.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging.Serilog.Elasticsearch/LINGYUN/Abp/AuditLogging/Serilog/Elasticsearch/SerilogElasticsearchLoggingManager.cs
@@ -1,8 +1,7 @@
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;
using LINGYUN.Abp.Elasticsearch;
-using LINGYUN.Abp.Serilog.Enrichers.Application;
-using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
+using LINGYUN.Linq.Dynamic.Queryable;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@@ -16,6 +15,7 @@ using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
+using Volo.Abp.Specifications;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.Logging.Serilog.Elasticsearch;
@@ -23,12 +23,21 @@ namespace LINGYUN.Abp.Logging.Serilog.Elasticsearch;
[Dependency(ReplaceServices = true)]
public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDependency
{
- private readonly static Regex IndexFormatRegex = new Regex(@"^(.*)(?:\{0\:.+\})(.*)$");
+ private readonly static Regex _indexFormatRegex = new Regex(@"^(.*)(?:\{0\:.+\})(.*)$");
+ private readonly static Dictionary _defaultTypeMap = new Dictionary
+ {
+ [typeof(LogInfo)] = typeof(SerilogInfo),
+ [typeof(LogLevel)] = typeof(string),
+ [typeof(LogField)] = typeof(SerilogField),
+ [typeof(LogException)] = typeof(SerilogException),
+ };
private readonly IClock _clock;
private readonly ICurrentTenant _currentTenant;
private readonly AbpLoggingSerilogElasticsearchOptions _options;
private readonly IElasticsearchClientFactory _clientFactory;
+ private readonly IIndexMappingProvider _indexMappingProvider;
+ private readonly IExpressionQueryService _expressionQueryService;
private readonly IObjectMapper _objectMapper;
public ILogger Logger { protected get; set; }
@@ -38,17 +47,74 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
ICurrentTenant currentTenant,
IOptions options,
IElasticsearchClientFactory clientFactory,
+ IIndexMappingProvider indexMappingProvider,
+ IExpressionQueryService expressionQueryService,
IObjectMapper objectMapper)
{
_clock = clock;
_objectMapper = objectMapper;
_currentTenant = currentTenant;
_clientFactory = clientFactory;
+ _indexMappingProvider = indexMappingProvider;
+ _expressionQueryService = expressionQueryService;
_options = options.Value;
Logger = NullLogger.Instance;
}
+ public async virtual Task GetCountAsync(
+ ISpecification specification,
+ CancellationToken cancellationToken = default)
+ {
+ var indexName = CreateIndex();
+ var converter = new ExpressionQueryConverter(_defaultTypeMap);
+ var expression = converter.Convert(specification.ToExpression());
+
+ return await _expressionQueryService.GetCountAsync(
+ indexName,
+ expression,
+ cancellationToken);
+ }
+
+ public async virtual Task> GetListAsync(
+ ISpecification specification,
+ string? sorting = null,
+ int maxResultCount = 50,
+ int skipCount = 0,
+ CancellationToken cancellationToken = default)
+ {
+ var indexName = CreateIndex();
+ var converter = new ExpressionQueryConverter(_defaultTypeMap);
+ var expression = converter.Convert(specification.ToExpression());
+
+ var sortingField = sorting;
+ if (sortingField.IsNullOrWhiteSpace())
+ {
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
+ if (indexMapping != null)
+ {
+ var sortingFieldMap = indexMapping.Fields
+ .Where(x => x.Key.Equals(sortingField, StringComparison.CurrentCultureIgnoreCase))
+ .Select(x => x.Value)
+ .FirstOrDefault();
+ if (sortingFieldMap != null)
+ {
+ sortingField = sortingFieldMap.Path;
+ }
+ }
+ }
+
+ var serilogLogs = await _expressionQueryService.GetListAsync(
+ indexName,
+ expression,
+ sortingField,
+ maxResultCount,
+ skipCount,
+ cancellationToken: cancellationToken);
+
+ return _objectMapper.Map, List>(serilogLogs);
+ }
+
///
///
///
@@ -59,7 +125,9 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
string id,
CancellationToken cancellationToken = default)
{
+ var indexName = CreateIndex();
var client = _clientFactory.Create();
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
SearchResponse response;
@@ -94,9 +162,9 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
(q) => q.Bool(
(b) => b.Must(
(s) => s.Term(
- (t) => t.Field(GetField(nameof(SerilogInfo.Fields.UniqueId))).Value(id)),
+ (t) => t.Field(GetField(indexMapping, "fields.UniqueId")).Value(id)),
(s) => s.Term(
- (t) => t.Field(GetField(nameof(SerilogInfo.Fields.TenantId))).Value(_currentTenant.GetId().ToString())))))
+ (t) => t.Field(GetField(indexMapping, "fields.TenantId")).Value(_currentTenant.GetId().ToString())))))
.Size(1),
cancellationToken);
}
@@ -124,7 +192,7 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
(q) => q.Bool(
(b) => b.Must(
(s) => s.Term(
- (t) => t.Field(GetField(nameof(SerilogInfo.Fields.UniqueId))).Value(id)))))
+ (t) => t.Field(GetField(indexMapping, "fields.UniqueId")).Value(id)))))
.Size(1),
cancellationToken);
}
@@ -148,9 +216,12 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
bool? hasException = null,
CancellationToken cancellationToken = default)
{
+ var indexName = CreateIndex();
var client = _clientFactory.Create();
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
var querys = BuildQueryDescriptor(
+ indexMapping,
startTime,
endTime,
level,
@@ -166,7 +237,7 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
hasException);
var response = await client.CountAsync((dsl) =>
- dsl.Indices(CreateIndex())
+ dsl.Indices(indexName)
.Query(log => log.Bool(b => b.Must(querys.ToArray()))),
cancellationToken);
@@ -215,15 +286,14 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
+ var indexName = CreateIndex();
var client = _clientFactory.Create();
+ var indexMapping = await _indexMappingProvider.GetMappingAsync(indexName, cancellationToken);
- var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
- ? SortOrder.Asc : SortOrder.Desc;
- sorting = !sorting.IsNullOrWhiteSpace()
- ? sorting.Split()[0]
- : nameof(SerilogInfo.TimeStamp);
+ var sorts = GetOrDefaultSort(indexMapping, sorting);
var querys = BuildQueryDescriptor(
+ indexMapping,
startTime,
endTime,
level,
@@ -238,21 +308,17 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
threadId,
hasException);
- var response = await client.SearchAsync((dsl) =>
- dsl.Indices(CreateIndex())
- .Query(log =>
- log.Bool(b =>
- b.Must(querys.ToArray())))
- .SourceExcludes(se => se.Exceptions)
- .Sort(log => log.Field(GetField(sorting), sortOrder))
- .From(skipCount)
- .Size(maxResultCount),
- cancellationToken);
+ var query = new BoolQuery { Must = querys };
+
+ var serilogLogs = skipCount >= 10000 && sorts != null
+ ? await SearchAfterSerilogLogs(client, query, sorts, maxResultCount, skipCount, cancellationToken)
+ : await SearchFromSizeSerilogLogs(client, query, sorts, maxResultCount, skipCount, cancellationToken);
- return _objectMapper.Map, List>(response.Documents.ToList());
+ return _objectMapper.Map, List>(serilogLogs);
}
protected virtual List BuildQueryDescriptor(
+ IndexMappingInfo indexMappingInfo,
DateTime? startTime = null,
DateTime? endTime = null,
LogLevel? level = null,
@@ -271,30 +337,30 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
if (_currentTenant.IsAvailable)
{
- queries.Add(new TermQuery(GetField(nameof(SerilogInfo.Fields.TenantId)), _currentTenant.GetId().ToString()));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, "fields.TenantId"), _currentTenant.GetId().ToString()));
}
if (startTime.HasValue)
{
- queries.Add(new DateRangeQuery(GetField(nameof(SerilogInfo.TimeStamp)))
+ queries.Add(new DateRangeQuery(GetField(indexMappingInfo, "@timestamp"))
{
Gte = _clock.Normalize(startTime.Value),
});
}
if (endTime.HasValue)
{
- queries.Add(new DateRangeQuery(GetField(nameof(SerilogInfo.TimeStamp)))
+ queries.Add(new DateRangeQuery(GetField(indexMappingInfo, "@timestamp"))
{
Lte = _clock.Normalize(endTime.Value),
});
}
if (level.HasValue)
{
- queries.Add(new TermQuery(GetField(nameof(SerilogInfo.Level)), GetLogEventLevel(level.Value).ToString()));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, "level"), GetLogEventLevel(level.Value).ToString()));
}
if (!machineName.IsNullOrWhiteSpace())
{
// 模糊匹配
- queries.Add(new WildcardQuery(GetField(nameof(SerilogInfo.Fields.MachineName)))
+ queries.Add(new WildcardQuery(GetField(indexMappingInfo, "fields.MachineName"))
{
Value = $"*{machineName}*"
});
@@ -302,7 +368,7 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
if (!environment.IsNullOrWhiteSpace())
{
// 模糊匹配
- queries.Add(new WildcardQuery(GetField(nameof(SerilogInfo.Fields.Environment)))
+ queries.Add(new WildcardQuery(GetField(indexMappingInfo, "fields.EnvironmentName"))
{
Value = $"*{environment}*"
});
@@ -310,39 +376,39 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
if (!application.IsNullOrWhiteSpace())
{
// 模糊匹配
- queries.Add(new WildcardQuery(GetField(nameof(SerilogInfo.Fields.Application)))
+ queries.Add(new WildcardQuery(GetField(indexMappingInfo, "fields.ApplicationName"))
{
Value = $"*{application}*"
});
}
if (!context.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(SerilogInfo.Fields.Context)), context));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, "fields.SourceContext"), context));
}
if (!requestId.IsNullOrWhiteSpace())
{
- queries.Add(new TermQuery(GetField(nameof(SerilogInfo.Fields.RequestId)), requestId));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, "fields.RequestId"), requestId));
}
if (!requestPath.IsNullOrWhiteSpace())
{
// 前缀匹配
- queries.Add(new MatchPhrasePrefixQuery(GetField(nameof(SerilogInfo.Fields.RequestPath)), requestPath));
+ queries.Add(new MatchPhrasePrefixQuery(GetField(indexMappingInfo, "fields.RequestPath"), requestPath));
}
if (!correlationId.IsNullOrWhiteSpace())
{
// 模糊匹配
- queries.Add(new WildcardQuery(GetField(nameof(SerilogInfo.Fields.CorrelationId)))
+ queries.Add(new WildcardQuery(GetField(indexMappingInfo, "fields.CorrelationId"))
{
Value = $"*{correlationId}*"
});
}
if (processId.HasValue)
{
- queries.Add(new TermQuery(GetField(nameof(SerilogInfo.Fields.ProcessId)), FieldValue.FromValue(processId.Value)));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, "fields.ProcessId"), FieldValue.FromValue(processId.Value)));
}
if (threadId.HasValue)
{
- queries.Add(new TermQuery(GetField(nameof(SerilogInfo.Fields.ThreadId)), FieldValue.FromValue(threadId.Value)));
+ queries.Add(new TermQuery(GetField(indexMappingInfo, "fields.ThreadId"), FieldValue.FromValue(threadId.Value)));
}
if (hasException.HasValue)
@@ -354,7 +420,7 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
"field": "exceptions"
}
*/
- queries.Add(new ExistsQuery(GetField("Exceptions")));
+ queries.Add(new ExistsQuery(GetField(indexMappingInfo, "fields.Exceptions")));
}
else
{
@@ -374,7 +440,7 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
{
MustNot = new List
{
- new ExistsQuery(GetField("Exceptions"))
+ new ExistsQuery(GetField(indexMappingInfo, "fields.Exceptions"))
}
});
}
@@ -383,11 +449,155 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
return queries;
}
+ private async Task> SearchFromSizeSerilogLogs(
+ ElasticsearchClient client,
+ Query query,
+ SortOptions[]? sorts = null,
+ int maxResultCount = 50,
+ int skipCount = 0,
+ CancellationToken cancellationToken = default)
+ {
+ var searchResponse = await client.SearchAsync(dsl =>
+ {
+ dsl.Indices(CreateIndex())
+ .Query(query)
+ .From(skipCount)
+ .Size(maxResultCount);
+ if (sorts != null)
+ {
+ dsl.Sort(sorts);
+ }
+ }, cancellationToken);
+
+ if (!searchResponse.IsSuccess())
+ {
+ return [];
+ }
+
+ return searchResponse.Documents.ToList();
+ }
+
+ private async Task> SearchAfterSerilogLogs(
+ ElasticsearchClient client,
+ Query query,
+ SortOptions[] sorts,
+ int maxResultCount = 50,
+ int skipCount = 0,
+ CancellationToken cancellationToken = default)
+ {
+ var searchAfter = await GetSearchAfterValue(
+ client,
+ query,
+ sorts,
+ skipCount,
+ cancellationToken);
+
+ if (searchAfter == null || !searchAfter.Any())
+ {
+ return [];
+ }
+
+ var searchResponse = await client.SearchAsync(dsl =>
+ {
+ dsl.Indices(CreateIndex())
+ .Query(query)
+ .Sort(sorts)
+ .Size(maxResultCount)
+ .SearchAfter(searchAfter);
+ }, cancellationToken);
+
+ if (!searchResponse.IsSuccess())
+ {
+ return [];
+ }
+
+ return searchResponse.Documents.ToList();
+ }
+
+ private async Task?> GetSearchAfterValue(
+ ElasticsearchClient client,
+ Query query,
+ SortOptions[] sorts,
+ int skipCount,
+ CancellationToken cancellationToken = default)
+ {
+ // 10000以内直接取最后一条数据
+ if (skipCount < 10000)
+ {
+ var response = await client.SearchAsync(
+ dsl => dsl.Indices(CreateIndex())
+ .Query(query)
+ .Sort(sorts)
+ .SourceIncludes(x => x.Level)
+ .From(skipCount)
+ .Size(1),
+ cancellationToken);
+
+ if (!response.IsSuccess() || response.Hits == null || !response.Hits.Any())
+ {
+ return null;
+ }
+
+ var hit = response.Hits.FirstOrDefault();
+ return hit?.Sort?.ToList();
+ }
+
+ // 获取第9999条数据Hits作为searchAfter
+ var firstResponse = await client.SearchAsync(
+ dsl => dsl.Indices(CreateIndex())
+ .Query(query)
+ .Sort(sorts)
+ .SourceIncludes(x => x.Level)
+ .From(9999)
+ .Size(1),
+ cancellationToken);
+
+ if (!firstResponse.IsSuccess() || firstResponse.Hits == null || !firstResponse.Hits.Any())
+ {
+ return null;
+ }
+
+ var firstHit = firstResponse.Hits.FirstOrDefault();
+ if (firstHit?.Sort == null || !firstHit.Sort.Any())
+ {
+ return null;
+ }
+
+ var remaining = skipCount - 10000;
+ // 获取skipCount最近一条数据作为searchAfter
+ var secondResponse = await client.SearchAsync(
+ dsl => dsl.Indices(CreateIndex())
+ .Query(query)
+ .Sort(sorts)
+ .SourceIncludes(x => x.Level)
+ .SearchAfter(firstHit.Sort.ToList())
+ .Size(remaining),
+ cancellationToken);
+
+ if (!secondResponse.IsSuccess() || secondResponse.Hits == null || !secondResponse.Hits.Any())
+ {
+ return null;
+ }
+
+ if (secondResponse.Hits.Count < remaining)
+ {
+ return null;
+ }
+
+ var lastHit = secondResponse.Hits.LastOrDefault();
+ if (lastHit?.Sort == null || !lastHit.Sort.Any())
+ {
+ return null;
+ }
+
+ return lastHit.Sort.ToList();
+ }
+
protected virtual string CreateIndex(DateTimeOffset? offset = null)
{
if (!offset.HasValue)
{
- return IndexFormatRegex.Replace(_options.IndexFormat, @"$1*$2");
+ return _indexFormatRegex.Replace(_options.IndexFormat, @"$1*$2");
}
return string.Format(_options.IndexFormat, offset.Value).ToLowerInvariant();
}
@@ -405,37 +615,45 @@ public class SerilogElasticsearchLoggingManager : ILoggingManager, ISingletonDep
};
}
- private readonly static IDictionary _fieldMaps = new Dictionary(StringComparer.InvariantCultureIgnoreCase)
+ private static SortOptions[]? GetOrDefaultSort(IndexMappingInfo indexMappingInfo, string? sorting = null)
{
- { "timestamp", "@timestamp" },
- { "level", "level.keyword" },
- { "machinename", $"fields.{AbpLoggingEnricherPropertyNames.MachineName}.keyword" },
- { "environment", $"fields.{AbpLoggingEnricherPropertyNames.EnvironmentName}.keyword" },
- { "application", $"fields.{AbpSerilogEnrichersConsts.ApplicationNamePropertyName}.keyword" },
- { "context", "fields.SourceContext.keyword" },
- { "actionid", "fields.ActionId.keyword" },
- { "actionname", "fields.ActionName.keyword" },
- { "requestid", "fields.RequestId.keyword" },
- { "requestpath", "fields.RequestPath" },
- { "connectionid", "fields.ConnectionId" },
- { "correlationid", "fields.CorrelationId.keyword" },
- { "clientid", "fields.ClientId.keyword" },
- { "userid", "fields.UserId.keyword" },
- { "processid", "fields.ProcessId" },
- { "threadid", "fields.ThreadId" },
- { "id", $"fields.{AbpSerilogUniqueIdConsts.UniqueIdPropertyName}" },
- { "uniqueid", $"fields.{AbpSerilogUniqueIdConsts.UniqueIdPropertyName}" },
- };
- protected virtual string GetField(string field)
- {
- foreach (var fieldMap in _fieldMaps)
+ var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
+ ? SortOrder.Asc : SortOrder.Desc;
+ sorting = !sorting.IsNullOrWhiteSpace()
+ ? sorting.Split()[0]
+ : nameof(SerilogInfo.TimeStamp);
+
+ SortOptions[]? sorts = null;
+ if (sorting.IsNullOrWhiteSpace())
{
- if (field.ToLowerInvariant().Contains(fieldMap.Key))
+ var sortingFieldMap = indexMappingInfo.Fields
+ .Where(x => x.Key.Equals(sorting, StringComparison.CurrentCultureIgnoreCase))
+ .Select(x => x.Value)
+ .FirstOrDefault();
+ if (sortingFieldMap != null)
{
- return fieldMap.Value;
+ sorting = sortingFieldMap.Path;
+ }
+ if (!sorting.IsNullOrWhiteSpace())
+ {
+ sorts = new SortOptions[1]
+ {
+ new SortOptions
+ {
+ Field = new FieldSort(new Field(sorting))
+ {
+ Order = sortOrder,
+ },
+ }
+ };
}
}
- return field;
+ return sorts;
+ }
+
+ private static string GetField(IndexMappingInfo indexMappingInfo, string fieldFullPath)
+ {
+ return indexMappingInfo.GetExactFieldPath(fieldFullPath);
}
}
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN.Abp.Logging.csproj b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN.Abp.Logging.csproj
index 800090740..29265cbfd 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN.Abp.Logging.csproj
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN.Abp.Logging.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/AbpLoggingModule.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/AbpLoggingModule.cs
index 25d46980a..59b9311b0 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/AbpLoggingModule.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/AbpLoggingModule.cs
@@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
+using Volo.Abp.Specifications;
namespace LINGYUN.Abp.Logging;
+[DependsOn(typeof(AbpSpecificationsModule))]
public class AbpLoggingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/DefaultLoggingManager.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/DefaultLoggingManager.cs
index fc1725cff..01e50f8fc 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/DefaultLoggingManager.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/DefaultLoggingManager.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.Specifications;
namespace LINGYUN.Abp.Logging;
@@ -68,4 +69,23 @@ public class DefaultLoggingManager : ILoggingManager, ISingletonDependency
Logger.LogDebug("No logging manager is available!");
return Task.FromResult(new List());
}
+
+ public Task GetCountAsync(
+ ISpecification specification,
+ CancellationToken cancellationToken = default)
+ {
+ Logger.LogDebug("No logging manager is available!");
+ return Task.FromResult(0L);
+ }
+
+ public Task> GetListAsync(
+ ISpecification specification,
+ string? sorting = null,
+ int maxResultCount = 50,
+ int skipCount = 0,
+ CancellationToken cancellationToken = default)
+ {
+ Logger.LogDebug("No logging manager is available!");
+ return Task.FromResult(new List());
+ }
}
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/ILoggingManager.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/ILoggingManager.cs
index aa0e37ee6..c81f5e1c9 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/ILoggingManager.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/ILoggingManager.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Volo.Abp.Specifications;
namespace LINGYUN.Abp.Logging;
@@ -47,4 +48,15 @@ public interface ILoggingManager
bool? hasException = null,
bool includeDetails = false,
CancellationToken cancellationToken = default);
+
+ Task GetCountAsync(
+ ISpecification specification,
+ CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(
+ ISpecification specification,
+ string? sorting = null,
+ int maxResultCount = 50,
+ int skipCount = 0,
+ CancellationToken cancellationToken = default);
}
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogException.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogException.cs
index 666b2d8c8..c7a83be3a 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogException.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogException.cs
@@ -1,12 +1,27 @@
-namespace LINGYUN.Abp.Logging;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Logging;
public class LogException
{
+ [JsonPropertyName("SourceContext")]
public int Depth { get; set; }
+
+ [JsonPropertyName("ClassName")]
public string? Class { get; set; }
+
+ [JsonPropertyName("Message")]
public string? Message { get; set; }
+
+ [JsonPropertyName("Source")]
public string? Source { get; set; }
+
+ [JsonPropertyName("StackTraceString")]
public string? StackTrace { get; set; }
+
+ [JsonPropertyName("HResult")]
public int HResult { get; set; }
+
+ [JsonPropertyName("HelpURL")]
public string? HelpURL { get; set; }
}
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogField.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogField.cs
index 494884d03..281dca1dd 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogField.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogField.cs
@@ -1,20 +1,55 @@
-namespace LINGYUN.Abp.Logging;
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Logging;
public class LogField
{
+ [JsonPropertyName("UniqueId")]
public string? Id { get; set; }
+
+ [JsonPropertyName(AbpLoggingEnricherPropertyNames.MachineName)]
public string? MachineName { get; set; }
+
+ [JsonPropertyName(AbpLoggingEnricherPropertyNames.EnvironmentName)]
public string? Environment { get; set; }
+
+ [JsonPropertyName("ApplicationName")]
public string? Application { get; set; }
+
+ [JsonPropertyName("SourceContext")]
public string? Context { get; set; }
+
+ [JsonPropertyName("ActionId")]
public string? ActionId { get; set; }
+
+ [JsonPropertyName("ActionName")]
public string? ActionName { get; set; }
+
+ [JsonPropertyName("RequestId")]
public string? RequestId { get; set; }
+
+ [JsonPropertyName("RequestPath")]
public string? RequestPath { get; set; }
+
+ [JsonPropertyName("ConnectionId")]
public string? ConnectionId { get; set; }
+
+ [JsonPropertyName("CorrelationId")]
public string? CorrelationId { get; set; }
+
+ [JsonPropertyName("ClientId")]
public string? ClientId { get; set; }
+
+ [JsonPropertyName("UserId")]
public string? UserId { get; set; }
+
+ [JsonPropertyName("TenantId")]
+ public Guid? TenantId { get; set; }
+
+ [JsonPropertyName("ProcessId")]
public int? ProcessId { get; set; }
+
+ [JsonPropertyName("ThreadId")]
public int? ThreadId { get; set; }
}
diff --git a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogInfo.cs b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogInfo.cs
index e285d6d8e..460858612 100644
--- a/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogInfo.cs
+++ b/aspnet-core/framework/logging/LINGYUN.Abp.Logging/LINGYUN/Abp/AuditLogging/LogInfo.cs
@@ -1,14 +1,24 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
+using System.Text.Json.Serialization;
namespace LINGYUN.Abp.Logging;
public class LogInfo
{
+ [JsonPropertyName("@timestamp")]
public DateTime TimeStamp { get; set; }
+
+ [JsonPropertyName("level")]
public LogLevel Level { get; set; }
+
+ [JsonPropertyName("message")]
public string? Message { get; set; }
+
+ [JsonPropertyName("fields")]
public LogField Fields { get; set; } = default!;
+
+ [JsonPropertyName("exceptions")]
public List? Exceptions { get; set; }
}
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests.csproj
index a2cd57d31..f85e9f7a9 100644
--- a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests.csproj
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests.csproj
@@ -14,6 +14,6 @@
-
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestModule.cs
index 3b42d7145..ce5ef4e23 100644
--- a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestModule.cs
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AbpAuditLoggingElasticsearchTestModule.cs
@@ -1,6 +1,5 @@
using Elastic.Clients.Elasticsearch;
using LINGYUN.Abp.Elasticsearch;
-using LINGYUN.Abp.Tests;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -11,7 +10,7 @@ using Volo.Abp.Modularity;
namespace LINGYUN.Abp.AuditLogging.Elasticsearch
{
[DependsOn(
- typeof(AbpTestsBaseModule),
+ typeof(AbpAuditLoggingTestModule),
typeof(AbpAuditLoggingElasticsearchModule))]
public class AbpAuditLoggingElasticsearchTestModule : AbpModule
{
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogManagerTests.cs b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogManagerTests.cs
deleted file mode 100644
index 6fdc26be4..000000000
--- a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/AuditLogManagerTests.cs
+++ /dev/null
@@ -1,196 +0,0 @@
-using Moq.AutoMock;
-using Newtonsoft.Json;
-using Shouldly;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Volo.Abp.Auditing;
-using Volo.Abp.Specifications;
-using Xunit;
-
-namespace LINGYUN.Abp.AuditLogging.Elasticsearch
-{
- public class AuditLogManagerTests : AbpAuditLoggingElasticsearchTestBase
- {
- private readonly IAuditLogWriter _writer;
- private readonly IAuditLogManager _manager;
-
- public AuditLogManagerTests()
- {
- _writer = GetRequiredService();
- _manager = GetRequiredService();
- }
-
- [Fact]
- public async Task Save_Audit_Log_Should_Be_Find_By_Id()
- {
- var mock = new AutoMocker();
- var auditLogInfo = mock.CreateInstance();
-
- var id = await _writer.WriteAsync(auditLogInfo);
- id.ShouldNotBeNullOrWhiteSpace();
-
- var findId = Guid.Parse(id);
- var auditLog = await _manager.GetAsync(findId);
-
- auditLog.ShouldNotBeNull();
- auditLog.Id.ShouldBe(findId);
-
- await _manager.DeleteAsync(findId);
- }
-
- [Fact]
- public async Task Save_Audit_Log_Should_Get_List()
- {
- var count = 10;
- await MockcAsync(count);
-
- // 延迟等待ES索引完成
- await Task.Delay(5000);
-
- // 异常应该只有3个
- (await _manager.GetCountAsync(
- hasException: true)).ShouldBe(3);
-
- // 正常可以查询7个
- (await _manager.GetCountAsync(
- hasException: false)).ShouldBe(7);
-
- // POST方法能查到5个
- (await _manager.GetCountAsync(
- httpMethod: "POST")).ShouldBe(5);
-
- (await _manager.GetCountAsync(
- startTime: DateTime.Now.AddDays(-1).AddHours(5))).ShouldBe(6);
-
- (await _manager.GetCountAsync(
- endTime: DateTime.Now.AddDays(-1))).ShouldBe(4);
-
- (await _manager.GetCountAsync(
- startTime: DateTime.Now.AddDays(-3).AddHours(1),
- endTime: DateTime.Now)).ShouldBe(8);
-
- // 索引5只存在一个
- (await _manager.GetCountAsync(
- userName: "_user_5",
- clientId: "_client_5")).ShouldBe(1);
-
- var logs = await _manager.GetListAsync(
- userName: "_user_5",
- clientId: "_client_5",
- maxResultCount: count);
-
- logs.Count.ShouldBe(1);
- logs[0].Url.ShouldBe("_url_5");
- logs[0].BrowserInfo.ShouldBe("_browser_5");
- logs[0].ApplicationName.ShouldBe("_app_5");
-
- await _manager.DeleteManyAsync(logs.Select(x => x.Id).ToList());
- }
-
- [Fact]
- public async Task Save_Audit_Log_Should_Get_List_With_Specification()
- {
- var count = 10;
- await MockcAsync(count);
-
- // 延迟等待ES索引完成
- await Task.Delay(5000);
-
- // 异常应该只有3个
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.Exceptions != null))).ShouldBe(3);
-
- // 请求参数中包含 AAAAA 应该只有3个
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.Actions.Any(a => a.Parameters.Contains("AAAAA"))))).ShouldBe(3);
-
- // 正常可以查询7个
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.Exceptions == null))).ShouldBe(7);
-
- // POST方法能查到5个
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.HttpMethod == "POST"))).ShouldBe(5);
-
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.ExecutionTime >= DateTime.Now.AddDays(-1).AddHours(5)))).ShouldBe(6);
-
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.ExecutionTime <= DateTime.Now.AddDays(-1)))).ShouldBe(4);
-
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.ExecutionTime >= DateTime.Now.AddDays(-3).AddHours(1) &&
- x.ExecutionTime <= DateTime.Now))).ShouldBe(8);
-
- // 索引5只存在一个
- (await _manager.GetCountAsync(
- new ExpressionSpecification(x => x.UserName == "_user_5" && x.ClientId == "_client_5"))).ShouldBe(1);
-
- var logs = await _manager.GetListAsync(
- new ExpressionSpecification(x => x.UserName == "_user_5" && x.ClientId == "_client_5"),
- maxResultCount: count);
-
- logs.Count.ShouldBe(1);
- logs[0].Url.ShouldBe("_url_5");
- logs[0].BrowserInfo.ShouldBe("_browser_5");
- logs[0].ApplicationName.ShouldBe("_app_5");
-
- await _manager.DeleteManyAsync(logs.Select(x => x.Id).ToList());
- }
-
- protected async virtual Task> MockcAsync(int count)
- {
- var mock = new AutoMocker();
-
- var auditLogIds = new List();
-
- for (var i = 1; i <= count; i++)
- {
- var auditLogInfo = mock.CreateInstance();
- auditLogInfo.ClientId = $"_client_{i}";
- auditLogInfo.Url = $"_url_{i}";
- auditLogInfo.UserName = $"_user_{i}";
- auditLogInfo.ApplicationName = $"_app_{i}";
- auditLogInfo.BrowserInfo = $"_browser_{i}";
- auditLogInfo.ExecutionTime = DateTime.Now;
- if (i % 3 == 0)
- {
- auditLogInfo.Exceptions.Add(new Exception($"_exception_{i}"));
- auditLogInfo.Actions.Add(
- new AuditLogActionInfo
- {
- ServiceName = $"_service_{i}",
- MethodName = $"_method_{i}",
- ExecutionTime = DateTime.Now,
- ExecutionDuration = new Random().Next(1, 1000),
- Parameters = JsonConvert.SerializeObject(new
- {
- Paramter = "AAAAA",
- }),
- });
- }
-
- if (i % 2 == 0)
- {
- auditLogInfo.HttpMethod = "POST";
- }
-
- if (i % 4 == 0)
- {
- auditLogInfo.ExecutionTime = DateTime.Now.AddDays(-3);
- }
-
- if (i % 5 == 0)
- {
- auditLogInfo.ExecutionTime = DateTime.Now.AddDays(-2);
- }
-
- auditLogIds.Add(await _writer.WriteAsync(auditLogInfo));
- }
-
- return auditLogIds;
- }
- }
-}
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager_Tests.cs
new file mode 100644
index 000000000..7e3d0b5ec
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Elasticsearch.Tests/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager_Tests.cs
@@ -0,0 +1,5 @@
+namespace LINGYUN.Abp.AuditLogging.Elasticsearch;
+
+public class ElasticsearchAuditLogManager_Tests : AuditLogManager_Tests
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN.Abp.AuditLogging.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN.Abp.AuditLogging.Tests.csproj
new file mode 100644
index 000000000..10733d813
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN.Abp.AuditLogging.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net10.0
+
+ false
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AbpAuditLoggingTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AbpAuditLoggingTestBase.cs
new file mode 100644
index 000000000..345fb46aa
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AbpAuditLoggingTestBase.cs
@@ -0,0 +1,8 @@
+using LINGYUN.Abp.Tests;
+
+namespace LINGYUN.Abp.AuditLogging;
+
+public abstract class AbpAuditLoggingTestBase : AbpTestsBase
+{
+
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AbpAuditLoggingTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AbpAuditLoggingTestModule.cs
new file mode 100644
index 000000000..f0da4f9e7
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AbpAuditLoggingTestModule.cs
@@ -0,0 +1,11 @@
+using LINGYUN.Abp.Tests;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.AuditLogging;
+
+[DependsOn(
+ typeof(AbpTestsBaseModule),
+ typeof(AbpAuditLoggingModule))]
+public class AbpAuditLoggingTestModule : AbpModule
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AuditLogManager_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AuditLogManager_Tests.cs
new file mode 100644
index 000000000..6b9778917
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.AuditLogging.Tests/LINGYUN/Abp/AuditLogging/AuditLogManager_Tests.cs
@@ -0,0 +1,198 @@
+using Moq.AutoMock;
+using Newtonsoft.Json;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Auditing;
+using Volo.Abp.Modularity;
+using Volo.Abp.Specifications;
+using Volo.Abp.Testing;
+using Xunit;
+
+namespace LINGYUN.Abp.AuditLogging;
+
+public abstract class AuditLogManager_Tests : AbpIntegratedTest
+ where TStartupModule : IAbpModule
+{
+ private readonly IAuditLogWriter _writer;
+ private readonly IAuditLogManager _manager;
+
+ public AuditLogManager_Tests()
+ {
+ _writer = GetRequiredService();
+ _manager = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task Save_Audit_Log_Should_Be_Find_By_Id()
+ {
+ var mock = new AutoMocker();
+ var auditLogInfo = mock.CreateInstance();
+
+ var id = await _writer.WriteAsync(auditLogInfo);
+ id.ShouldNotBeNullOrWhiteSpace();
+
+ var findId = Guid.Parse(id);
+ var auditLog = await _manager.GetAsync(findId);
+
+ auditLog.ShouldNotBeNull();
+ auditLog.Id.ShouldBe(findId);
+
+ await _manager.DeleteAsync(findId);
+ }
+
+ [Fact]
+ public async Task Save_Audit_Log_Should_Get_List()
+ {
+ var count = 10;
+ await MockcAsync(count);
+
+ // 延迟等待写入完成
+ await Task.Delay(5000);
+
+ // 异常应该只有3个
+ (await _manager.GetCountAsync(
+ hasException: true)).ShouldBe(3);
+
+ // 正常可以查询7个
+ (await _manager.GetCountAsync(
+ hasException: false)).ShouldBe(7);
+
+ // POST方法能查到5个
+ (await _manager.GetCountAsync(
+ httpMethod: "POST")).ShouldBe(5);
+
+ (await _manager.GetCountAsync(
+ startTime: DateTime.Now.AddDays(-1).AddHours(5))).ShouldBe(6);
+
+ (await _manager.GetCountAsync(
+ endTime: DateTime.Now.AddDays(-1))).ShouldBe(4);
+
+ (await _manager.GetCountAsync(
+ startTime: DateTime.Now.AddDays(-3).AddHours(1),
+ endTime: DateTime.Now)).ShouldBe(8);
+
+ // 索引5只存在一个
+ (await _manager.GetCountAsync(
+ userName: "_user_5",
+ clientId: "_client_5")).ShouldBe(1);
+
+ var logs = await _manager.GetListAsync(
+ userName: "_user_5",
+ clientId: "_client_5",
+ maxResultCount: count);
+
+ logs.Count.ShouldBe(1);
+ logs[0].Url.ShouldBe("_url_5");
+ logs[0].BrowserInfo.ShouldBe("_browser_5");
+ logs[0].ApplicationName.ShouldBe("_app_5");
+
+ await _manager.DeleteManyAsync(logs.Select(x => x.Id).ToList());
+ }
+
+ [Fact]
+ public async Task Save_Audit_Log_Should_Get_List_With_Specification()
+ {
+ var count = 10;
+ await MockcAsync(count);
+
+ // 延迟等待写入完成
+ await Task.Delay(5000);
+
+ // 请求参数中包含 AAAAA 应该只有3个
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.Actions.Any(a => a.Parameters.Contains("AAAAA"))))).ShouldBe(3);
+
+ // 异常应该只有3个
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.Exceptions != null))).ShouldBe(3);
+
+ // 正常可以查询7个
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.Exceptions == null))).ShouldBe(7);
+
+ // POST方法能查到5个
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.HttpMethod == "POST"))).ShouldBe(5);
+
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.ExecutionTime >= DateTime.Now.AddDays(-1).AddHours(5)))).ShouldBe(6);
+
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.ExecutionTime <= DateTime.Now.AddDays(-1)))).ShouldBe(4);
+
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.ExecutionTime >= DateTime.Now.AddDays(-3).AddHours(1) &&
+ x.ExecutionTime <= DateTime.Now))).ShouldBe(8);
+
+ // 索引5只存在一个
+ (await _manager.GetCountAsync(
+ new ExpressionSpecification(x => x.UserName == "_user_5" && x.ClientId == "_client_5"))).ShouldBe(1);
+
+ var logs = await _manager.GetListAsync(
+ new ExpressionSpecification(x => x.UserName == "_user_5" && x.ClientId == "_client_5"),
+ maxResultCount: count);
+
+ logs.Count.ShouldBe(1);
+ logs[0].Url.ShouldBe("_url_5");
+ logs[0].BrowserInfo.ShouldBe("_browser_5");
+ logs[0].ApplicationName.ShouldBe("_app_5");
+
+ await _manager.DeleteManyAsync(logs.Select(x => x.Id).ToList());
+ }
+
+ protected async virtual Task> MockcAsync(int count)
+ {
+ var mock = new AutoMocker();
+
+ var auditLogIds = new List();
+
+ for (var i = 1; i <= count; i++)
+ {
+ var auditLogInfo = mock.CreateInstance();
+ auditLogInfo.ClientId = $"_client_{i}";
+ auditLogInfo.Url = $"_url_{i}";
+ auditLogInfo.UserName = $"_user_{i}";
+ auditLogInfo.ApplicationName = $"_app_{i}";
+ auditLogInfo.BrowserInfo = $"_browser_{i}";
+ auditLogInfo.ExecutionTime = DateTime.Now;
+ if (i % 3 == 0)
+ {
+ auditLogInfo.Exceptions.Add(new Exception($"_exception_{i}"));
+ auditLogInfo.Actions.Add(
+ new AuditLogActionInfo
+ {
+ ServiceName = $"_service_{i}",
+ MethodName = $"_method_{i}",
+ ExecutionTime = DateTime.Now,
+ ExecutionDuration = new Random().Next(1, 1000),
+ Parameters = JsonConvert.SerializeObject(new
+ {
+ Paramter = "AAAAA",
+ }),
+ });
+ }
+
+ if (i % 2 == 0)
+ {
+ auditLogInfo.HttpMethod = "POST";
+ }
+
+ if (i % 4 == 0)
+ {
+ auditLogInfo.ExecutionTime = DateTime.Now.AddDays(-3);
+ }
+
+ if (i % 5 == 0)
+ {
+ auditLogInfo.ExecutionTime = DateTime.Now.AddDays(-2);
+ }
+
+ auditLogIds.Add(await _writer.WriteAsync(auditLogInfo));
+ }
+
+ return auditLogIds;
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests.csproj
new file mode 100644
index 000000000..a38587e92
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net10.0
+
+ false
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/AbpLoggingSerilogElasticsearchTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/AbpLoggingSerilogElasticsearchTestBase.cs
new file mode 100644
index 000000000..844c91503
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/AbpLoggingSerilogElasticsearchTestBase.cs
@@ -0,0 +1,7 @@
+using LINGYUN.Abp.Tests;
+
+namespace LINGYUN.Abp.Logging.Serilog.Elasticsearch;
+
+public abstract class AbpLoggingSerilogElasticsearchTestBase : AbpTestsBase
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/AbpLoggingSerilogElasticsearchTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/AbpLoggingSerilogElasticsearchTestModule.cs
new file mode 100644
index 000000000..8fa1b3475
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/AbpLoggingSerilogElasticsearchTestModule.cs
@@ -0,0 +1,51 @@
+using Elastic.Clients.Elasticsearch;
+using LINGYUN.Abp.Elasticsearch;
+using LINGYUN.Abp.Tests;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using Volo.Abp;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.Logging.Serilog.Elasticsearch;
+
+[DependsOn(
+ typeof(AbpTestsBaseModule),
+ typeof(AbpLoggingTestModule),
+ typeof(AbpLoggingSerilogElasticsearchModule))]
+public class AbpLoggingSerilogElasticsearchTestModule : AbpModule
+{
+ private const string UserSecretsId = "11A604D4-3A64-4F92-94C6-5B1525CF63DD";
+
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder =>
+ {
+ builder.AddUserSecrets(UserSecretsId);
+ }));
+ }
+
+ public override void OnPostApplicationInitialization(ApplicationInitializationContext context)
+ {
+ RemoveTestIndexs(context.ServiceProvider);
+ }
+
+ public override void OnApplicationShutdown(ApplicationShutdownContext context)
+ {
+ RemoveTestIndexs(context.ServiceProvider);
+ }
+
+ private static void RemoveTestIndexs(IServiceProvider serviceProvider)
+ {
+ var clientFactory = serviceProvider.GetRequiredService();
+ var client = clientFactory.Create();
+ var indicesResponse = client.Indices.Get("abp-test-logging");
+ if (indicesResponse.IsSuccess())
+ {
+ foreach (var index in indicesResponse.Indices)
+ {
+ client.Indices.Delete(index.Key);
+ }
+ }
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/ElasticsearchLoggingManager_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/ElasticsearchLoggingManager_Tests.cs
new file mode 100644
index 000000000..16fa8483f
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Serilog.Elasticsearch.Tests/LINGYUN/Abp/Logging/Serilog/Elasticsearch/ElasticsearchLoggingManager_Tests.cs
@@ -0,0 +1,25 @@
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute.Extensions;
+using Serilog;
+
+namespace LINGYUN.Abp.Logging.Serilog.Elasticsearch;
+
+public class ElasticsearchLoggingManager_Tests : LoggingManager_Tests
+{
+ protected override void BeforeAddApplication(IServiceCollection services)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .Enrich.FromLogContext()
+ .Enrich.WithUniqueId()
+ .WriteTo.Elasticsearch(
+ nodeUris: "http://localhost:9200",
+ indexFormat: "abp-test-logging")
+ .CreateLogger();
+
+ services.AddLogging(logging =>
+ {
+ logging.AddSerilog();
+ });
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN.Abp.Logging.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN.Abp.Logging.Tests.csproj
new file mode 100644
index 000000000..dec2b41e4
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN.Abp.Logging.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net10.0
+
+ false
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/AbpLoggingTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/AbpLoggingTestBase.cs
new file mode 100644
index 000000000..c69652127
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/AbpLoggingTestBase.cs
@@ -0,0 +1,7 @@
+using LINGYUN.Abp.Tests;
+
+namespace LINGYUN.Abp.Logging;
+
+public abstract class AbpLoggingTestBase : AbpTestsBase
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/AbpLoggingTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/AbpLoggingTestModule.cs
new file mode 100644
index 000000000..e41b102eb
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/AbpLoggingTestModule.cs
@@ -0,0 +1,14 @@
+using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
+using LINGYUN.Abp.Tests;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.Logging;
+
+[DependsOn(
+ typeof(AbpTestsBaseModule),
+ typeof(AbpLoggingModule),
+ typeof(AbpSerilogEnrichersUniqueIdModule))]
+public class AbpLoggingTestModule : AbpModule
+{
+
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/LoggingManager_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/LoggingManager_Tests.cs
new file mode 100644
index 000000000..49ae03bd8
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.Logging.Tests/LINGYUN/Abp/Logging/LoggingManager_Tests.cs
@@ -0,0 +1,107 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using Serilog.Sinks.InMemory;
+using Shouldly;
+using System.Threading.Tasks;
+using Volo.Abp.Modularity;
+using Volo.Abp.Specifications;
+using Volo.Abp.Testing;
+using Xunit;
+
+namespace LINGYUN.Abp.Logging;
+
+public abstract class LoggingManager_Tests : AbpIntegratedTest
+ where TStartupModule : IAbpModule
+{
+ private readonly string _context;
+ private readonly Microsoft.Extensions.Logging.ILogger _logger;
+ private readonly ILoggingManager _manager;
+
+ public LoggingManager_Tests()
+ {
+ _manager = GetRequiredService();
+
+ _context = GetType().FullName!;
+ var loggerFactory = GetRequiredService();
+ _logger = loggerFactory.CreateLogger(_context);
+ }
+
+ protected override void BeforeAddApplication(IServiceCollection services)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .Enrich.FromLogContext()
+ .Enrich.WithUniqueId()
+ .WriteTo.InMemory()
+ .CreateLogger();
+
+ services.AddLogging(logging =>
+ {
+ logging.AddSerilog();
+ });
+ }
+
+ [Fact]
+ public async Task Should_Get_List()
+ {
+ _logger.LogDebug("xunit test debug log");
+ _logger.LogInformation("xunit test information log");
+ _logger.LogWarning("xunit test warning log");
+ _logger.LogError("xunit test error log");
+
+ await Log.CloseAndFlushAsync();
+
+ await Task.Delay(5000);
+
+ (await _manager.GetCountAsync(context: _context)).ShouldBe(4);
+
+ (await _manager.GetCountAsync(level: LogLevel.Information, context: _context)).ShouldBe(1);
+
+ var logs = await _manager.GetListAsync(level: LogLevel.Information, context: _context);
+ logs.Count.ShouldBe(1);
+ logs[0].Level.ShouldBe(LogLevel.Information);
+ logs[0].Message.ShouldBe("xunit test information log");
+ logs[0].Fields.ShouldNotBeNull();
+ logs[0].Fields.Id.ShouldNotBeNullOrWhiteSpace();
+ logs[0].Fields.Context.ShouldBe(_context);
+
+ var log = await _manager.GetAsync(logs[0].Fields.Id);
+ log.Message.ShouldBe("xunit test information log");
+ log.Fields.ShouldNotBeNull();
+ log.Fields.Id.ShouldNotBeNullOrWhiteSpace();
+ log.Fields.Id.ShouldBe(logs[0].Fields.Id);
+ log.Fields.Context.ShouldBe(_context);
+ }
+
+ [Fact]
+ public async Task Should_Get_List_With_Specification()
+ {
+ _logger.LogDebug("xunit test debug log");
+ _logger.LogInformation("xunit test information log");
+ _logger.LogWarning("xunit test warning log");
+ _logger.LogError("xunit test error log");
+
+ await Log.CloseAndFlushAsync();
+
+ await Task.Delay(5000);
+
+ var specification = new ExpressionSpecification(
+ x => x.Level == LogLevel.Information && x.Fields.Context == _context);
+
+ var logs = await _manager.GetListAsync(specification);
+ logs.Count.ShouldBe(1);
+ logs[0].Level.ShouldBe(LogLevel.Information);
+ logs[0].Message.ShouldBe("xunit test information log");
+ logs[0].Fields.ShouldNotBeNull();
+ logs[0].Fields.Id.ShouldNotBeNullOrWhiteSpace();
+ logs[0].Fields.Context.ShouldBe(_context);
+
+ var log = await _manager.GetAsync(logs[0].Fields.Id);
+ log.Message.ShouldBe("xunit test information log");
+ log.Fields.ShouldNotBeNull();
+ log.Fields.Id.ShouldNotBeNullOrWhiteSpace();
+ log.Fields.Id.ShouldBe(logs[0].Fields.Id);
+ log.Fields.Context.ShouldBe(_context);
+ }
+}