Abp模块
abp
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

368 lines
14 KiB

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Nest;
using Sanhe.Abp.Elasticsearch;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
namespace Sanhe.Abp.AuditLogging.Elasticsearch
{
[Dependency(ReplaceServices = true)]
public class ElasticsearchAuditLogManager : IAuditLogManager, ITransientDependency
{
private readonly AbpAuditingOptions _auditingOptions;
private readonly AbpElasticsearchOptions _elasticsearchOptions;
private readonly IIndexNameNormalizer _indexNameNormalizer;
private readonly IElasticsearchClientFactory _clientFactory;
private readonly IAuditLogInfoToAuditLogConverter _converter;
public ILogger<ElasticsearchAuditLogManager> Logger { protected get; set; }
public ElasticsearchAuditLogManager(
IIndexNameNormalizer indexNameNormalizer,
IOptions<AbpElasticsearchOptions> elasticsearchOptions,
IElasticsearchClientFactory clientFactory,
IOptions<AbpAuditingOptions> auditingOptions,
IAuditLogInfoToAuditLogConverter converter)
{
_converter = converter;
_clientFactory = clientFactory;
_auditingOptions = auditingOptions.Value;
_elasticsearchOptions = elasticsearchOptions.Value;
_indexNameNormalizer = indexNameNormalizer;
Logger = NullLogger<ElasticsearchAuditLogManager>.Instance;
}
public virtual async Task<long> GetCountAsync(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
string url = null,
Guid? userId = null,
string userName = null,
string applicationName = null,
string correlationId = null,
string clientId = null,
string clientIpAddress = null,
int? maxExecutionDuration = null,
int? minExecutionDuration = null,
bool? hasException = null,
HttpStatusCode? httpStatusCode = null,
CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var querys = BuildQueryDescriptor(
startTime,
endTime,
httpMethod,
url,
userId,
userName,
applicationName,
correlationId,
clientId,
clientIpAddress,
maxExecutionDuration,
minExecutionDuration,
hasException,
httpStatusCode);
var response = await client.CountAsync<AuditLog>(dsl =>
dsl.Index(CreateIndex())
.Query(log => log.Bool(b => b.Must(querys.ToArray()))),
cancellationToken);
return response.Count;
}
public virtual async Task<List<AuditLog>> GetListAsync(
string sorting = null,
int maxResultCount = 50,
int skipCount = 0,
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
string url = null,
Guid? userId = null,
string userName = null,
string applicationName = null,
string correlationId = null,
string clientId = null,
string clientIpAddress = null,
int? maxExecutionDuration = null,
int? minExecutionDuration = null,
bool? hasException = null,
HttpStatusCode? httpStatusCode = null,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
? SortOrder.Ascending : SortOrder.Descending;
sorting = !sorting.IsNullOrWhiteSpace()
? sorting.Split()[0]
: nameof(AuditLog.ExecutionTime);
var querys = BuildQueryDescriptor(
startTime,
endTime,
httpMethod,
url,
userId,
userName,
applicationName,
correlationId,
clientId,
clientIpAddress,
maxExecutionDuration,
minExecutionDuration,
hasException,
httpStatusCode);
SourceFilterDescriptor<AuditLog> SourceFilter(SourceFilterDescriptor<AuditLog> selector)
{
selector.IncludeAll();
if (!includeDetails)
{
selector.Excludes(field =>
field.Field(f => f.Actions)
.Field(f => f.Comments)
.Field(f => f.Exceptions)
.Field(f => f.EntityChanges));
}
return selector;
}
var response = await client.SearchAsync<AuditLog>(dsl =>
dsl.Index(CreateIndex())
.Query(log => log.Bool(b => b.Must(querys.ToArray())))
.Source(SourceFilter)
.Sort(log => log.Field(GetField(sorting), sortOrder))
.From(skipCount)
.Size(maxResultCount),
cancellationToken);
return response.Documents.ToList();
}
public virtual async Task<AuditLog> GetAsync(
Guid id,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var response = await client.GetAsync<AuditLog>(
id,
dsl =>
dsl.Index(CreateIndex()),
cancellationToken);
return response.Source;
}
public virtual async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
await client.DeleteAsync<AuditLog>(
id,
dsl =>
dsl.Index(CreateIndex()),
cancellationToken);
}
public virtual async Task<string> SaveAsync(
AuditLogInfo auditInfo,
CancellationToken cancellationToken = default)
{
if (!_auditingOptions.HideErrors)
{
return await SaveLogAsync(auditInfo, cancellationToken);
}
try
{
return await SaveLogAsync(auditInfo, cancellationToken);
}
catch (Exception ex)
{
Logger.LogWarning("Could not save the audit log object: " + Environment.NewLine + auditInfo.ToString());
Logger.LogException(ex, Microsoft.Extensions.Logging.LogLevel.Error);
}
return "";
}
protected virtual async Task<string> SaveLogAsync(
AuditLogInfo auditLogInfo,
CancellationToken cancellationToken = default)
{
var client = _clientFactory.Create();
var auditLog = await _converter.ConvertAsync(auditLogInfo);
//var response = await client.IndexAsync(
// auditLog,
// (x) => x.Index(CreateIndex())
// .Id(auditLog.Id),
// cancellationToken);
// 使用 Bulk 命令传输可能存在参数庞大的日志结构
var response = await client.BulkAsync(
dsl => dsl.Index(CreateIndex())
.Create<AuditLog>(ct =>
ct.Id(auditLog.Id)
.Document(auditLog)));
return response.Items?.FirstOrDefault()?.Id;
}
protected virtual List<Func<QueryContainerDescriptor<AuditLog>, QueryContainer>> BuildQueryDescriptor(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
string url = null,
Guid? userId = null,
string userName = null,
string applicationName = null,
string correlationId = null,
string clientId = null,
string clientIpAddress = null,
int? maxExecutionDuration = null,
int? minExecutionDuration = null,
bool? hasException = null,
HttpStatusCode? httpStatusCode = null)
{
var querys = new List<Func<QueryContainerDescriptor<AuditLog>, QueryContainer>>();
if (startTime.HasValue)
{
querys.Add((log) => log.DateRange((q) => q.Field(GetField(nameof(AuditLog.ExecutionTime))).GreaterThanOrEquals(startTime)));
}
if (endTime.HasValue)
{
querys.Add((log) => log.DateRange((q) => q.Field(GetField(nameof(AuditLog.ExecutionTime))).LessThanOrEquals(endTime)));
}
if (!httpMethod.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.HttpMethod))).Value(httpMethod)));
}
if (!url.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Wildcard((q) => q.Field(GetField(nameof(AuditLog.Url))).Value($"*{url}*")));
}
if (userId.HasValue)
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.UserId))).Value(userId)));
}
if (!userName.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.UserName))).Value(userName)));
}
if (!applicationName.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.ApplicationName))).Value(applicationName)));
}
if (!correlationId.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.CorrelationId))).Value(correlationId)));
}
if (!clientId.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.ClientId))).Value(clientId)));
}
if (!clientIpAddress.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.ClientIpAddress))).Value(clientIpAddress)));
}
if (maxExecutionDuration.HasValue)
{
querys.Add((log) => log.Range((q) => q.Field(GetField(nameof(AuditLog.ExecutionDuration))).LessThanOrEquals(maxExecutionDuration)));
}
if (minExecutionDuration.HasValue)
{
querys.Add((log) => log.Range((q) => q.Field(GetField(nameof(AuditLog.ExecutionDuration))).GreaterThanOrEquals(minExecutionDuration)));
}
if (hasException.HasValue)
{
if (hasException.Value)
{
querys.Add(
(q) => q.Bool(
(b) => b.Must(
(m) => m.Exists(
(e) => e.Field((f) => f.Exceptions)))
)
);
}
else
{
querys.Add(
(q) => q.Bool(
(b) => b.MustNot(
(mn) => mn.Exists(
(e) => e.Field(
(f) => f.Exceptions)))
)
);
}
}
if (httpStatusCode.HasValue)
{
querys.Add((log) => log.Term((q) => q.Field(GetField(nameof(AuditLog.HttpStatusCode))).Value(httpStatusCode)));
}
return querys;
}
protected virtual string CreateIndex()
{
return _indexNameNormalizer.NormalizeIndex("audit-log");
}
private readonly static IDictionary<string, string> _fieldMaps = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
{ "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" },
};
protected virtual string GetField(string field)
{
if (_fieldMaps.TryGetValue(field, out var mapField))
{
return _elasticsearchOptions.FieldCamelCase ? mapField.ToCamelCase() : mapField.ToPascalCase();
}
return _elasticsearchOptions.FieldCamelCase ? field.ToCamelCase() : field.ToPascalCase();
}
}
}