mirror of https://github.com/abpframework/abp.git
committed by
GitHub
67 changed files with 3231 additions and 2 deletions
@ -0,0 +1,47 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using System.Linq; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher>))] |
||||
|
public sealed class TelemetryApplicationMetricsEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher> |
||||
|
{ |
||||
|
private readonly ITypeFinder _typeFinder; |
||||
|
public TelemetryApplicationMetricsEnricher(ITypeFinder typeFinder, IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
_typeFinder = typeFinder; |
||||
|
} |
||||
|
|
||||
|
protected override Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime); |
||||
|
} |
||||
|
|
||||
|
protected override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
var appServiceCount = _typeFinder.Types.Count(t => |
||||
|
typeof(IApplicationService).IsAssignableFrom(t) && |
||||
|
t is { IsAbstract: false, IsInterface: false } && |
||||
|
!t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); |
||||
|
|
||||
|
var controllerCount = _typeFinder.Types.Count(t => |
||||
|
typeof(ControllerBase).IsAssignableFrom(t) && |
||||
|
!t.IsAbstract && |
||||
|
!t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); |
||||
|
|
||||
|
|
||||
|
context.Current[ActivityPropertyNames.AppServiceCount] = appServiceCount; |
||||
|
context.Current[ActivityPropertyNames.ControllerCount] = controllerCount; |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Reflection; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
// General Information about an assembly is controlled through the following
|
||||
|
// set of attributes. Change these attribute values to modify the information
|
||||
|
// associated with an assembly.
|
||||
|
[assembly: AssemblyConfiguration("")] |
||||
|
[assembly: AssemblyCompany("")] |
||||
|
[assembly: AssemblyProduct("Volo.Abp.Authorization.Abstractions")] |
||||
|
[assembly: AssemblyTrademark("")] |
||||
|
[assembly: InternalsVisibleTo("Volo.Abp.Authorization")] |
||||
|
|
||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
|
// to COM components. If you need to access a type in this assembly from
|
||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||
|
[assembly: ComVisible(false)] |
||||
|
|
||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
[assembly: Guid("83632bec-2f60-4c2b-b964-30e8b37fa9d8")] |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher>))] |
||||
|
public sealed class TelemetryPermissionInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher> |
||||
|
{ |
||||
|
private readonly IPermissionDefinitionManager _permissionDefinitionManager; |
||||
|
|
||||
|
public TelemetryPermissionInfoEnricher(IPermissionDefinitionManager permissionDefinitionManager, |
||||
|
IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
_permissionDefinitionManager = permissionDefinitionManager; |
||||
|
} |
||||
|
|
||||
|
protected override Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
return Task.FromResult(context.ProjectId.HasValue); |
||||
|
} |
||||
|
|
||||
|
protected async override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
var permissions = await _permissionDefinitionManager.GetPermissionsAsync(); |
||||
|
|
||||
|
var userDefinedPermissionsCount = permissions.Count(IsUserDefinedPermission); |
||||
|
|
||||
|
context.Current[ActivityPropertyNames.PermissionCount] = userDefinedPermissionsCount; |
||||
|
} |
||||
|
|
||||
|
private static bool IsUserDefinedPermission(PermissionDefinition permission) |
||||
|
{ |
||||
|
return permission.Properties.TryGetValue(PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName, out var providerName) && |
||||
|
providerName is string && |
||||
|
!providerName.ToString()!.StartsWith(TelemetryConsts.VoloNameSpaceFilter); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity; |
||||
|
|
||||
|
public class ActivityContext |
||||
|
{ |
||||
|
public ActivityEvent Current { get; } |
||||
|
public Dictionary<string, object> ExtraProperties { get; } = new(); |
||||
|
public bool IsTerminated { get; private set; } |
||||
|
|
||||
|
public Guid? ProjectId => Current.Get<Guid?>(ActivityPropertyNames.ProjectId); |
||||
|
|
||||
|
public Guid? SolutionId => Current.Get<Guid?>(ActivityPropertyNames.SolutionId); |
||||
|
|
||||
|
public SessionType? SessionType => Current.Get<SessionType?>(ActivityPropertyNames.SessionType); |
||||
|
|
||||
|
public string? DeviceId => Current.Get<string?>(ActivityPropertyNames.DeviceId); |
||||
|
|
||||
|
public string? SolutionPath => ExtraProperties.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath) |
||||
|
? solutionPath?.ToString() |
||||
|
: null; |
||||
|
|
||||
|
private ActivityContext(ActivityEvent current) |
||||
|
{ |
||||
|
Current = current; |
||||
|
} |
||||
|
|
||||
|
public static ActivityContext Create(string activityName, string? details = null, |
||||
|
Action<Dictionary<string, object>>? additionalProperties = null) |
||||
|
{ |
||||
|
var activity = new ActivityEvent(activityName, details); |
||||
|
|
||||
|
if (additionalProperties is not null) |
||||
|
{ |
||||
|
var additionalPropertiesDict = new Dictionary<string, object>(); |
||||
|
activity[ActivityPropertyNames.AdditionalProperties] = additionalPropertiesDict; |
||||
|
additionalProperties.Invoke(additionalPropertiesDict); |
||||
|
} |
||||
|
|
||||
|
return new ActivityContext(activity); |
||||
|
} |
||||
|
|
||||
|
public void Terminate() |
||||
|
{ |
||||
|
IsTerminated = true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,147 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text.Json; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity; |
||||
|
|
||||
|
public class ActivityEvent : Dictionary<string, object?> |
||||
|
{ |
||||
|
public ActivityEvent() |
||||
|
{ |
||||
|
this[ActivityPropertyNames.Id] = Guid.NewGuid(); |
||||
|
this[ActivityPropertyNames.Time] = DateTimeOffset.UtcNow; |
||||
|
} |
||||
|
|
||||
|
public ActivityEvent(string activityName, string? details = null) : this() |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(activityName, nameof(activityName)); |
||||
|
|
||||
|
this[ActivityPropertyNames.ActivityName] = activityName; |
||||
|
this[ActivityPropertyNames.ActivityDetails] = details; |
||||
|
} |
||||
|
|
||||
|
public bool HasSolutionInfo() |
||||
|
{ |
||||
|
return this.ContainsKey(ActivityPropertyNames.HasSolutionInfo); |
||||
|
} |
||||
|
|
||||
|
public bool HasDeviceInfo() |
||||
|
{ |
||||
|
return this.ContainsKey(ActivityPropertyNames.HasDeviceInfo); |
||||
|
} |
||||
|
|
||||
|
public bool HasProjectInfo() |
||||
|
{ |
||||
|
return this.ContainsKey(ActivityPropertyNames.HasProjectInfo); |
||||
|
} |
||||
|
|
||||
|
public T Get<T>(string key) |
||||
|
{ |
||||
|
return TryConvert<T>(key, out var value) ? value : default!; |
||||
|
} |
||||
|
|
||||
|
public bool TryGetValue<T>(string key, out T value) |
||||
|
{ |
||||
|
return TryConvert(key, out value); |
||||
|
} |
||||
|
|
||||
|
private bool TryConvert<T>(string key, out T result) |
||||
|
{ |
||||
|
result = default!; |
||||
|
if (!this.TryGetValue(key, out var value) || value is null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (value is T tValue) |
||||
|
{ |
||||
|
result = tValue; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (value is JsonElement jsonElement) |
||||
|
{ |
||||
|
value = ExtractFromJsonElement(jsonElement); |
||||
|
if (value is null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var targetType = typeof(T); |
||||
|
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; |
||||
|
if (underlyingType.IsEnum) |
||||
|
{ |
||||
|
if (value is string str) |
||||
|
{ |
||||
|
result = (T)Enum.Parse(underlyingType, str, ignoreCase: true); |
||||
|
} |
||||
|
else if (value is int intValue) |
||||
|
{ |
||||
|
result = (T)Enum.ToObject(underlyingType, intValue); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
if (underlyingType == typeof(Dictionary<string, object>[])) |
||||
|
{ |
||||
|
result = (T)value; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (underlyingType == typeof(Guid)) |
||||
|
{ |
||||
|
result = (T)(object)Guid.Parse(value.ToString()!); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (underlyingType == typeof(DateTimeOffset)) |
||||
|
{ |
||||
|
result = (T)(object)DateTimeOffset.Parse(value.ToString()!); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// Nullable types
|
||||
|
result = (T)Convert.ChangeType(value, underlyingType); |
||||
|
return true; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static object? ExtractFromJsonElement(JsonElement element) |
||||
|
{ |
||||
|
return element.ValueKind switch |
||||
|
{ |
||||
|
JsonValueKind.String => element.GetString(), |
||||
|
JsonValueKind.Number => element.GetInt32(), |
||||
|
JsonValueKind.True => true, |
||||
|
JsonValueKind.False => false, |
||||
|
JsonValueKind.Null => null, |
||||
|
JsonValueKind.Array => element.EnumerateArray() |
||||
|
.Select(item => |
||||
|
{ |
||||
|
if (item.ValueKind == JsonValueKind.Object) |
||||
|
{ |
||||
|
return item.EnumerateObject() |
||||
|
.ToDictionary(prop => prop.Name, prop => ExtractFromJsonElement(prop.Value)); |
||||
|
} |
||||
|
|
||||
|
return new Dictionary<string, object?> { { "value", ExtractFromJsonElement(item) } }; |
||||
|
}) |
||||
|
.ToArray(), |
||||
|
|
||||
|
JsonValueKind.Object => element.EnumerateObject() |
||||
|
.ToDictionary(prop => prop.Name, prop => ExtractFromJsonElement(prop.Value)), |
||||
|
_ => element.ToString() |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
using Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
|
||||
|
public interface IHasParentTelemetryActivityEventEnricher<out TParent> where TParent: TelemetryActivityEventEnricher |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
|
||||
|
public interface ITelemetryActivityEventBuilder |
||||
|
{ |
||||
|
Task<ActivityEvent?> BuildAsync(ActivityContext context); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
|
||||
|
public interface ITelemetryActivityEventEnricher |
||||
|
{ |
||||
|
int ExecutionOrder { get; } |
||||
|
|
||||
|
Task EnrichAsync(ActivityContext context); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
|
||||
|
public interface ITelemetryActivityStorage |
||||
|
{ |
||||
|
Guid InitializeOrGetSession(); |
||||
|
void DeleteActivities(ActivityEvent[] activities); |
||||
|
void SaveActivity(ActivityEvent activityEvent); |
||||
|
List<ActivityEvent> GetActivities(); |
||||
|
bool ShouldAddDeviceInfo(); |
||||
|
bool ShouldAddSolutionInformation(Guid solutionId); |
||||
|
bool ShouldAddProjectInfo(Guid projectId); |
||||
|
bool ShouldSendActivities(); |
||||
|
void MarkActivitiesAsFailed(ActivityEvent[] activities); |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
public class TelemetryActivityEventBuilder : ITelemetryActivityEventBuilder, ISingletonDependency |
||||
|
{ |
||||
|
private readonly List<ITelemetryActivityEventEnricher> _activityEnrichers; |
||||
|
|
||||
|
public TelemetryActivityEventBuilder(IEnumerable<ITelemetryActivityEventEnricher> activityDataEnrichers) |
||||
|
{ |
||||
|
_activityEnrichers = activityDataEnrichers |
||||
|
.Where(FilterEnricher) |
||||
|
.OrderByDescending(x => x.ExecutionOrder) |
||||
|
.ToList(); |
||||
|
} |
||||
|
public virtual async Task<ActivityEvent?> BuildAsync(ActivityContext context) |
||||
|
{ |
||||
|
foreach (var enricher in _activityEnrichers) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await enricher.EnrichAsync(context); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
|
||||
|
if (context.IsTerminated) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return context.Current; |
||||
|
} |
||||
|
|
||||
|
private static bool FilterEnricher(ITelemetryActivityEventEnricher enricher) |
||||
|
{ |
||||
|
return ProxyHelper.GetUnProxiedType(enricher).Assembly.FullName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter) && |
||||
|
enricher is not IHasParentTelemetryActivityEventEnricher<TelemetryActivityEventEnricher>; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
public abstract class TelemetryActivityEventEnricher : ITelemetryActivityEventEnricher, IScopedDependency |
||||
|
{ |
||||
|
public virtual int ExecutionOrder { get; set; } = 0; |
||||
|
protected bool IgnoreChildren { get; set; } |
||||
|
protected virtual Type? ReplaceParentType { get; set; } |
||||
|
|
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
protected TelemetryActivityEventEnricher(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task EnrichAsync(ActivityContext context) |
||||
|
{ |
||||
|
if (!await CanExecuteAsync(context)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await ExecuteAsync(context); |
||||
|
await ExecuteChildrenAsync(context); |
||||
|
} |
||||
|
|
||||
|
protected virtual Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
return Task.FromResult(true); |
||||
|
} |
||||
|
|
||||
|
protected abstract Task ExecuteAsync(ActivityContext context); |
||||
|
|
||||
|
protected virtual async Task ExecuteChildrenAsync(ActivityContext context) |
||||
|
{ |
||||
|
if (IgnoreChildren) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
using var scope = _serviceProvider.CreateScope(); |
||||
|
|
||||
|
foreach (var child in GetChildren(scope.ServiceProvider)) |
||||
|
{ |
||||
|
await child.EnrichAsync(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ITelemetryActivityEventEnricher[] GetChildren(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
var targetType = ReplaceParentType ?? ProxyHelper.GetUnProxiedType(this); |
||||
|
var genericInterfaceType = typeof(IHasParentTelemetryActivityEventEnricher<>).MakeGenericType(targetType); |
||||
|
var enumerableType = typeof(IEnumerable<>).MakeGenericType(genericInterfaceType); |
||||
|
|
||||
|
var childServices = (IEnumerable<object>)serviceProvider.GetRequiredService(enumerableType); |
||||
|
|
||||
|
return childServices |
||||
|
.Cast<ITelemetryActivityEventEnricher>() |
||||
|
.ToArray(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Reflection; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher<TelemetrySessionInfoEnricher>))] |
||||
|
public sealed class TelemetryApplicationInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher<TelemetrySessionInfoEnricher> |
||||
|
{ |
||||
|
private readonly ITelemetryActivityStorage _telemetryActivityStorage; |
||||
|
|
||||
|
public TelemetryApplicationInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
_telemetryActivityStorage = telemetryActivityStorage; |
||||
|
} |
||||
|
|
||||
|
protected override Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime); |
||||
|
} |
||||
|
|
||||
|
protected override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var entryAssembly = Assembly.GetEntryAssembly(); |
||||
|
if (entryAssembly is null) |
||||
|
{ |
||||
|
context.Terminate(); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
var projectMetaData = AbpProjectMetadataReader.ReadProjectMetadata(entryAssembly); |
||||
|
if (projectMetaData?.ProjectId == null || projectMetaData.AbpSlnPath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
context.Terminate(); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
if (!_telemetryActivityStorage.ShouldAddProjectInfo(projectMetaData.ProjectId.Value)) |
||||
|
{ |
||||
|
IgnoreChildren = true; |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
var solutionId = ReadSolutionIdFromSolutionPath(projectMetaData.AbpSlnPath); |
||||
|
|
||||
|
if (!solutionId.HasValue) |
||||
|
{ |
||||
|
IgnoreChildren = true; |
||||
|
context.Terminate(); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
context.ExtraProperties[ActivityPropertyNames.SolutionPath] = projectMetaData.AbpSlnPath; |
||||
|
context.Current[ActivityPropertyNames.ProjectType] = projectMetaData.Role ?? string.Empty; |
||||
|
context.Current[ActivityPropertyNames.ProjectId] = projectMetaData.ProjectId.Value; |
||||
|
context.Current[ActivityPropertyNames.SolutionId] = solutionId; |
||||
|
context.Current[ActivityPropertyNames.HasProjectInfo] = true; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static Guid? ReadSolutionIdFromSolutionPath(string solutionPath) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (solutionPath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
using var fs = new FileStream(solutionPath!, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); |
||||
|
using var doc = JsonDocument.Parse(fs, new JsonDocumentOptions |
||||
|
{ |
||||
|
AllowTrailingCommas = true |
||||
|
}); |
||||
|
if (doc.RootElement.TryGetProperty("id", out var property) && property.TryGetGuid(out var solutionId)) |
||||
|
{ |
||||
|
return solutionId; |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// ignored
|
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher))] |
||||
|
internal sealed class TelemetryDeviceInfoEnricher : TelemetryActivityEventEnricher |
||||
|
{ |
||||
|
private readonly ITelemetryActivityStorage _telemetryActivityStorage; |
||||
|
private readonly ISoftwareInfoProvider _softwareInfoProvider; |
||||
|
|
||||
|
public TelemetryDeviceInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, |
||||
|
ISoftwareInfoProvider softwareInfoProvider, IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
_telemetryActivityStorage = telemetryActivityStorage; |
||||
|
_softwareInfoProvider = softwareInfoProvider; |
||||
|
} |
||||
|
protected async override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var deviceId = DeviceManager.GetUniquePhysicalKey(true); |
||||
|
context.Current[ActivityPropertyNames.DeviceId] = deviceId; |
||||
|
|
||||
|
if (!_telemetryActivityStorage.ShouldAddDeviceInfo()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var softwareList = await _softwareInfoProvider.GetSoftwareInfoAsync(); |
||||
|
|
||||
|
context.Current[ActivityPropertyNames.InstalledSoftwares] = softwareList; |
||||
|
context.Current[ActivityPropertyNames.DeviceLanguage] = CultureInfo.CurrentUICulture.Name; |
||||
|
context.Current[ActivityPropertyNames.OperatingSystem] = GetOperatingSystem(); |
||||
|
context.Current[ActivityPropertyNames.CountryIsoCode] = GetCountry(); |
||||
|
context.Current[ActivityPropertyNames.HasDeviceInfo] = true; |
||||
|
context.Current[ActivityPropertyNames.OperatingSystemArchitecture] = RuntimeInformation.OSArchitecture.ToString(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static OperationSystem GetOperatingSystem() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
return OperationSystem.Windows; |
||||
|
} |
||||
|
|
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
||||
|
{ |
||||
|
return OperationSystem.Linux; |
||||
|
} |
||||
|
|
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
return OperationSystem.MacOS; |
||||
|
} |
||||
|
|
||||
|
return OperationSystem.Unknown; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
private static string GetCountry() |
||||
|
{ |
||||
|
var region = new RegionInfo(CultureInfo.InstalledUICulture.Name); |
||||
|
return region.TwoLetterISORegionName; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher>))] |
||||
|
internal sealed class TelemetryModuleInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher> |
||||
|
{ |
||||
|
private readonly IModuleContainer _moduleContainer; |
||||
|
private readonly IAssemblyFinder _assemblyFinder; |
||||
|
|
||||
|
public TelemetryModuleInfoEnricher(IModuleContainer moduleContainer, IAssemblyFinder assemblyFinder, |
||||
|
IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
_moduleContainer = moduleContainer; |
||||
|
_assemblyFinder = assemblyFinder; |
||||
|
} |
||||
|
|
||||
|
protected override Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime); |
||||
|
} |
||||
|
|
||||
|
protected override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
context.Current[ActivityPropertyNames.ModuleCount] = _moduleContainer.Modules.Count; |
||||
|
context.Current[ActivityPropertyNames.ProjectCount] = _assemblyFinder.Assemblies.Count(x => |
||||
|
!x.FullName.IsNullOrEmpty() && |
||||
|
!x.FullName.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher))] |
||||
|
public class TelemetrySessionInfoEnricher : TelemetryActivityEventEnricher |
||||
|
{ |
||||
|
public override int ExecutionOrder { get; set; } = 10; |
||||
|
|
||||
|
public TelemetrySessionInfoEnricher(IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
context.Current[ActivityPropertyNames.SessionType] = SessionType.ApplicationRuntime; |
||||
|
context.Current[ActivityPropertyNames.SessionId] = Guid.NewGuid().ToString(); |
||||
|
context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher<TelemetrySessionInfoEnricher>))] |
||||
|
internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher<TelemetrySessionInfoEnricher> |
||||
|
{ |
||||
|
private readonly ITelemetryActivityStorage _telemetryActivityStorage; |
||||
|
|
||||
|
public TelemetrySolutionInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, IServiceProvider serviceProvider) : base(serviceProvider) |
||||
|
{ |
||||
|
_telemetryActivityStorage = telemetryActivityStorage; |
||||
|
} |
||||
|
|
||||
|
protected override Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
if (context.SolutionId.HasValue && !context.SolutionPath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return Task.FromResult(_telemetryActivityStorage.ShouldAddSolutionInformation(context.SolutionId.Value)); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(false); |
||||
|
} |
||||
|
|
||||
|
protected override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var jsonContent = File.ReadAllText(context.SolutionPath!); |
||||
|
using var doc = JsonDocument.Parse(jsonContent, new JsonDocumentOptions |
||||
|
{ |
||||
|
AllowTrailingCommas = true |
||||
|
}); |
||||
|
|
||||
|
var root = doc.RootElement; |
||||
|
|
||||
|
if (root.TryGetProperty("creatingStudioConfiguration", out var creatingStudioConfiguration)) |
||||
|
{ |
||||
|
AddSolutionCreationConfiguration(context, creatingStudioConfiguration); |
||||
|
} |
||||
|
|
||||
|
if (root.TryGetProperty("modules", out var modulesElement)) |
||||
|
{ |
||||
|
AddModuleInfo(context, modulesElement); |
||||
|
} |
||||
|
|
||||
|
context.Current[ActivityPropertyNames.HasSolutionInfo] = true; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
private static void AddSolutionCreationConfiguration(ActivityContext context, JsonElement config) |
||||
|
{ |
||||
|
context.Current[ActivityPropertyNames.Template] = TelemetryJsonExtensions.GetStringOrNull(config, "template"); |
||||
|
context.Current[ActivityPropertyNames.CreatedAbpStudioVersion] = TelemetryJsonExtensions.GetStringOrNull(config, "createdAbpStudioVersion"); |
||||
|
context.Current[ActivityPropertyNames.MultiTenancy] = TelemetryJsonExtensions.GetBooleanOrNull(config, "multiTenancy"); |
||||
|
context.Current[ActivityPropertyNames.UiFramework] = TelemetryJsonExtensions.GetStringOrNull(config, "uiFramework"); |
||||
|
context.Current[ActivityPropertyNames.DatabaseProvider] = TelemetryJsonExtensions.GetStringOrNull(config, "databaseProvider"); |
||||
|
context.Current[ActivityPropertyNames.Theme] = TelemetryJsonExtensions.GetStringOrNull(config, "theme"); |
||||
|
context.Current[ActivityPropertyNames.ThemeStyle] = TelemetryJsonExtensions.GetStringOrNull(config, "themeStyle"); |
||||
|
context.Current[ActivityPropertyNames.HasPublicWebsite] = TelemetryJsonExtensions.GetBooleanOrNull(config, "publicWebsite"); |
||||
|
context.Current[ActivityPropertyNames.IsTiered] = TelemetryJsonExtensions.GetBooleanOrNull(config, "tiered"); |
||||
|
context.Current[ActivityPropertyNames.SocialLogins] = TelemetryJsonExtensions.GetBooleanOrNull(config, "socialLogin"); |
||||
|
context.Current[ActivityPropertyNames.DatabaseManagementSystem] = TelemetryJsonExtensions.GetStringOrNull(config, "databaseManagementSystem"); |
||||
|
context.Current[ActivityPropertyNames.IsSeparateTenantSchema] = TelemetryJsonExtensions.GetBooleanOrNull(config, "separateTenantSchema"); |
||||
|
context.Current[ActivityPropertyNames.MobileFramework] = TelemetryJsonExtensions.GetStringOrNull(config, "mobileFramework"); |
||||
|
context.Current[ActivityPropertyNames.IncludeTests] = TelemetryJsonExtensions.GetBooleanOrNull(config, "includeTests"); |
||||
|
context.Current[ActivityPropertyNames.DynamicLocalization] = TelemetryJsonExtensions.GetBooleanOrNull(config, "dynamicLocalization"); |
||||
|
context.Current[ActivityPropertyNames.KubernetesConfiguration] = TelemetryJsonExtensions.GetBooleanOrNull(config, "kubernetesConfiguration"); |
||||
|
context.Current[ActivityPropertyNames.GrafanaDashboard] = TelemetryJsonExtensions.GetBooleanOrNull(config, "grafanaDashboard"); |
||||
|
} |
||||
|
|
||||
|
private static void AddModuleInfo(ActivityContext context, JsonElement modulesElement) |
||||
|
{ |
||||
|
var modules = new List<Dictionary<string, object?>>(); |
||||
|
|
||||
|
foreach (var module in modulesElement.EnumerateObject()) |
||||
|
{ |
||||
|
var modulePath = GetModuleFilePath(context.SolutionPath!, module); |
||||
|
if (modulePath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var moduleJsonFileContent = File.ReadAllText(modulePath); |
||||
|
using var moduleDoc = JsonDocument.Parse(moduleJsonFileContent); |
||||
|
|
||||
|
if (!moduleDoc.RootElement.TryGetProperty("imports", out var imports)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
foreach (var import in imports.EnumerateObject()) |
||||
|
{ |
||||
|
modules.Add(new Dictionary<string, object?> |
||||
|
{ |
||||
|
{ ActivityPropertyNames.ModuleName, import.Name }, |
||||
|
{ ActivityPropertyNames.ModuleVersion, TelemetryJsonExtensions.GetStringOrNull(import.Value, "version") }, |
||||
|
{ ActivityPropertyNames.ModuleInstallationTime, TelemetryJsonExtensions.GetDateTimeOffsetOrNull(import.Value, "creationTime") } |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
context.Current[ActivityPropertyNames.InstalledModules] = modules; |
||||
|
} |
||||
|
|
||||
|
private static string? GetModuleFilePath(string solutionPath, JsonProperty module) |
||||
|
{ |
||||
|
var path = TelemetryJsonExtensions.GetStringOrNull(module.Value, "path"); |
||||
|
if (path.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var fullPath = Path.Combine(Path.GetDirectoryName(solutionPath)!, path); |
||||
|
return File.Exists(fullPath) ? fullPath : null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Storage; |
||||
|
|
||||
|
internal class FailedActivityInfo |
||||
|
{ |
||||
|
public DateTimeOffset FirstFailTime { get; set; } |
||||
|
public DateTimeOffset LastFailTime { get; set; } |
||||
|
public int RetryCount { get; set; } |
||||
|
|
||||
|
public bool IsExpired() |
||||
|
{ |
||||
|
var now = DateTimeOffset.UtcNow; |
||||
|
|
||||
|
return RetryCount >= TelemetryPeriod.MaxActivityRetryCount || |
||||
|
now - FirstFailTime > TelemetryPeriod.MaxFailedActivityAge; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,207 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Text; |
||||
|
using System.Text.Encodings.Web; |
||||
|
using System.Text.Json; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Storage; |
||||
|
|
||||
|
public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDependency |
||||
|
{ |
||||
|
private TelemetryActivityStorageState State { get; } |
||||
|
private readonly static JsonSerializerOptions JsonSerializerOptions = new() |
||||
|
{ |
||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping |
||||
|
}; |
||||
|
|
||||
|
public TelemetryActivityStorage() |
||||
|
{ |
||||
|
CreateDirectoryIfNotExist(); |
||||
|
|
||||
|
State = LoadState(); |
||||
|
} |
||||
|
|
||||
|
public void SaveActivity(ActivityEvent activityEvent) |
||||
|
{ |
||||
|
State.Activities.Add(activityEvent); |
||||
|
|
||||
|
var activityName = activityEvent.Get<string>(ActivityPropertyNames.ActivityName); |
||||
|
|
||||
|
if (activityName == ActivityNameConsts.AbpStudioClose) |
||||
|
{ |
||||
|
State.SessionId = null; |
||||
|
} |
||||
|
|
||||
|
if (activityEvent.HasDeviceInfo()) |
||||
|
{ |
||||
|
State.LastDeviceInfoAddTime = DateTimeOffset.UtcNow; |
||||
|
} |
||||
|
|
||||
|
if (activityEvent.HasSolutionInfo()) |
||||
|
{ |
||||
|
var solutionId = activityEvent.Get<Guid>(ActivityPropertyNames.SolutionId); |
||||
|
State.Solutions[solutionId] = DateTimeOffset.UtcNow; |
||||
|
} |
||||
|
|
||||
|
if (activityEvent.HasProjectInfo()) |
||||
|
{ |
||||
|
var projectId = activityEvent.Get<Guid>(ActivityPropertyNames.ProjectId); |
||||
|
State.Projects[projectId] = DateTimeOffset.UtcNow; |
||||
|
} |
||||
|
|
||||
|
SaveState(); |
||||
|
} |
||||
|
|
||||
|
public List<ActivityEvent> GetActivities() |
||||
|
{ |
||||
|
return State.Activities; |
||||
|
} |
||||
|
|
||||
|
public Guid InitializeOrGetSession() |
||||
|
{ |
||||
|
if (State.SessionId.HasValue) |
||||
|
{ |
||||
|
return State.SessionId.Value; |
||||
|
} |
||||
|
|
||||
|
State.SessionId = Guid.NewGuid(); |
||||
|
SaveState(); |
||||
|
|
||||
|
return State.SessionId.Value; |
||||
|
} |
||||
|
|
||||
|
public void DeleteActivities(ActivityEvent[] activities) |
||||
|
{ |
||||
|
var activityIds = new HashSet<Guid>(activities.Select(x => x.Get<Guid>(ActivityPropertyNames.Id))); |
||||
|
|
||||
|
State.Activities.RemoveAll(x => activityIds.Contains(x.Get<Guid>(ActivityPropertyNames.Id))); |
||||
|
|
||||
|
SaveState(); |
||||
|
} |
||||
|
|
||||
|
public void MarkActivitiesAsFailed(ActivityEvent[] activities) |
||||
|
{ |
||||
|
var now = DateTimeOffset.UtcNow; |
||||
|
|
||||
|
foreach (var activity in activities) |
||||
|
{ |
||||
|
var activityId = activity.Get<Guid>(ActivityPropertyNames.Id); |
||||
|
|
||||
|
if (State.FailedActivities.TryGetValue(activityId, out var failedActivityInfo)) |
||||
|
{ |
||||
|
failedActivityInfo.RetryCount++; |
||||
|
failedActivityInfo.LastFailTime = now; |
||||
|
|
||||
|
if (!failedActivityInfo.IsExpired()) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
State.Activities.RemoveAll(x=> x.Get<Guid>(ActivityPropertyNames.Id) == activityId); |
||||
|
State.FailedActivities.Remove(activityId); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
State.FailedActivities[activityId] = new FailedActivityInfo |
||||
|
{ |
||||
|
FirstFailTime = now, |
||||
|
LastFailTime = now, |
||||
|
RetryCount = 1 |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
SaveState(); |
||||
|
} |
||||
|
|
||||
|
public bool ShouldAddDeviceInfo() |
||||
|
{ |
||||
|
return State.LastDeviceInfoAddTime is null || |
||||
|
DateTimeOffset.UtcNow - State.LastDeviceInfoAddTime > TelemetryPeriod.InformationSendPeriod; |
||||
|
} |
||||
|
|
||||
|
public bool ShouldAddSolutionInformation(Guid solutionId) |
||||
|
{ |
||||
|
return !State.Solutions.TryGetValue(solutionId, out var lastSend) || |
||||
|
DateTimeOffset.UtcNow - lastSend > TelemetryPeriod.InformationSendPeriod; |
||||
|
} |
||||
|
|
||||
|
public bool ShouldAddProjectInfo(Guid projectId) |
||||
|
{ |
||||
|
return !State.Projects.TryGetValue(projectId, out var lastSend) || |
||||
|
DateTimeOffset.UtcNow - lastSend > TelemetryPeriod.InformationSendPeriod; |
||||
|
} |
||||
|
|
||||
|
public bool ShouldSendActivities() |
||||
|
{ |
||||
|
return State.ActivitySendTime is null || |
||||
|
DateTimeOffset.UtcNow - State.ActivitySendTime > TelemetryPeriod.ActivitySendPeriod; |
||||
|
} |
||||
|
|
||||
|
private void SaveState() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var json = JsonSerializer.Serialize(State, JsonSerializerOptions); |
||||
|
var encryptedJson = Cryptography.Encrypt(json); |
||||
|
File.WriteAllText(TelemetryPaths.ActivityStorage, encryptedJson, Encoding.UTF8); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// Ignored
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TelemetryActivityStorageState LoadState() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (!File.Exists(TelemetryPaths.ActivityStorage)) |
||||
|
{ |
||||
|
return new TelemetryActivityStorageState(); |
||||
|
} |
||||
|
|
||||
|
var fileContent = MutexExecutor.ReadFileSafely(TelemetryPaths.ActivityStorage); |
||||
|
|
||||
|
if (fileContent.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return new TelemetryActivityStorageState(); |
||||
|
} |
||||
|
|
||||
|
var json = Cryptography.Decrypt(fileContent); |
||||
|
|
||||
|
var state = JsonSerializer.Deserialize<TelemetryActivityStorageState>(json, JsonSerializerOptions)!; |
||||
|
state.Activities = state.Activities.Where(x => x != null).ToList(); |
||||
|
return state; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return new TelemetryActivityStorageState(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void CreateDirectoryIfNotExist() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var storageDirectory = Path.GetDirectoryName(TelemetryPaths.ActivityStorage)!; |
||||
|
|
||||
|
if (!Directory.Exists(storageDirectory)) |
||||
|
{ |
||||
|
Directory.CreateDirectory(storageDirectory); |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// Ignored
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Storage; |
||||
|
|
||||
|
internal class TelemetryActivityStorageState |
||||
|
{ |
||||
|
public DateTimeOffset? ActivitySendTime { get; set; } |
||||
|
public DateTimeOffset? LastDeviceInfoAddTime { get; set; } |
||||
|
public Guid? SessionId { get; set; } |
||||
|
public List<ActivityEvent> Activities { get; set; } = new(); |
||||
|
public Dictionary<Guid,DateTimeOffset> Solutions { get; set; } = new(); |
||||
|
public Dictionary<Guid, DateTimeOffset> Projects { get; set; } = new(); |
||||
|
public Dictionary<Guid, FailedActivityInfo> FailedActivities { get; set; } = new(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity.Storage; |
||||
|
|
||||
|
static internal class TelemetryPeriod |
||||
|
{ |
||||
|
private const string TestModeEnvironmentVariable = "ABP_TELEMETRY_TEST_MODE"; |
||||
|
|
||||
|
static TelemetryPeriod() |
||||
|
{ |
||||
|
var isTestMode = IsTestModeEnabled(); |
||||
|
|
||||
|
InformationSendPeriod = isTestMode |
||||
|
? TimeSpan.FromSeconds(15) |
||||
|
: TimeSpan.FromDays(7); |
||||
|
|
||||
|
ActivitySendPeriod = isTestMode |
||||
|
? TimeSpan.FromSeconds(5) |
||||
|
: TimeSpan.FromDays(1); |
||||
|
} |
||||
|
|
||||
|
public static TimeSpan ActivitySendPeriod { get; } |
||||
|
public static TimeSpan InformationSendPeriod { get; } |
||||
|
|
||||
|
public static int MaxActivityRetryCount { get; set; } = 3; |
||||
|
public static TimeSpan MaxFailedActivityAge { get; set; } = TimeSpan.FromDays(30); |
||||
|
|
||||
|
private static bool IsTestModeEnabled() |
||||
|
{ |
||||
|
var testModeVariable = |
||||
|
Environment.GetEnvironmentVariable(TestModeEnvironmentVariable, EnvironmentVariableTarget.User); |
||||
|
return bool.TryParse(testModeVariable, out var isTestMode) && isTestMode; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using System.Text.Json; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Activity; |
||||
|
|
||||
|
static internal class TelemetryJsonExtensions |
||||
|
{ |
||||
|
static internal string? GetStringOrNull(JsonElement element, string propertyName) |
||||
|
{ |
||||
|
return element.TryGetProperty(propertyName, out var property) |
||||
|
? property.GetString() ?? null |
||||
|
: null; |
||||
|
} |
||||
|
|
||||
|
static internal bool? GetBooleanOrNull(JsonElement element, string propertyName) |
||||
|
{ |
||||
|
if (element.TryGetProperty(propertyName, out var property) && bool.TryParse(property.GetString(), out var boolValue)) |
||||
|
{ |
||||
|
return boolValue; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
static internal DateTimeOffset? GetDateTimeOffsetOrNull(JsonElement element, string propertyName) |
||||
|
{ |
||||
|
if (element.TryGetProperty(propertyName, out var date) && DateTimeOffset.TryParse(date.GetString(), out var dateTimeValue)) |
||||
|
{ |
||||
|
return dateTimeValue; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
public static class AbpPlatformUrls |
||||
|
{ |
||||
|
#if DEBUG
|
||||
|
public const string AbpTelemetryApiUrl = "https://localhost:44393/"; |
||||
|
#else
|
||||
|
public const string AbpTelemetryApiUrl = "https://telemetry.abp.io/"; |
||||
|
#endif
|
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
public static class ActivityNameConsts |
||||
|
{ |
||||
|
public const string AbpStudioOpen = "AbpStudio.Open"; |
||||
|
public const string AbpStudioOpenFirstTimeForDevice = "AbpStudio.Open.FirstTimeForDevice"; |
||||
|
public const string AbpStudioOpenFirstTimeForUser = "AbpStudio.Open.FirstTimeForUser"; |
||||
|
public const string AbpStudioClose = "AbpStudio.Close"; |
||||
|
public const string AbpStudioCloseWithoutLogin = "AbpStudio.Close.WithoutLogin"; |
||||
|
public const string AbpStudioLogin = "AbpStudio.Login"; |
||||
|
public const string AbpStudioCampaignClick = "AbpStudio.Campaing.Click"; |
||||
|
public const string AbpStudioCommunityPostClick = "AbpStudio.CommunityPost.Click"; |
||||
|
public const string AbpStudioSolutionInitExisting = "AbpStudio.Solution.InitExisting"; |
||||
|
public const string AbpStudioSolutionNew = "AbpStudio.Solution.New"; |
||||
|
public const string AbpStudioSolutionNewMicroservice = "AbpStudio.Solution.New.Microservice"; |
||||
|
public const string AbpStudioSolutionAddModule = "AbpStudio.Solution.Add.Module"; |
||||
|
public const string AbpStudioSolutionAddModuleEmpty = "AbpStudio.Solution.Add.Module.Empty"; |
||||
|
public const string AbpStudioSolutionAddModuleDdd = "AbpStudio.Solution.Add.Module.Ddd"; |
||||
|
public const string AbpStudioSolutionAddModuleStandard = "AbpStudio.Solution.Add.Module.Standard"; |
||||
|
public const string AbpStudioSolutionAddMicroservice = "AbpStudio.Solution.Add.Microservice"; |
||||
|
public const string AbpStudioSolutionAddGateway = "AbpStudio.Solution.Add.Gateway"; |
||||
|
public const string AbpStudioSolutionAddWeb = "AbpStudio.Solution.Add.Web"; |
||||
|
public const string AbpStudioSolutionAddPackage = "AbpStudio.Solution.Add.Package"; |
||||
|
public const string AbpStudioSolutionAddPackageHttpApiLayer = "AbpStudio.Solution.Add.Package.HttpApiLayer"; |
||||
|
public const string AbpStudioAbpCliInstallLibs = "AbpStudio.AbpCli.InstallLibs"; |
||||
|
public const string AbpStudioAbpCliUpgradeAbp = "AbpStudio.AbpCli.UpgradeAbp"; |
||||
|
public const string AbpStudioAbpCliSwitchToStable = "AbpStudio.AbpCli.SwitchToStable"; |
||||
|
public const string AbpStudioAbpCliSwitchToPreview = "AbpStudio.AbpCli.SwitchToPreview"; |
||||
|
public const string AbpStudioAbpCliSwitchToNightly = "AbpStudio.AbpCli.SwitchToNightly"; |
||||
|
public const string AbpStudioAbpCliClean = "AbpStudio.AbpCli.Clean"; |
||||
|
public const string AbpStudioDotnetCliBuild = "AbpStudio.DotnetCli.Build"; |
||||
|
public const string AbpStudioDotnetCliGraphBuild = "AbpStudio.DotnetCli.GraphBuild"; |
||||
|
public const string AbpStudioDotnetCliClean = "AbpStudio.DotnetCli.Clean"; |
||||
|
public const string AbpStudioDotnetCliRestore = "AbpStudio.DotnetCli.Restore"; |
||||
|
public const string AbpStudioSolutionRunnerRunApp = "AbpStudio.SolutionRunner.RunApp"; |
||||
|
public const string AbpStudioSolutionRunnerAddCsharpApp = "AbpStudio.SolutionRunner.Add.CsharpApp"; |
||||
|
public const string AbpStudioSolutionRunnerAddCliApp = "AbpStudio.SolutionRunner.Add.CliApp"; |
||||
|
public const string AbpStudioSolutionRunnerAddProfile = "AbpStudio.SolutionRunner.Add.Profile"; |
||||
|
public const string AbpStudioSolutionRunnerAppManageMetadata = "AbpStudio.SolutionRunner.App.Manage.Metadata"; |
||||
|
public const string AbpStudioSolutionRunnerAppManageSecrets = "AbpStudio.SolutionRunner.App.Manage.Secrets"; |
||||
|
public const string AbpStudioSolutionOpen = "AbpStudio.Solution.Open"; |
||||
|
public const string AbpStudioMonitoringBrowse = "AbpStudio.Monitoring.Browse"; |
||||
|
public const string AbpStudioMonitoringHttpRequests = "AbpStudio.Monitoring.HttpRequests"; |
||||
|
public const string AbpStudioMonitoringHttpRequestsDetail = "AbpStudio.Monitoring.HttpRequests.Detail"; |
||||
|
public const string AbpStudioMonitoringEvents = "AbpStudio.Monitoring.Events"; |
||||
|
public const string AbpStudioMonitoringEventsDetail = "AbpStudio.Monitoring.Events.Detail"; |
||||
|
public const string AbpStudioMonitoringExceptions = "AbpStudio.Monitoring.Exceptions"; |
||||
|
public const string AbpStudioMonitoringExceptionsDetail = "AbpStudio.Monitoring.Exceptions.Detail"; |
||||
|
public const string AbpStudioMonitoringLogs = "AbpStudio.Monitoring.Logs"; |
||||
|
public const string AbpStudioKubernetesAddProfile = "AbpStudio.Kubernetes.Add.Profile"; |
||||
|
public const string AbpStudioKubernetesConnect = "AbpStudio.Kubernetes.Connect"; |
||||
|
public const string AbpStudioKubernetesIntercept = "AbpStudio.Kubernetes.Intercept"; |
||||
|
public const string AbpStudioKubernetesHelmCommandsInstall = "AbpStudio.Kubernetes.Helm.Commands.Install"; |
||||
|
public const string AbpStudioKubernetesHelmCommandsBuildImages = "AbpStudio.Kubernetes.Helm.Commands.BuildImages"; |
||||
|
public const string AbpStudioKubernetesHelmChartsRefreshSubCharts = "AbpStudio.Kubernetes.Helm.Charts.RefreshSubCharts"; |
||||
|
public const string AbpStudioKubernetesHelmChartsManageMetadata = "AbpStudio.Kubernetes.Helm.Charts.Manage.Metadata"; |
||||
|
public const string AbpStudioLogsShow = "AbpStudio.Logs.Show"; |
||||
|
public const string AbpStudioSuiteOpen = "AbpStudio.Suite.Open"; |
||||
|
public const string AbpStudioGlobalSecretsManage = "AbpStudio.GlobalSecrets.Manage"; |
||||
|
public const string AbpStudioGlobalMetadataManage = "AbpStudio.GlobalMetadata.Manage"; |
||||
|
public const string AbpCliCommandsNewSolution = "AbpCli.Comands.NewSolution"; |
||||
|
public const string AbpCliCommandsNewModule = "AbpCli.Comands.NewModule"; |
||||
|
public const string AbpCliCommandsNewPackage = "AbpCli.Comands.NewPackage"; |
||||
|
public const string AbpCliCommandsUpdate = "AbpCli.Comands.Update"; |
||||
|
public const string AbpCliCommandsClean = "AbpCli.Comands.Clean"; |
||||
|
public const string AbpCliCommandsAddPackage = "AbpCli.Comands.AddPackage"; |
||||
|
public const string AbpCliCommandsAddPackageRef = "AbpCli.Comands.AddPackageRef"; |
||||
|
public const string AbpCliCommandsInstallModule = "AbpCli.Comands.InstallModule"; |
||||
|
public const string AbpCliCommandsInstallLocalModule = "AbpCli.Comands.InstallLocalModule"; |
||||
|
public const string AbpCliCommandsListModules = "AbpCli.Comands.ListModules"; |
||||
|
public const string AbpCliRun = "AbpCli.Run"; |
||||
|
public const string AbpCliExit = "AbpCli.Exit"; |
||||
|
public const string ApplicationRun = "Application.Run"; |
||||
|
public const string AbpStudioBrowserOpen = "AbpStudio.Browser.Open"; |
||||
|
public const string Error = "Error"; |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
public static class ActivityPropertyNames |
||||
|
{ |
||||
|
public const string SessionId = "SessionId"; |
||||
|
public const string ActivityName = "ActivityName"; |
||||
|
public const string Error = "Error"; |
||||
|
public const string ErrorDetail = "ErrorDetail"; |
||||
|
public const string Id = "Id"; |
||||
|
public const string UserId = "UserId"; |
||||
|
public const string OrganizationId = "OrganizationId"; |
||||
|
public const string IpAddress = "IpAddress"; |
||||
|
public const string IsFirstSession = "IsFirstSession"; |
||||
|
public const string DeviceId = "DeviceId"; |
||||
|
public const string DeviceLanguage = "DeviceLanguage"; |
||||
|
public const string OperatingSystem = "OperatingSystem"; |
||||
|
public const string CountryIsoCode = "CountryIsoCode"; |
||||
|
public const string InstalledSoftwares = "InstalledSoftwares"; |
||||
|
public const string ControllerCount = "ControllerCount"; |
||||
|
public const string EntityCount = "EntityCount"; |
||||
|
public const string ProjectCount = "ProjectCount"; |
||||
|
public const string ModuleCount = "ModuleCount"; |
||||
|
public const string PermissionCount = "PermissionCount"; |
||||
|
public const string AppServiceCount = "AppServiceCount"; |
||||
|
public const string ProjectType = "ProjectType"; |
||||
|
public const string ProjectId = "ProjectId"; |
||||
|
public const string SolutionId = "SolutionId"; |
||||
|
public const string Template = "Template"; |
||||
|
public const string CreatedAbpStudioVersion = "CreatedAbpStudioVersion"; |
||||
|
public const string IsTiered = "IsTiered"; |
||||
|
public const string UiFramework = "UiFramework"; |
||||
|
public const string DatabaseProvider = "DatabaseProvider"; |
||||
|
public const string DatabaseManagementSystem = "DatabaseManagementSystem"; |
||||
|
public const string IsSeparateTenantSchema = "IsSeparateTenantSchema"; |
||||
|
public const string Theme = "Theme"; |
||||
|
public const string ThemeStyle = "ThemeStyle"; |
||||
|
public const string MobileFramework = "MobileFramework"; |
||||
|
public const string HasPublicWebsite = "HasPublicWebsite"; |
||||
|
public const string IncludeTests = "IncludeTests"; |
||||
|
public const string MultiTenancy = "MultiTenancy"; |
||||
|
public const string DynamicLocalization = "DynamicLocalization"; |
||||
|
public const string KubernetesConfiguration = "KubernetesConfiguration"; |
||||
|
public const string GrafanaDashboard = "GrafanaDashboard"; |
||||
|
public const string SocialLogins = "SocialLogins"; |
||||
|
public const string InstalledModules = "InstalledModules"; |
||||
|
public const string SolutionPath = "SolutionPath"; |
||||
|
public const string LicenseType = "LicenseType"; |
||||
|
public const string SessionType = "SessionType"; |
||||
|
public const string HasError = "HasError"; |
||||
|
public const string ActivityDuration = "ActivityDuration"; |
||||
|
public const string ActivityDetails = "ActivityDetails"; |
||||
|
public const string Time = "Time"; |
||||
|
public const string SoftwareName = "Name"; |
||||
|
public const string SoftwareVersion = "Version"; |
||||
|
public const string SoftwareUiTheme = "UiTheme"; |
||||
|
public const string SoftwareType = "SoftwareType"; |
||||
|
public const string WebFramework = "WebFramework"; |
||||
|
public const string Dbms = "Dbms"; |
||||
|
public const string UiTheme = "UiTheme"; |
||||
|
public const string UiThemeStyle = "UiThemeStyle"; |
||||
|
public const string MobileApp = "MobileApp"; |
||||
|
public const string SampleCrudPage = "SampleCrudPage"; |
||||
|
public const string FirstAbpVersion = "FirstAbpVersion"; |
||||
|
public const string FirstDotnetVersion = "FirstDotnetVersion"; |
||||
|
public const string CreationTool = "CreationTool"; |
||||
|
public const string ModuleName = "ModuleName"; |
||||
|
public const string ModuleVersion = "ModuleVersion"; |
||||
|
public const string ModuleInstallationTime = "ModuleInstallationTime"; |
||||
|
public const string ExtraProperties = "ExtraProperties"; |
||||
|
public const string HasSolutionInfo = "HasSolutionInfo"; |
||||
|
public const string HasDeviceInfo = "HasDeviceInfo"; |
||||
|
public const string HasProjectInfo = "HasProjectInfo"; |
||||
|
public const string ErrorMessage = "ErrorMessage"; |
||||
|
public const string FailingActivity = "FailingActivity"; |
||||
|
public const string OperatingSystemArchitecture = "OperatingSystemArchitecture"; |
||||
|
public const string AdditionalProperties = "AdditionalProperties"; |
||||
|
} |
||||
@ -0,0 +1,260 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
static internal class DeviceManager |
||||
|
{ |
||||
|
public static string GetUniquePhysicalKey(bool shouldHash) |
||||
|
{ |
||||
|
char platformId = '?'; |
||||
|
char osArchitecture = '?'; |
||||
|
string operatingSystem = "?"; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
string osPrefix; |
||||
|
string uniqueKey; |
||||
|
|
||||
|
platformId = GetPlatformIdOrDefault(); |
||||
|
osArchitecture = GetOsArchitectureOrDefault(); |
||||
|
|
||||
|
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform |
||||
|
.Windows)) |
||||
|
{ |
||||
|
operatingSystem = "Windows"; |
||||
|
uniqueKey = GetUniqueKeyForWindows(); |
||||
|
osPrefix = "W"; |
||||
|
} |
||||
|
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices |
||||
|
.OSPlatform.Linux)) |
||||
|
{ |
||||
|
operatingSystem = "Linux"; |
||||
|
uniqueKey = GetHarddiskSerialForLinux(); |
||||
|
osPrefix = "L"; |
||||
|
} |
||||
|
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices |
||||
|
.OSPlatform.OSX)) //MAC
|
||||
|
{ |
||||
|
operatingSystem = "OSX"; |
||||
|
uniqueKey = GetHarddiskSerialForOsX(); |
||||
|
osPrefix = "O"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
operatingSystem = "Other"; |
||||
|
uniqueKey = GetNetworkAdapterSerial(); |
||||
|
osPrefix = "X"; |
||||
|
} |
||||
|
|
||||
|
if (shouldHash) |
||||
|
{ |
||||
|
uniqueKey = ConvertToMd5(uniqueKey).ToUpperInvariant(); |
||||
|
} |
||||
|
|
||||
|
return osPrefix + platformId + osArchitecture + "-" + uniqueKey; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return Guid.NewGuid().ToString(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string GetNetworkAdapterSerial() |
||||
|
{ |
||||
|
string macAddress = string.Empty; |
||||
|
|
||||
|
var networkInterfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); |
||||
|
foreach (var networkInterface in networkInterfaces) |
||||
|
{ |
||||
|
if (networkInterface.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Loopback) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var physicalAddress = networkInterface.GetPhysicalAddress().ToString(); |
||||
|
if (string.IsNullOrEmpty(physicalAddress)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
macAddress = physicalAddress; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return macAddress!; |
||||
|
} |
||||
|
|
||||
|
private static char GetPlatformIdOrDefault(char defaultValue = '*') |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return ((int)System.Environment.OSVersion.Platform).ToString()[0]; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return defaultValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string ConvertToMd5(string text) |
||||
|
{ |
||||
|
using (var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider()) |
||||
|
{ |
||||
|
return EncodeBase64(md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(text))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string EncodeBase64(byte[] ba) |
||||
|
{ |
||||
|
var hex = new System.Text.StringBuilder(ba.Length * 2); |
||||
|
|
||||
|
foreach (var b in ba) |
||||
|
{ |
||||
|
hex.AppendFormat("{0:x2}", b); |
||||
|
} |
||||
|
|
||||
|
return hex.ToString(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static char GetOsArchitectureOrDefault(char defaultValue = '*') |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return ((int)System.Runtime.InteropServices.RuntimeInformation.OSArchitecture).ToString()[0]; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return defaultValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string GetUniqueKeyForWindows() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return GetProcessorIdForWindows(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
return GetWindowsMachineUniqueId(); |
||||
|
} |
||||
|
|
||||
|
private static string GetProcessorIdForWindows() |
||||
|
{ |
||||
|
using (var managementObjectSearcher = |
||||
|
new System.Management.ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor")) |
||||
|
{ |
||||
|
using (var searcherObj = managementObjectSearcher.Get()) |
||||
|
{ |
||||
|
if (searcherObj.Count == 0) |
||||
|
{ |
||||
|
throw new System.Exception("No unique computer ID found for this computer!"); |
||||
|
} |
||||
|
|
||||
|
var managementObjectEnumerator = searcherObj.GetEnumerator(); |
||||
|
managementObjectEnumerator.MoveNext(); |
||||
|
return managementObjectEnumerator.Current.GetPropertyValue("ProcessorId").ToString()!; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string GetWindowsMachineUniqueId() |
||||
|
{ |
||||
|
return RunCommandAndGetOutput("powershell (Get-CimInstance -Class Win32_ComputerSystemProduct).UUID"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static string GetHarddiskSerialForLinux() |
||||
|
{ |
||||
|
return RunCommandAndGetOutput( |
||||
|
"udevadm info --query=all --name=/dev/sda | grep ID_SERIAL_SHORT | tr -d \"ID_SERIAL_SHORT=:\""); |
||||
|
} |
||||
|
|
||||
|
private static string GetHarddiskSerialForOsX() |
||||
|
{ |
||||
|
var command = |
||||
|
"ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ { split($0, line, \"\\\"\"); printf(\"%s\\n\", line[4]); }'"; |
||||
|
|
||||
|
command = System.Text.RegularExpressions.Regex.Replace(command, @"(\\*)" + "\"", @"$1$1\" + "\""); |
||||
|
|
||||
|
return RunCommandAndGetOutput(command); |
||||
|
} |
||||
|
|
||||
|
private static string RunCommandAndGetOutput(string command) |
||||
|
{ |
||||
|
var output = ""; |
||||
|
|
||||
|
using (var process = new System.Diagnostics.Process()) |
||||
|
{ |
||||
|
process.StartInfo = new System.Diagnostics.ProcessStartInfo(GetFileName()) |
||||
|
{ |
||||
|
Arguments = GetArguments(command), |
||||
|
UseShellExecute = false, |
||||
|
CreateNoWindow = true, |
||||
|
RedirectStandardOutput = true, |
||||
|
RedirectStandardError = true |
||||
|
}; |
||||
|
|
||||
|
process.Start(); |
||||
|
process?.WaitForExit(); |
||||
|
|
||||
|
using (var stdOut = process!.StandardOutput) |
||||
|
{ |
||||
|
using (var stdErr = process.StandardError) |
||||
|
{ |
||||
|
output = stdOut.ReadToEnd(); |
||||
|
output += stdErr.ReadToEnd(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return output.Trim(); |
||||
|
} |
||||
|
|
||||
|
private static string GetFileName() |
||||
|
{ |
||||
|
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( |
||||
|
System.Runtime.InteropServices.OSPlatform.OSX) || |
||||
|
System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform |
||||
|
.Linux)) |
||||
|
{ |
||||
|
string[] fileNames = { "/bin/bash", "/usr/bin/bash", "/bin/sh", "/usr/bin/sh" }; |
||||
|
foreach (var fileName in fileNames) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (System.IO.File.Exists(fileName)) |
||||
|
{ |
||||
|
return fileName; |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignore
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return "/bin/bash"; |
||||
|
} |
||||
|
|
||||
|
//Windows default.
|
||||
|
return "cmd.exe"; |
||||
|
} |
||||
|
|
||||
|
private static string GetArguments(string command) |
||||
|
{ |
||||
|
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( |
||||
|
System.Runtime.InteropServices.OSPlatform.OSX) || |
||||
|
System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform |
||||
|
.Linux)) |
||||
|
{ |
||||
|
return "-c \"" + command + "\""; |
||||
|
} |
||||
|
|
||||
|
//Windows default.
|
||||
|
return "/C \"" + command + "\""; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
public enum AbpTool : byte |
||||
|
{ |
||||
|
Unknown = 0, |
||||
|
StudioUI = 1, |
||||
|
StudioCli = 2, |
||||
|
OldCli = 3 |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
public enum OperationSystem |
||||
|
{ |
||||
|
Unknown = 0, |
||||
|
Windows = 1, |
||||
|
MacOS = 2, |
||||
|
Linux = 3, |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
public enum SessionType |
||||
|
{ |
||||
|
Unknown = 0, |
||||
|
AbpStudio = 1, |
||||
|
AbpCli = 2, |
||||
|
ApplicationRuntime = 3 |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
public enum SoftwareType : byte |
||||
|
{ |
||||
|
Others = 0, |
||||
|
AbpStudio = 1, |
||||
|
DotnetSdk = 2, |
||||
|
OperatingSystem = 3, |
||||
|
Ide = 4, |
||||
|
Browser = 5 |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
public class TelemetryConsts |
||||
|
{ |
||||
|
public const string VoloNameSpaceFilter = "Volo."; |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
public static class TelemetryPaths |
||||
|
{ |
||||
|
public static string AccessToken => Path.Combine(AbpRootPath, "cli", "access-token.bin"); |
||||
|
public static string ComputerId => Path.Combine(AbpRootPath, "cli", "computer-id.bin"); |
||||
|
public static string ActivityStorage => Path.Combine(AbpRootPath , "telemetry", "activity-storage.bin"); |
||||
|
public static string Studio => Path.Combine(AbpRootPath, "studio"); |
||||
|
private readonly static string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp"); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
|
||||
|
internal interface ISoftwareDetector |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
Task<SoftwareInfo?> DetectAsync(); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
|
||||
|
internal interface ISoftwareInfoProvider |
||||
|
{ |
||||
|
Task<List<SoftwareInfo>> GetSoftwareInfoAsync(); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
|
||||
|
internal class SoftwareInfo(string name, string? version, string? uiTheme, SoftwareType softwareType) |
||||
|
{ |
||||
|
public string Name { get; set; } = name; |
||||
|
public string? Version { get; set; } = version; |
||||
|
public string? UiTheme { get; set; } = uiTheme; |
||||
|
public SoftwareType SoftwareType { get; set; } = softwareType; |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
[ExposeServices(typeof(ISoftwareDetector))] |
||||
|
abstract internal class SoftwareDetector: ISoftwareDetector , ISingletonDependency |
||||
|
{ |
||||
|
public abstract string Name { get; } |
||||
|
public abstract Task<SoftwareInfo?> DetectAsync(); |
||||
|
|
||||
|
protected virtual async Task<string?> ExecuteCommandAsync(string command, string? arg) |
||||
|
{ |
||||
|
var outputBuilder = new StringBuilder(); |
||||
|
|
||||
|
var processStartInfo = new ProcessStartInfo |
||||
|
{ |
||||
|
FileName = command, |
||||
|
Arguments = arg ?? "", |
||||
|
RedirectStandardOutput = true, |
||||
|
RedirectStandardError = true, |
||||
|
UseShellExecute = false, |
||||
|
CreateNoWindow = true |
||||
|
}; |
||||
|
|
||||
|
using var process = new Process(); |
||||
|
process.StartInfo = processStartInfo; |
||||
|
process.EnableRaisingEvents = true; |
||||
|
|
||||
|
var tcs = new TaskCompletionSource<bool>(); |
||||
|
|
||||
|
process.OutputDataReceived += (sender, e) => |
||||
|
{ |
||||
|
if (e.Data != null) |
||||
|
{ |
||||
|
outputBuilder.AppendLine(e.Data); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
process.ErrorDataReceived += (sender, e) => |
||||
|
{ |
||||
|
if (e.Data != null) |
||||
|
{ |
||||
|
outputBuilder.AppendLine(e.Data); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
process.Exited += (sender, e) => |
||||
|
{ |
||||
|
tcs.TrySetResult(true); |
||||
|
}; |
||||
|
|
||||
|
process.Start(); |
||||
|
process.BeginOutputReadLine(); |
||||
|
process.BeginErrorReadLine(); |
||||
|
|
||||
|
await tcs.Task; |
||||
|
|
||||
|
var output = outputBuilder.ToString().Trim(); |
||||
|
return string.IsNullOrWhiteSpace(output) ? null : output; |
||||
|
} |
||||
|
|
||||
|
protected string? GetFileVersion(string filePath) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var versionInfo = FileVersionInfo.GetVersionInfo(filePath); |
||||
|
return versionInfo.FileVersion; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return string.Empty; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
using System.IO; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class AbpStudioDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Abp Studio"; |
||||
|
private const string AbpStudioVersionExtensionName = "Volo.Abp.Studio.Extensions.StandardSolutionTemplates"; |
||||
|
|
||||
|
public override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var uiTheme = GetAbpStudioUiTheme(); |
||||
|
var version = GetAbpStudioVersion(); |
||||
|
|
||||
|
return Task.FromResult<SoftwareInfo?>(new SoftwareInfo(Name, version, uiTheme, SoftwareType.AbpStudio)); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return Task.FromResult<SoftwareInfo?>(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private string? GetAbpStudioUiTheme() |
||||
|
{ |
||||
|
var ideStateJsonPath = Path.Combine( |
||||
|
TelemetryPaths.Studio, |
||||
|
"ui", |
||||
|
"ide-state.json" |
||||
|
); |
||||
|
if (!File.Exists(ideStateJsonPath)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
using var fs = new FileStream(ideStateJsonPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); |
||||
|
using var doc = JsonDocument.Parse(fs); |
||||
|
|
||||
|
return doc.RootElement.TryGetProperty("theme", out var themeElement) ? themeElement.GetString() : null; |
||||
|
} |
||||
|
|
||||
|
private string? GetAbpStudioVersion() |
||||
|
{ |
||||
|
var extensionsFilePath = Path.Combine(TelemetryPaths.Studio, "extensions.json"); |
||||
|
|
||||
|
if (!File.Exists(extensionsFilePath)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
using var fs = new FileStream(extensionsFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); |
||||
|
using var doc = JsonDocument.Parse(fs); |
||||
|
|
||||
|
if (doc.RootElement.TryGetProperty("Extensions", out var extensionsElement) && |
||||
|
extensionsElement.ValueKind == JsonValueKind.Array) |
||||
|
{ |
||||
|
foreach (var extension in extensionsElement.EnumerateArray()) |
||||
|
{ |
||||
|
if (extension.TryGetProperty("name", out var nameProp) && |
||||
|
nameProp.GetString() == AbpStudioVersionExtensionName && |
||||
|
extension.TryGetProperty("version", out var versionProp)) |
||||
|
{ |
||||
|
return versionProp.GetString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class ChromeDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Chrome"; |
||||
|
|
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
var chromePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google", "Chrome", "Application", "chrome.exe"); |
||||
|
if (File.Exists(chromePath)) |
||||
|
{ |
||||
|
return new SoftwareInfo(Name, GetFileVersion(chromePath), null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
var chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; |
||||
|
if (File.Exists(chromePath)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync(chromePath, "--version"); |
||||
|
return new SoftwareInfo(Name, version, null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
||||
|
{ |
||||
|
var chromePath = "/usr/bin/google-chrome"; |
||||
|
if (File.Exists(chromePath)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync(chromePath, "--version"); |
||||
|
return new SoftwareInfo(Name, version, null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class DotnetSdkDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "DotnetSdk"; |
||||
|
|
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
return new SoftwareInfo(Name, Environment.Version.ToString(), null, SoftwareType.DotnetSdk); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class FireFoxDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Firefox"; |
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
var firefoxPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Mozilla Firefox", "firefox.exe"); |
||||
|
if (File.Exists(firefoxPath)) |
||||
|
{ |
||||
|
return new SoftwareInfo(Name, GetFileVersion(firefoxPath), null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
var firefoxPath = "/Applications/Firefox.app/Contents/MacOS/firefox"; |
||||
|
if (File.Exists(firefoxPath)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync(firefoxPath, "--version"); |
||||
|
return new SoftwareInfo(Name, version, null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
||||
|
{ |
||||
|
var firefoxPath = "/usr/bin/firefox"; |
||||
|
if (File.Exists(firefoxPath)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync(firefoxPath, "--version"); |
||||
|
return new SoftwareInfo(Name, version, null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class MsEdgeDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "MsEdge"; |
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
var firefoxPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), |
||||
|
"Microsoft", "Edge", "Application", "msedge.exe"); |
||||
|
|
||||
|
if (File.Exists(firefoxPath)) |
||||
|
{ |
||||
|
return new SoftwareInfo(Name, GetFileVersion(firefoxPath), null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
var edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; |
||||
|
if (File.Exists(edgePath)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync(edgePath, "--version"); |
||||
|
return new SoftwareInfo(Name, version, null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
||||
|
{ |
||||
|
var edgePath = "/usr/bin/microsoft-edge"; |
||||
|
if (File.Exists(edgePath)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync(edgePath, "--version"); |
||||
|
return new SoftwareInfo(Name, version, null, SoftwareType.Browser); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal class NodeJsDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Node.js"; |
||||
|
|
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var output = await ExecuteCommandAsync("node", "-v"); |
||||
|
|
||||
|
if (output.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var version = output.Trim().TrimStart('v'); |
||||
|
|
||||
|
return new SoftwareInfo(Name, version, uiTheme: null, SoftwareType.Others); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class OperatingSystemDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : |
||||
|
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "macOS" : "Linux"; |
||||
|
|
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
return new SoftwareInfo(Name, Environment.OSVersion.Version.ToString(), null, SoftwareType.OperatingSystem); |
||||
|
} |
||||
|
|
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync("sw_vers", "-productVersion"); |
||||
|
return new SoftwareInfo(Name, version, GetMacUiTheme(), SoftwareType.OperatingSystem); |
||||
|
} |
||||
|
|
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
||||
|
{ |
||||
|
var version = await ExecuteCommandAsync("lsb_release", "-ds") ?? await ExecuteCommandAsync("uname", "-r"); |
||||
|
return new SoftwareInfo(Name, version, await GetLinuxUiTheme(), SoftwareType.OperatingSystem); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private async Task<string?> GetLinuxUiTheme() |
||||
|
{ |
||||
|
var output = await ExecuteCommandAsync("gsettings", "get org.gnome.desktop.interface gtk-theme"); |
||||
|
|
||||
|
if (!output.IsNullOrWhiteSpace() && output.ToLowerInvariant().Contains("dark")) |
||||
|
{ |
||||
|
return "Dark"; |
||||
|
} |
||||
|
|
||||
|
return "Light"; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private string? GetMacUiTheme() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var process = new Process(); |
||||
|
process.StartInfo = new ProcessStartInfo |
||||
|
{ |
||||
|
FileName = "defaults", |
||||
|
Arguments = "read -g AppleInterfaceStyle", |
||||
|
RedirectStandardOutput = true, |
||||
|
RedirectStandardError = true, |
||||
|
UseShellExecute = false, |
||||
|
CreateNoWindow = true |
||||
|
}; |
||||
|
|
||||
|
process.Start(); |
||||
|
var output = process.StandardOutput.ReadToEnd().Trim(); |
||||
|
process.WaitForExit(); |
||||
|
|
||||
|
return output == "Dark" ? "Dark" : "Light"; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return "Light"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Xml.Linq; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class RiderDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Rider"; |
||||
|
|
||||
|
public override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
string baseConfigDir; |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
baseConfigDir = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), |
||||
|
"JetBrains"); |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
baseConfigDir = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal), |
||||
|
"Library", "Application Support", "JetBrains"); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
baseConfigDir = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal), |
||||
|
".config", "JetBrains"); |
||||
|
} |
||||
|
|
||||
|
if (!Directory.Exists(baseConfigDir)) |
||||
|
{ |
||||
|
return Task.FromResult<SoftwareInfo?>(null); |
||||
|
} |
||||
|
|
||||
|
var riderDirs = Directory |
||||
|
.GetDirectories(baseConfigDir, "Rider*") |
||||
|
.Select(dir => |
||||
|
{ |
||||
|
var name = Path.GetFileName(dir); |
||||
|
var verStr = name.Substring("Rider".Length); |
||||
|
return Version.TryParse(verStr, out var v) |
||||
|
? (Path: dir, Version: v) |
||||
|
: (Path: null, Version: null); |
||||
|
}) |
||||
|
.Where(x => x.Path != null) |
||||
|
.ToList(); |
||||
|
|
||||
|
if (!riderDirs.Any()) |
||||
|
{ |
||||
|
return Task.FromResult<SoftwareInfo?>(null); |
||||
|
} |
||||
|
|
||||
|
var latest = riderDirs |
||||
|
.OrderByDescending(x => x.Version) |
||||
|
.First(); |
||||
|
|
||||
|
var theme = string.Empty; |
||||
|
var colorsFile = Path.Combine(latest.Path!, "options", "colors.scheme.xml"); |
||||
|
if (File.Exists(colorsFile)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var doc = XDocument.Load(colorsFile); |
||||
|
var schemeEl = doc |
||||
|
.Descendants("global_color_scheme") |
||||
|
.FirstOrDefault(); |
||||
|
var schemeName = schemeEl?.Attribute("name")?.Value; |
||||
|
if (!schemeName.IsNullOrEmpty()) |
||||
|
{ |
||||
|
theme = schemeName.IndexOf("dark", StringComparison.OrdinalIgnoreCase) >= 0 |
||||
|
? "Dark" |
||||
|
: "Light"; |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult<SoftwareInfo?>(new SoftwareInfo(Name, latest.Version?.ToString(), theme, |
||||
|
SoftwareType.Ide)); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
return Task.FromResult<SoftwareInfo?>(null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class VisualStudioCodeDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Visual Studio Code"; |
||||
|
|
||||
|
public async override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
string? installDir = null; |
||||
|
string? settingsPath = null; |
||||
|
|
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); |
||||
|
var progFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); |
||||
|
var candidates = new[] |
||||
|
{ |
||||
|
Path.Combine(localAppData, "Programs", "Microsoft VS Code"), |
||||
|
Path.Combine(progFiles, "Microsoft VS Code") |
||||
|
}; |
||||
|
installDir = candidates.FirstOrDefault(Directory.Exists); |
||||
|
|
||||
|
settingsPath = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), |
||||
|
"Code", "User", "globalStorage" ,"storage.json" |
||||
|
); |
||||
|
} |
||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
||||
|
{ |
||||
|
var app = "/Applications/Visual Studio Code.app"; |
||||
|
if (Directory.Exists(app)) |
||||
|
{ |
||||
|
installDir = app; |
||||
|
} |
||||
|
|
||||
|
settingsPath = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal), |
||||
|
"Library", "Application Support", "Code", "User", "globalStorage", "storage.json" |
||||
|
); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var candidate = "/usr/share/code"; |
||||
|
if (Directory.Exists(candidate)) |
||||
|
{ |
||||
|
installDir = candidate; |
||||
|
} |
||||
|
|
||||
|
settingsPath = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal), |
||||
|
".config", "Code", "User", "globalStorage", "storage.json" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (installDir == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
Version? version = null; |
||||
|
var productJson = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) |
||||
|
? Path.Combine(installDir, "Contents", "Resources", "app", "product.json") |
||||
|
: Path.Combine(installDir, "resources", "app", "product.json"); |
||||
|
|
||||
|
if (File.Exists(productJson)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var jsonDoc = JsonDocument.Parse(File.ReadAllText(productJson)); |
||||
|
var root = jsonDoc.RootElement; |
||||
|
if (root.TryGetProperty("version", out var versionProp)) |
||||
|
{ |
||||
|
var versionStr = versionProp.GetString(); |
||||
|
if (Version.TryParse(versionStr, out var v)) |
||||
|
{ |
||||
|
version = v; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (version == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var theme = "Unknown"; |
||||
|
|
||||
|
if (File.Exists(settingsPath)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var json = JsonDocument.Parse( File.ReadAllText(settingsPath)); |
||||
|
var root = json.RootElement; |
||||
|
if (root.TryGetProperty("theme", out var themeProp)) |
||||
|
{ |
||||
|
var themeName = themeProp.GetString() ?? ""; |
||||
|
if (themeName.IndexOf("dark", StringComparison.OrdinalIgnoreCase) >= 0) |
||||
|
{ |
||||
|
theme = "Dark"; |
||||
|
} |
||||
|
else if (themeName.IndexOf("light", StringComparison.OrdinalIgnoreCase) >= 0) |
||||
|
{ |
||||
|
theme = "Light"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// ignored
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new SoftwareInfo(Name, version?.ToString(), theme, SoftwareType.Ide); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Xml.Linq; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants.Enums; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; |
||||
|
|
||||
|
internal sealed class VisualStudioDetector : SoftwareDetector |
||||
|
{ |
||||
|
public override string Name => "Visual Studio"; |
||||
|
|
||||
|
public override Task<SoftwareInfo?> DetectAsync() |
||||
|
{ |
||||
|
var version = GetVisualStudioVersionViaVsWhere(); |
||||
|
var theme = GetVisualStudioTheme(); |
||||
|
|
||||
|
if (version == null) |
||||
|
{ |
||||
|
return Task.FromResult<SoftwareInfo?>(null); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult<SoftwareInfo?>(new SoftwareInfo( |
||||
|
name: Name, |
||||
|
version: version, |
||||
|
uiTheme: theme, |
||||
|
softwareType: SoftwareType.Ide)); |
||||
|
} |
||||
|
|
||||
|
private string? GetVisualStudioVersionViaVsWhere() |
||||
|
{ |
||||
|
var vswherePath = Path.Combine( |
||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), |
||||
|
"Microsoft Visual Studio", |
||||
|
"Installer", |
||||
|
"vswhere.exe"); |
||||
|
|
||||
|
if (!File.Exists(vswherePath)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var process = new Process |
||||
|
{ |
||||
|
StartInfo = new ProcessStartInfo |
||||
|
{ |
||||
|
FileName = vswherePath, |
||||
|
Arguments = "-latest -property catalog_productDisplayVersion", |
||||
|
RedirectStandardOutput = true, |
||||
|
UseShellExecute = false, |
||||
|
CreateNoWindow = true |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
process.Start(); |
||||
|
var output = process.StandardOutput.ReadToEnd().Trim(); |
||||
|
process.WaitForExit(); |
||||
|
|
||||
|
return string.IsNullOrWhiteSpace(output) ? null : output; |
||||
|
} |
||||
|
|
||||
|
private string? GetVisualStudioTheme() |
||||
|
{ |
||||
|
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); |
||||
|
|
||||
|
var vsSettingsDir = Path.Combine(localAppData, "Microsoft", "VisualStudio"); |
||||
|
|
||||
|
if (!Directory.Exists(vsSettingsDir)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var settingsPath = Directory.GetFiles(vsSettingsDir, "CurrentSettings*.vssettings", SearchOption.AllDirectories) |
||||
|
.OrderByDescending(File.GetLastWriteTime) |
||||
|
.FirstOrDefault(); |
||||
|
|
||||
|
if (string.IsNullOrEmpty(settingsPath)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var doc = XDocument.Load(settingsPath); |
||||
|
|
||||
|
var themeId = doc.Descendants("Theme") |
||||
|
.FirstOrDefault()?.Attribute("Id")?.Value; |
||||
|
|
||||
|
return themeId?.ToUpperInvariant() switch |
||||
|
{ |
||||
|
"{1DED0138-47CE-435E-84EF-9EC1F439B749}" => "Dark", |
||||
|
"{DE3DBBCD-F642-433C-8353-8F1DF4370ABA}" => "Light", |
||||
|
"{2DED0138-47CE-435E-84EF-9EC1F439B749}" => "Blue", |
||||
|
_ => "Unknown" |
||||
|
}; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Providers; |
||||
|
|
||||
|
internal class SoftwareInfoProvider : ISoftwareInfoProvider , ISingletonDependency |
||||
|
{ |
||||
|
private readonly IEnumerable<ISoftwareDetector> _softwareDetectors; |
||||
|
|
||||
|
public SoftwareInfoProvider(IEnumerable<ISoftwareDetector> softwareDetectors) |
||||
|
{ |
||||
|
_softwareDetectors = softwareDetectors; |
||||
|
} |
||||
|
|
||||
|
public async Task<List<SoftwareInfo>> GetSoftwareInfoAsync() |
||||
|
{ |
||||
|
var result = new List<SoftwareInfo>(); |
||||
|
|
||||
|
foreach (var softwareDetector in _softwareDetectors) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var softwareInfo = await softwareDetector.DetectAsync(); |
||||
|
if (softwareInfo is not null) |
||||
|
{ |
||||
|
result.Add(softwareInfo); |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
|
||||
|
static internal class AbpProjectMetadataReader |
||||
|
{ |
||||
|
private const string AbpPackageSearchPattern = "*.abppkg"; |
||||
|
private const string AbpSolutionSearchPattern = "*.abpsln"; |
||||
|
private const int MaxDepth = 5; |
||||
|
public static AbpProjectMetaData? ReadProjectMetadata(Assembly assembly) |
||||
|
{ |
||||
|
var assemblyPath = assembly.Location; |
||||
|
try |
||||
|
{ |
||||
|
var projectDirectory = Path.GetDirectoryName(assemblyPath); |
||||
|
if (projectDirectory == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var abpPackagePath = FindFileUpwards(projectDirectory, AbpPackageSearchPattern); |
||||
|
|
||||
|
if (abpPackagePath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var projectMetaData = ReadOrCreateMetadata(abpPackagePath); |
||||
|
|
||||
|
var abpSolutionPath = FindFileUpwards(projectDirectory, AbpSolutionSearchPattern); |
||||
|
|
||||
|
if (!abpSolutionPath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
projectMetaData.AbpSlnPath = abpSolutionPath; |
||||
|
} |
||||
|
|
||||
|
return projectMetaData; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static AbpProjectMetaData ReadOrCreateMetadata(string packagePath) |
||||
|
{ |
||||
|
|
||||
|
var fileContent = File.ReadAllText(packagePath); |
||||
|
var metadata = new AbpProjectMetaData(); |
||||
|
|
||||
|
using var document = JsonDocument.Parse(fileContent); |
||||
|
var root = document.RootElement; |
||||
|
|
||||
|
if (TryGetProjectId(root,out var projectId)) |
||||
|
{ |
||||
|
metadata.ProjectId = projectId; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
metadata.ProjectId = Guid.NewGuid(); |
||||
|
WriteProjectIdToPackageFile(root, packagePath, metadata.ProjectId.Value); |
||||
|
} |
||||
|
|
||||
|
if (root.TryGetProperty("role", out var roleElement) && |
||||
|
roleElement.ValueKind == JsonValueKind.String) |
||||
|
{ |
||||
|
metadata.Role = roleElement.GetString()!; |
||||
|
} |
||||
|
|
||||
|
return metadata; |
||||
|
} |
||||
|
|
||||
|
private static void WriteProjectIdToPackageFile(JsonElement root, string packagePath, Guid projectId) |
||||
|
{ |
||||
|
using var stream = new MemoryStream(); |
||||
|
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true })) |
||||
|
{ |
||||
|
writer.WriteStartObject(); |
||||
|
|
||||
|
if (root.ValueKind == JsonValueKind.Object) |
||||
|
{ |
||||
|
foreach (var property in root.EnumerateObject()) |
||||
|
{ |
||||
|
if (property.Name != "projectId") |
||||
|
{ |
||||
|
property.WriteTo(writer); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writer.WriteString("projectId", projectId.ToString()); |
||||
|
writer.WriteEndObject(); |
||||
|
} |
||||
|
|
||||
|
var json = Encoding.UTF8.GetString(stream.ToArray()); |
||||
|
File.WriteAllText(packagePath, json); |
||||
|
} |
||||
|
|
||||
|
private static string? FindFileUpwards(string startingDir, string searchPattern) |
||||
|
{ |
||||
|
var currentDir = new DirectoryInfo(startingDir); |
||||
|
var currentDepth = 0; |
||||
|
|
||||
|
while (currentDir != null && currentDepth < MaxDepth) |
||||
|
{ |
||||
|
var file = currentDir.GetFiles(searchPattern).FirstOrDefault(); |
||||
|
if (file != null) |
||||
|
{ |
||||
|
return file.FullName; |
||||
|
} |
||||
|
|
||||
|
currentDir = currentDir.Parent; |
||||
|
currentDepth++; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
private static bool TryGetProjectId(JsonElement element, out Guid projectId) |
||||
|
{ |
||||
|
if (element.TryGetProperty("projectId", out var projectIdElement) && |
||||
|
projectIdElement.ValueKind == JsonValueKind.String && |
||||
|
Guid.TryParse(projectIdElement.GetString(), out projectId)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
projectId = Guid.Empty; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
|
||||
|
internal class AbpProjectMetaData |
||||
|
{ |
||||
|
public Guid? ProjectId { get; set; } |
||||
|
public string? Role { get; set; } |
||||
|
public string? AbpSlnPath { get; set; } |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using System; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
|
||||
|
static internal class Cryptography |
||||
|
{ |
||||
|
private const string EncryptionKey = "AbpTelemetryStorageKey"; |
||||
|
|
||||
|
public static string Encrypt(string plainText) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(plainText, nameof(plainText)); |
||||
|
using var aes = Aes.Create(); |
||||
|
using var sha256 = SHA256.Create(); |
||||
|
|
||||
|
aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(EncryptionKey)); |
||||
|
aes.Mode = CipherMode.ECB; |
||||
|
aes.Padding = PaddingMode.PKCS7; |
||||
|
|
||||
|
var encryptor = aes.CreateEncryptor(); |
||||
|
var inputBytes = Encoding.UTF8.GetBytes(plainText); |
||||
|
var encryptedBytes = encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); |
||||
|
return Convert.ToBase64String(encryptedBytes); |
||||
|
} |
||||
|
|
||||
|
public static string Decrypt(string cipherText) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(cipherText, nameof(cipherText)); |
||||
|
using var aes = Aes.Create(); |
||||
|
using var sha256 = SHA256.Create(); |
||||
|
|
||||
|
aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(EncryptionKey)); |
||||
|
aes.Mode = CipherMode.ECB; |
||||
|
aes.Padding = PaddingMode.PKCS7; |
||||
|
|
||||
|
var decryptor = aes.CreateDecryptor(); |
||||
|
var inputBytes = Convert.FromBase64String(cipherText); |
||||
|
var decryptedBytes = decryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); |
||||
|
return Encoding.UTF8.GetString(decryptedBytes); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
|
||||
|
static internal class MutexExecutor |
||||
|
{ |
||||
|
private const string MutexName = "Global\\MyFileReadMutex"; |
||||
|
private const int TimeoutMilliseconds = 3000; |
||||
|
|
||||
|
public static string? ReadFileSafely(string filePath) |
||||
|
{ |
||||
|
using var mutex = new Mutex(false, MutexName); |
||||
|
|
||||
|
if (!mutex.WaitOne(TimeoutMilliseconds)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (!File.Exists(filePath)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return File.ReadAllText(filePath); |
||||
|
} |
||||
|
catch (IOException) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
mutex.ReleaseMutex(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// Already released or abandoned
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry; |
||||
|
|
||||
|
public interface ITelemetryActivitySender |
||||
|
{ |
||||
|
Task TrySendQueuedActivitiesAsync(); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry; |
||||
|
public interface ITelemetryService |
||||
|
{ |
||||
|
IAsyncDisposable TrackActivityAsync(string activityName, Action<Dictionary<string,object>>? additionalProperties = null); |
||||
|
Task AddActivityAsync(string activityName, Action<Dictionary<string,object>>? additionalProperties = null); |
||||
|
Task AddErrorActivityAsync(Action<Dictionary<string, object>> additionalProperties); |
||||
|
Task AddErrorActivityAsync(string errorMessage); |
||||
|
Task AddErrorForActivityAsync(string failingActivity, string errorMessage); |
||||
|
} |
||||
@ -0,0 +1,131 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry; |
||||
|
|
||||
|
public class TelemetryActivitySender : ITelemetryActivitySender, IScopedDependency |
||||
|
{ |
||||
|
private readonly ITelemetryActivityStorage _telemetryActivityStorage; |
||||
|
private readonly ILogger<TelemetryActivitySender> _logger; |
||||
|
|
||||
|
private const int ActivitySendBatchSize = 50; |
||||
|
private const int MaxRetryAttempts = 3; |
||||
|
private const int RetryDelayMilliseconds = 1000; |
||||
|
|
||||
|
public TelemetryActivitySender(ITelemetryActivityStorage telemetryActivityStorage, ILogger<TelemetryActivitySender> logger) |
||||
|
{ |
||||
|
_telemetryActivityStorage = telemetryActivityStorage; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public async Task TrySendQueuedActivitiesAsync() |
||||
|
{ |
||||
|
if (!_telemetryActivityStorage.ShouldSendActivities()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await SendActivitiesAsync(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private async Task SendActivitiesAsync() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var activities = _telemetryActivityStorage.GetActivities(); |
||||
|
var batches = CreateActivityBatches(activities); |
||||
|
|
||||
|
using var httpClient = new HttpClient(); |
||||
|
ConfigureHttpClientAuthentication(httpClient); |
||||
|
|
||||
|
foreach (var batch in batches) |
||||
|
{ |
||||
|
var isSuccessful = await TrySendBatchWithRetriesAsync(httpClient, batch); |
||||
|
|
||||
|
if (!isSuccessful) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private async Task<bool> TrySendBatchWithRetriesAsync(HttpClient httpClient, ActivityEvent[] activities) |
||||
|
{ |
||||
|
var currentAttempt = 0; |
||||
|
|
||||
|
while (currentAttempt < MaxRetryAttempts) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var response = await httpClient.PostAsync($"{AbpPlatformUrls.AbpTelemetryApiUrl}api/telemetry/collect", new StringContent(JsonSerializer.Serialize(activities), Encoding.UTF8, "application/json")); |
||||
|
|
||||
|
if (response.IsSuccessStatusCode) |
||||
|
{ |
||||
|
_telemetryActivityStorage.DeleteActivities(activities); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_logger.LogWithLevel(LogLevel.Trace, |
||||
|
$"Failed to send telemetry activities. Status code: {response.StatusCode}, Reason: {response.ReasonPhrase}"); |
||||
|
_telemetryActivityStorage.MarkActivitiesAsFailed(activities); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogWithLevel(LogLevel.Trace, $"Error sending telemetry activities: {ex.Message}"); |
||||
|
currentAttempt++; |
||||
|
await Task.Delay(currentAttempt * RetryDelayMilliseconds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_logger.LogWithLevel(LogLevel.Trace, "Max retries reached. Failed to send telemetry activities."); |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static IEnumerable<ActivityEvent[]> CreateActivityBatches(List<ActivityEvent> activities) |
||||
|
{ |
||||
|
return activities |
||||
|
.Select((x, i) => new { Index = i, Value = x }) |
||||
|
.GroupBy(x => x.Index / ActivitySendBatchSize) |
||||
|
.Select(x => x.Select(v => v.Value).ToArray()); |
||||
|
} |
||||
|
|
||||
|
private static void ConfigureHttpClientAuthentication(HttpClient httpClient) |
||||
|
{ |
||||
|
if (!File.Exists(TelemetryPaths.AccessToken)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var accessToken = File.ReadAllText(TelemetryPaths.AccessToken, Encoding.UTF8); |
||||
|
|
||||
|
if (accessToken.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using ActivityContext = Volo.Abp.Internal.Telemetry.Activity.ActivityContext; |
||||
|
|
||||
|
namespace Volo.Abp.Internal.Telemetry; |
||||
|
|
||||
|
public class TelemetryService : ITelemetryService, IScopedDependency |
||||
|
{ |
||||
|
private readonly ITelemetryActivitySender _telemetryActivitySender; |
||||
|
private readonly ITelemetryActivityEventBuilder _telemetryActivityEventBuilder; |
||||
|
private readonly ITelemetryActivityStorage _telemetryActivityStorage; |
||||
|
|
||||
|
public TelemetryService(ITelemetryActivitySender telemetryActivitySender, |
||||
|
ITelemetryActivityEventBuilder telemetryActivityEventBuilder, |
||||
|
ITelemetryActivityStorage telemetryActivityStorage) |
||||
|
{ |
||||
|
_telemetryActivitySender = telemetryActivitySender; |
||||
|
_telemetryActivityEventBuilder = telemetryActivityEventBuilder; |
||||
|
_telemetryActivityStorage = telemetryActivityStorage; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public IAsyncDisposable TrackActivityAsync(string activityName, |
||||
|
Action<Dictionary<string, object>>? additionalProperties = null) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(activityName, nameof(activityName)); |
||||
|
var stopwatch = Stopwatch.StartNew(); |
||||
|
var context = ActivityContext.Create(activityName, additionalProperties: additionalProperties); |
||||
|
|
||||
|
return new AsyncDisposeFunc(async () => |
||||
|
{ |
||||
|
stopwatch.Stop(); |
||||
|
context.Current[ActivityPropertyNames.ActivityDuration] = stopwatch.ElapsedMilliseconds; |
||||
|
await AddActivityAsync(context); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task AddActivityAsync(string activityName, |
||||
|
Action<Dictionary<string, object>>? additionalProperties = null) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(activityName, nameof(activityName)); |
||||
|
var context = ActivityContext.Create(activityName, additionalProperties: additionalProperties); |
||||
|
await AddActivityAsync(context); |
||||
|
} |
||||
|
|
||||
|
public async Task AddErrorActivityAsync(Action<Dictionary<string, object>> additionalProperties) |
||||
|
{ |
||||
|
var context = ActivityContext.Create(ActivityNameConsts.Error, additionalProperties: additionalProperties); |
||||
|
await AddActivityAsync(context); |
||||
|
} |
||||
|
|
||||
|
public async Task AddErrorActivityAsync(string errorMessage) |
||||
|
{ |
||||
|
var context = ActivityContext.Create(ActivityNameConsts.Error, errorMessage); |
||||
|
await AddActivityAsync(context); |
||||
|
} |
||||
|
|
||||
|
public async Task AddErrorForActivityAsync(string failingActivity, string errorMessage) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(failingActivity, nameof(failingActivity)); |
||||
|
var context = ActivityContext.Create(ActivityNameConsts.Error, errorMessage, configure => |
||||
|
{ |
||||
|
configure[ActivityPropertyNames.FailingActivity] = failingActivity; |
||||
|
}); |
||||
|
await AddActivityAsync(context); |
||||
|
} |
||||
|
|
||||
|
private Task AddActivityAsync(ActivityContext context) |
||||
|
{ |
||||
|
_ = Task.Run(async () => |
||||
|
{ |
||||
|
await BuildAndSendActivityAsync(context); |
||||
|
}); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
private async Task BuildAndSendActivityAsync(ActivityContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var activityEvent = await _telemetryActivityEventBuilder.BuildAsync(context); |
||||
|
if (activityEvent is null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_telemetryActivityStorage.SaveActivity(activityEvent); |
||||
|
await _telemetryActivitySender.TrySendQueuedActivitiesAsync(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//ignored
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Contracts; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity.Providers; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace Volo.Abp.Domain.Telemetry; |
||||
|
|
||||
|
[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher>))] |
||||
|
public class TelemetryDomainInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher<TelemetryApplicationInfoEnricher> |
||||
|
{ |
||||
|
private readonly ITypeFinder _typeFinder; |
||||
|
|
||||
|
public TelemetryDomainInfoEnricher(ITypeFinder typeFinder, IServiceProvider serviceProvider) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
_typeFinder = typeFinder; |
||||
|
} |
||||
|
|
||||
|
protected override Task<bool> CanExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
return Task.FromResult(context.ProjectId.HasValue); |
||||
|
} |
||||
|
|
||||
|
protected override Task ExecuteAsync(ActivityContext context) |
||||
|
{ |
||||
|
var entityCount = _typeFinder.Types.Count(t => |
||||
|
typeof(IEntity).IsAssignableFrom(t) && !t.IsAbstract && |
||||
|
!t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); |
||||
|
|
||||
|
context.Current[ActivityPropertyNames.EntityCount] = entityCount; |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Internal.Telemetry.Activity; |
||||
|
using Volo.Abp.Internal.Telemetry.Constants; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Telemetry; |
||||
|
|
||||
|
public class ActivityEvent_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Create_ActivityEvent_With_Required_Parameters() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var activityName = "TestActivity"; |
||||
|
var details = "Test Details"; |
||||
|
|
||||
|
// Act
|
||||
|
var activityEvent = new ActivityEvent(activityName, details); |
||||
|
|
||||
|
// Assert
|
||||
|
activityEvent[ActivityPropertyNames.ActivityName].ShouldBe(activityName); |
||||
|
activityEvent[ActivityPropertyNames.ActivityDetails].ShouldBe(details); |
||||
|
activityEvent[ActivityPropertyNames.Id].ShouldNotBe(Guid.Empty); |
||||
|
activityEvent[ActivityPropertyNames.Time].ShouldNotBe(default); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(null)] |
||||
|
[InlineData("")] |
||||
|
[InlineData(" ")] |
||||
|
public void Should_Throw_Exception_When_ActivityName_Is_Invalid(string invalidName) |
||||
|
{ |
||||
|
// Act & Assert
|
||||
|
Should.Throw<ArgumentException>(() => |
||||
|
new ActivityEvent(invalidName)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Set_And_Get_AdditionalProperties() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var activityEvent = new ActivityEvent("TestActivity"); |
||||
|
var additionalProps = new Dictionary<string, object> |
||||
|
{ |
||||
|
{ "key1", "value1" }, |
||||
|
{ "key2", 42 } |
||||
|
}; |
||||
|
|
||||
|
// Act
|
||||
|
activityEvent[ActivityPropertyNames.AdditionalProperties] = additionalProps; |
||||
|
|
||||
|
// Assert
|
||||
|
|
||||
|
var activityAdditionalProps = activityEvent[ActivityPropertyNames.AdditionalProperties] as Dictionary<string, object>; |
||||
|
activityAdditionalProps.ShouldNotBeNull(); |
||||
|
activityAdditionalProps.Count.ShouldBe(2); |
||||
|
activityAdditionalProps["key1"].ShouldBe("value1"); |
||||
|
activityAdditionalProps["key2"].ShouldBe(42); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Return_Default_Values_When_Properties_Not_Set() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var activityEvent = new ActivityEvent("TestActivity"); |
||||
|
|
||||
|
// Assert
|
||||
|
activityEvent[ActivityPropertyNames.ActivityDetails].ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Behave_Like_Dictionary() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var activityEvent = new ActivityEvent("TestActivity"); |
||||
|
|
||||
|
// Act
|
||||
|
activityEvent["CustomKey"] = "CustomValue"; |
||||
|
|
||||
|
// Assert
|
||||
|
activityEvent["CustomKey"].ShouldBe("CustomValue"); |
||||
|
activityEvent.ContainsKey("CustomKey").ShouldBeTrue(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
@ -0,0 +1,52 @@ |
|||||
|
using System; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Internal.Telemetry.Helpers; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Telemetry; |
||||
|
|
||||
|
public class Cryptography_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Encrypt_And_Decrypt_Text_Successfully() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
const string plainText = "Test message 123!"; |
||||
|
|
||||
|
// Act
|
||||
|
var encryptedText = Cryptography.Encrypt(plainText); |
||||
|
var decryptedText = Cryptography.Decrypt(encryptedText); |
||||
|
|
||||
|
// Assert
|
||||
|
decryptedText.ShouldBe(plainText); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Generate_Different_Encrypted_Values_For_Different_Inputs() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
const string text1 = "Message 1"; |
||||
|
const string text2 = "Message 2"; |
||||
|
|
||||
|
// Act
|
||||
|
var encrypted1 = Cryptography.Encrypt(text1); |
||||
|
var encrypted2 = Cryptography.Encrypt(text2); |
||||
|
|
||||
|
// Assert
|
||||
|
encrypted1.ShouldNotBe(encrypted2); |
||||
|
} |
||||
|
[Fact] |
||||
|
public void Encrypt_Should_Throw_ArgumentException_When_Input_Is_Null() |
||||
|
{ |
||||
|
// Act & Assert
|
||||
|
Should.Throw<ArgumentException>(() => Cryptography.Encrypt(null)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Decrypt_Should_Throw_ArgumentException_When_Input_Is_Null() |
||||
|
{ |
||||
|
// Act & Assert
|
||||
|
Should.Throw<ArgumentException>(() => Cryptography.Decrypt(null)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue