committed by
GitHub
33 changed files with 1064 additions and 808 deletions
@ -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; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// ES DSL表达式翻译器:把 <see cref="Expression{TDelegate}"/> 翻译为
|
|
||||
/// Elastic.Clients.Elasticsearch 的 <see cref="Query"/>(QueryDsl)。
|
|
||||
/// <para>
|
|
||||
/// 支持的算子(超出即抛 <see cref="NotSupportedException"/>,fail loud):
|
|
||||
/// <list type="bullet">
|
|
||||
/// <item>逻辑:&&、||、!(映射为 bool filter / should+minimum_should_match / must_not)</item>
|
|
||||
/// <item>比较:==、!=、>、>=、<、<=(数值与日期映射为 term / range)</item>
|
|
||||
/// <item>null 判断:字段 == null / != null(映射为 must_not exists / exists)</item>
|
|
||||
/// <item>字符串:Contains / StartsWith / EndsWith(映射为 wildcard)、Equals(映射为 term)</item>
|
|
||||
/// <item>集合:x.Actions.Any(predicate)(映射为 nested 查询或扁平字段展开)</item>
|
|
||||
/// <item>常量:true / false(映射为 match_all / match_none)</item>
|
|
||||
/// </list>
|
|
||||
/// </para>
|
|
||||
/// </summary>
|
|
||||
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<Func<AuditLog, bool>> 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<string>(); |
|
||||
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); |
|
||||
} |
|
||||
} |
|
||||
@ -1,12 +1,27 @@ |
|||||
namespace LINGYUN.Abp.Logging; |
using System.Text.Json.Serialization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Logging; |
||||
|
|
||||
public class LogException |
public class LogException |
||||
{ |
{ |
||||
|
[JsonPropertyName("SourceContext")] |
||||
public int Depth { get; set; } |
public int Depth { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ClassName")] |
||||
public string? Class { get; set; } |
public string? Class { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("Message")] |
||||
public string? Message { get; set; } |
public string? Message { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("Source")] |
||||
public string? Source { get; set; } |
public string? Source { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("StackTraceString")] |
||||
public string? StackTrace { get; set; } |
public string? StackTrace { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("HResult")] |
||||
public int HResult { get; set; } |
public int HResult { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("HelpURL")] |
||||
public string? HelpURL { get; set; } |
public string? HelpURL { get; set; } |
||||
} |
} |
||||
|
|||||
@ -1,20 +1,55 @@ |
|||||
namespace LINGYUN.Abp.Logging; |
using System; |
||||
|
using System.Text.Json.Serialization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Logging; |
||||
|
|
||||
public class LogField |
public class LogField |
||||
{ |
{ |
||||
|
[JsonPropertyName("UniqueId")] |
||||
public string? Id { get; set; } |
public string? Id { get; set; } |
||||
|
|
||||
|
[JsonPropertyName(AbpLoggingEnricherPropertyNames.MachineName)] |
||||
public string? MachineName { get; set; } |
public string? MachineName { get; set; } |
||||
|
|
||||
|
[JsonPropertyName(AbpLoggingEnricherPropertyNames.EnvironmentName)] |
||||
public string? Environment { get; set; } |
public string? Environment { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ApplicationName")] |
||||
public string? Application { get; set; } |
public string? Application { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("SourceContext")] |
||||
public string? Context { get; set; } |
public string? Context { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ActionId")] |
||||
public string? ActionId { get; set; } |
public string? ActionId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ActionName")] |
||||
public string? ActionName { get; set; } |
public string? ActionName { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("RequestId")] |
||||
public string? RequestId { get; set; } |
public string? RequestId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("RequestPath")] |
||||
public string? RequestPath { get; set; } |
public string? RequestPath { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ConnectionId")] |
||||
public string? ConnectionId { get; set; } |
public string? ConnectionId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("CorrelationId")] |
||||
public string? CorrelationId { get; set; } |
public string? CorrelationId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ClientId")] |
||||
public string? ClientId { get; set; } |
public string? ClientId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("UserId")] |
||||
public string? UserId { get; set; } |
public string? UserId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("TenantId")] |
||||
|
public Guid? TenantId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ProcessId")] |
||||
public int? ProcessId { get; set; } |
public int? ProcessId { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("ThreadId")] |
||||
public int? ThreadId { get; set; } |
public int? ThreadId { get; set; } |
||||
} |
} |
||||
|
|||||
@ -1,14 +1,24 @@ |
|||||
using Microsoft.Extensions.Logging; |
using Microsoft.Extensions.Logging; |
||||
using System; |
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
|
using System.Text.Json.Serialization; |
||||
|
|
||||
namespace LINGYUN.Abp.Logging; |
namespace LINGYUN.Abp.Logging; |
||||
|
|
||||
public class LogInfo |
public class LogInfo |
||||
{ |
{ |
||||
|
[JsonPropertyName("@timestamp")] |
||||
public DateTime TimeStamp { get; set; } |
public DateTime TimeStamp { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("level")] |
||||
public LogLevel Level { get; set; } |
public LogLevel Level { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("message")] |
||||
public string? Message { get; set; } |
public string? Message { get; set; } |
||||
|
|
||||
|
[JsonPropertyName("fields")] |
||||
public LogField Fields { get; set; } = default!; |
public LogField Fields { get; set; } = default!; |
||||
|
|
||||
|
[JsonPropertyName("exceptions")] |
||||
public List<LogException>? Exceptions { get; set; } |
public List<LogException>? Exceptions { get; set; } |
||||
} |
} |
||||
|
|||||
@ -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<IAuditLogWriter>(); |
|
||||
_manager = GetRequiredService<IAuditLogManager>(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Save_Audit_Log_Should_Be_Find_By_Id() |
|
||||
{ |
|
||||
var mock = new AutoMocker(); |
|
||||
var auditLogInfo = mock.CreateInstance<AuditLogInfo>(); |
|
||||
|
|
||||
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<AuditLog>(x => x.Exceptions != null))).ShouldBe(3); |
|
||||
|
|
||||
// 请求参数中包含 AAAAA 应该只有3个
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.Actions.Any(a => a.Parameters.Contains("AAAAA"))))).ShouldBe(3); |
|
||||
|
|
||||
// 正常可以查询7个
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.Exceptions == null))).ShouldBe(7); |
|
||||
|
|
||||
// POST方法能查到5个
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.HttpMethod == "POST"))).ShouldBe(5); |
|
||||
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.ExecutionTime >= DateTime.Now.AddDays(-1).AddHours(5)))).ShouldBe(6); |
|
||||
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.ExecutionTime <= DateTime.Now.AddDays(-1)))).ShouldBe(4); |
|
||||
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.ExecutionTime >= DateTime.Now.AddDays(-3).AddHours(1) && |
|
||||
x.ExecutionTime <= DateTime.Now))).ShouldBe(8); |
|
||||
|
|
||||
// 索引5只存在一个
|
|
||||
(await _manager.GetCountAsync( |
|
||||
new ExpressionSpecification<AuditLog>(x => x.UserName == "_user_5" && x.ClientId == "_client_5"))).ShouldBe(1); |
|
||||
|
|
||||
var logs = await _manager.GetListAsync( |
|
||||
new ExpressionSpecification<AuditLog>(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<List<string>> MockcAsync(int count) |
|
||||
{ |
|
||||
var mock = new AutoMocker(); |
|
||||
|
|
||||
var auditLogIds = new List<string>(); |
|
||||
|
|
||||
for (var i = 1; i <= count; i++) |
|
||||
{ |
|
||||
var auditLogInfo = mock.CreateInstance<AuditLogInfo>(); |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,5 @@ |
|||||
|
namespace LINGYUN.Abp.AuditLogging.Elasticsearch; |
||||
|
|
||||
|
public class ElasticsearchAuditLogManager_Tests : AuditLogManager_Tests<AbpAuditLoggingElasticsearchTestModule> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<Platforms>AnyCPU</Platforms> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
<PackageReference Include="Moq.AutoMock" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging\LINGYUN.Abp.AuditLogging.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AuditLogging; |
||||
|
|
||||
|
public abstract class AbpAuditLoggingTestBase : AbpTestsBase<AbpAuditLoggingTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
} |
||||
@ -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<TStartupModule> : AbpIntegratedTest<TStartupModule> |
||||
|
where TStartupModule : IAbpModule |
||||
|
{ |
||||
|
private readonly IAuditLogWriter _writer; |
||||
|
private readonly IAuditLogManager _manager; |
||||
|
|
||||
|
public AuditLogManager_Tests() |
||||
|
{ |
||||
|
_writer = GetRequiredService<IAuditLogWriter>(); |
||||
|
_manager = GetRequiredService<IAuditLogManager>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Save_Audit_Log_Should_Be_Find_By_Id() |
||||
|
{ |
||||
|
var mock = new AutoMocker(); |
||||
|
var auditLogInfo = mock.CreateInstance<AuditLogInfo>(); |
||||
|
|
||||
|
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<AuditLog>(x => x.Actions.Any(a => a.Parameters.Contains("AAAAA"))))).ShouldBe(3); |
||||
|
|
||||
|
// 异常应该只有3个
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.Exceptions != null))).ShouldBe(3); |
||||
|
|
||||
|
// 正常可以查询7个
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.Exceptions == null))).ShouldBe(7); |
||||
|
|
||||
|
// POST方法能查到5个
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.HttpMethod == "POST"))).ShouldBe(5); |
||||
|
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.ExecutionTime >= DateTime.Now.AddDays(-1).AddHours(5)))).ShouldBe(6); |
||||
|
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.ExecutionTime <= DateTime.Now.AddDays(-1)))).ShouldBe(4); |
||||
|
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.ExecutionTime >= DateTime.Now.AddDays(-3).AddHours(1) && |
||||
|
x.ExecutionTime <= DateTime.Now))).ShouldBe(8); |
||||
|
|
||||
|
// 索引5只存在一个
|
||||
|
(await _manager.GetCountAsync( |
||||
|
new ExpressionSpecification<AuditLog>(x => x.UserName == "_user_5" && x.ClientId == "_client_5"))).ShouldBe(1); |
||||
|
|
||||
|
var logs = await _manager.GetListAsync( |
||||
|
new ExpressionSpecification<AuditLog>(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<List<string>> MockcAsync(int count) |
||||
|
{ |
||||
|
var mock = new AutoMocker(); |
||||
|
|
||||
|
var auditLogIds = new List<string>(); |
||||
|
|
||||
|
for (var i = 1; i <= count; i++) |
||||
|
{ |
||||
|
var auditLogInfo = mock.CreateInstance<AuditLogInfo>(); |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<Platforms>AnyCPU</Platforms> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
<PackageReference Include="Moq.AutoMock" /> |
||||
|
<PackageReference Include="Serilog.Sinks.Elasticsearch" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\framework\logging\LINGYUN.Abp.Logging.Serilog.Elasticsearch\LINGYUN.Abp.Logging.Serilog.Elasticsearch.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Logging.Tests\LINGYUN.Abp.Logging.Tests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Logging.Serilog.Elasticsearch; |
||||
|
|
||||
|
public abstract class AbpLoggingSerilogElasticsearchTestBase : AbpTestsBase<AbpLoggingSerilogElasticsearchTestModule> |
||||
|
{ |
||||
|
} |
||||
@ -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<IElasticsearchClientFactory>(); |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<AbpLoggingSerilogElasticsearchTestModule> |
||||
|
{ |
||||
|
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(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<Platforms>AnyCPU</Platforms> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
<PackageReference Include="Moq.AutoMock" /> |
||||
|
<PackageReference Include="Serilog.Sinks.InMemory" /> |
||||
|
<PackageReference Include="Serilog.Extensions.Logging" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\framework\logging\LINGYUN.Abp.Logging\LINGYUN.Abp.Logging.csproj" /> |
||||
|
<ProjectReference Include="..\..\framework\logging\LINGYUN.Abp.Serilog.Enrichers.UniqueId\LINGYUN.Abp.Serilog.Enrichers.UniqueId.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Logging; |
||||
|
|
||||
|
public abstract class AbpLoggingTestBase : AbpTestsBase<AbpLoggingTestModule> |
||||
|
{ |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -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<TStartupModule> : AbpIntegratedTest<TStartupModule> |
||||
|
where TStartupModule : IAbpModule |
||||
|
{ |
||||
|
private readonly string _context; |
||||
|
private readonly Microsoft.Extensions.Logging.ILogger _logger; |
||||
|
private readonly ILoggingManager _manager; |
||||
|
|
||||
|
public LoggingManager_Tests() |
||||
|
{ |
||||
|
_manager = GetRequiredService<ILoggingManager>(); |
||||
|
|
||||
|
_context = GetType().FullName!; |
||||
|
var loggerFactory = GetRequiredService<ILoggerFactory>(); |
||||
|
_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<LogInfo>( |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue