mirror of https://github.com/abpframework/abp.git
36 changed files with 1875 additions and 1 deletions
@ -0,0 +1,48 @@ |
|||
using System; |
|||
using System.IO.Abstractions; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Telemetry.Activity; |
|||
using Volo.Abp.Telemetry.Helpers; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpDddDomainModule), |
|||
typeof(AbpDddApplicationContractsModule) |
|||
)] |
|||
public class AbpTelemetryModule : AbpModule |
|||
{ |
|||
|
|||
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) |
|||
{ |
|||
var packageMetadata = AbpPackageMetadataHelper.GetMetaData(Assembly.GetCallingAssembly()); |
|||
|
|||
if (packageMetadata != null) |
|||
{ |
|||
var activityStorage = context.ServiceProvider.GetRequiredService<IActivityStorage>(); |
|||
var lastApplicationInfoSendTime = await activityStorage.GetApplicationInfoLastActivitySendTimeAsync(packageMetadata.ProjectId.To<Guid>()); |
|||
if (lastApplicationInfoSendTime is null || |
|||
DateTimeOffset.UtcNow - lastApplicationInfoSendTime > TimeSpan.FromDays(7)) |
|||
{ |
|||
var telemetryService = context.ServiceProvider.GetRequiredService<ITelemetryService>(); |
|||
|
|||
await using var _ = telemetryService.TrackActivity(ActivityNameConsts.ApplicationRun, c => |
|||
{ |
|||
c.Add("ProjectAssemblyForScan", Assembly.GetCallingAssembly()); |
|||
c.Add("ProjectId", packageMetadata.ProjectId); |
|||
c.Add("ProjectType", packageMetadata.Role); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,76 @@ |
|||
using System.Diagnostics; |
|||
using System.Reflection; |
|||
|
|||
namespace Volo.Abp.Telemetry.Activity; |
|||
|
|||
public class ActivityData : Dictionary<string, object> |
|||
{ |
|||
public string ActivityName |
|||
{ |
|||
get => (string) this[nameof(ActivityName)]; |
|||
init => this[nameof(ActivityName)] = value; |
|||
} |
|||
|
|||
public string? ActivityDetails |
|||
{ |
|||
get => (string?) this[nameof(ActivityDetails)]; |
|||
internal set |
|||
{ |
|||
if (value is not null) this[nameof(ActivityDetails)] = value; |
|||
} |
|||
} |
|||
|
|||
public long? ActivityDuration |
|||
{ |
|||
get => (long?) this[nameof(ActivityDuration)]; |
|||
internal set |
|||
{ |
|||
if (value is not null) this[nameof(ActivityDuration)] = value; |
|||
} |
|||
} |
|||
|
|||
public DateTimeOffset Time = DateTimeOffset.UtcNow; |
|||
|
|||
public ActivityData(string activityName, string? details = null) |
|||
{ |
|||
if (activityName.IsNullOrWhiteSpace()) |
|||
throw new ArgumentNullException(nameof(activityName)); |
|||
|
|||
ActivityName = activityName; |
|||
ActivityDetails = details; |
|||
} |
|||
} |
|||
|
|||
public static class ActivityPropertyNameConstants |
|||
{ |
|||
public const string SolutionId = nameof(SolutionId); |
|||
public const string SessionId = nameof(SessionId); |
|||
public const string ActivityDuration = nameof(ActivityDuration); |
|||
public const string OperationSystem = nameof(OperationSystem); |
|||
public const string DeviceType = nameof(DeviceType); |
|||
public const string DeviceLanguage = nameof(DeviceLanguage); |
|||
} |
|||
|
|||
public static class ActivityDataExtensions |
|||
{ |
|||
public static IAsyncDisposable BeginActivity(this ActivityData activity, ITelemetryService telemetryService) |
|||
{ |
|||
var stopwatch = Stopwatch.StartNew(); |
|||
return new AsyncDisposeFunc(async () => |
|||
{ |
|||
stopwatch.Stop(); |
|||
activity.ActivityDuration = stopwatch.ElapsedMilliseconds; |
|||
await telemetryService.AddActivityAsync(activity); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public class ApplicationRunActivityData : ActivityData |
|||
{ |
|||
public Assembly ProjectAssemblyForScan { get; set; } |
|||
public ApplicationRunActivityData(Assembly projectAssemblyForScan) : base(ActivityNameConsts.ApplicationRun) |
|||
{ |
|||
ProjectAssemblyForScan = projectAssemblyForScan; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
using System.Reflection; |
|||
using Newtonsoft.Json.Linq; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
using DatabaseProvider = Volo.Abp.Telemetry.EnvironmentInspection.DatabaseProvider; |
|||
using MobileApp = Volo.Abp.Telemetry.EnvironmentInspection.MobileApp; |
|||
|
|||
namespace Volo.Abp.Telemetry.Activity; |
|||
|
|||
public class ActivityDataProvider : IActivityDataProvider |
|||
{ |
|||
private IActivityStorage _activityStorage; |
|||
public IAbpLazyServiceProvider ServiceProvider { protected get; set; } |
|||
|
|||
public virtual async Task<ActivityData> AddExtraInformationAsync(ActivityData activityData, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
_activityStorage = ServiceProvider.LazyGetRequiredService<IActivityStorage>(); |
|||
try |
|||
{ |
|||
|
|||
var (isFirstSession, sessionId) = await _activityStorage.GetOrCreateSessionInfoAsync(cancellationToken); |
|||
|
|||
activityData.Add("SessionId", sessionId); |
|||
activityData.Add("IsFirstSession", isFirstSession); |
|||
await AddDeviceInformationAsync(activityData); |
|||
|
|||
AddApplicationInformation(activityData); |
|||
|
|||
await AddSolutionInformationAsync(activityData); |
|||
|
|||
return activityData; |
|||
} |
|||
catch |
|||
{ |
|||
return activityData; |
|||
} |
|||
} |
|||
|
|||
private async Task AddDeviceInformationAsync(ActivityData activityData) |
|||
{ |
|||
var deviceInfoProvider = ServiceProvider.LazyGetRequiredService<IDeviceInfoProvider>(); |
|||
var softwareInfoProvider = ServiceProvider.LazyGetRequiredService<ISoftwareInfoProvider>(); |
|||
|
|||
var lastDeviceInfoSendTime = await _activityStorage.GetLastDeviceInfoSendTimeAsync(); |
|||
activityData.Add("DeviceId", await deviceInfoProvider.GetDeviceIdAsync()); |
|||
|
|||
if (lastDeviceInfoSendTime is null || DateTimeOffset.UtcNow - lastDeviceInfoSendTime > TimeSpan.FromDays(7)) |
|||
{ |
|||
activityData.Add("DeviceType", deviceInfoProvider.GetDeviceType()); |
|||
activityData.Add("DeviceLanguage", deviceInfoProvider.GetLanguage()); |
|||
activityData.Add("OperatingSystem", deviceInfoProvider.GetOperatingSystem()); |
|||
activityData.Add("Country", deviceInfoProvider.GetCountry()); |
|||
|
|||
var softwareList = await softwareInfoProvider.GetSoftwareInfoAsync(); |
|||
activityData.Add("InstalledSoftwares" , softwareList); |
|||
await _activityStorage.MarkDeviceInfoAsSentAsync(); |
|||
} |
|||
} |
|||
|
|||
private void AddApplicationInformation(ActivityData activityData) |
|||
{ |
|||
if (activityData.ContainsKey("ProjectAssemblyForScan")) |
|||
{ |
|||
var assembly = activityData["ProjectAssemblyForScan"] as Assembly; |
|||
if (assembly != null) |
|||
{ |
|||
var applicationInfoProvider = ServiceProvider.LazyGetRequiredService<ApplicationInfoProvider>(); |
|||
var info = applicationInfoProvider.Scan(); |
|||
|
|||
activityData.Add(nameof(Telemetry.ApplicationInfo.ControllerCount), info.ControllerCount); |
|||
activityData.Add(nameof(Telemetry.ApplicationInfo.EntityCount), info.EntityCount); |
|||
activityData.Add(nameof(Telemetry.ApplicationInfo.AbpModuleCount), info.AbpModuleCount); |
|||
activityData.Add(nameof(Telemetry.ApplicationInfo.PermissionCount), info.PermissionCount); |
|||
activityData.Add(nameof(Telemetry.ApplicationInfo.AppServiceCount), info.AppServiceCount); |
|||
activityData.Add("ProjectType", activityData["ProjectType"]); |
|||
activityData.Add("ProjectId", activityData["ProjectId"]); |
|||
|
|||
activityData.Remove("ProjectAssemblyForScan"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task AddSolutionInformationAsync(ActivityData activityData) |
|||
{ |
|||
if (activityData.TryGetValue("SolutionPath", out var value)) |
|||
{ |
|||
var solutionPath = value as string; |
|||
|
|||
var rootJObject = JObject.Parse(await File.ReadAllTextAsync(solutionPath)); |
|||
var solutionId = rootJObject["id"]!.To<Guid>(); |
|||
var lastSolutionInfoSendTime = |
|||
await _activityStorage.GetLastSolutionInfoSendTimeAsync(solutionId); |
|||
activityData.Add("SolutionId", solutionId); |
|||
|
|||
if (lastSolutionInfoSendTime is null || |
|||
DateTimeOffset.UtcNow - lastSolutionInfoSendTime > TimeSpan.FromDays(7)) |
|||
{ |
|||
var infoJObject = (JObject) rootJObject["creatingStudioConfiguration"]!; |
|||
activityData.Add("Template", GetSolutionTemplate(infoJObject["template"]?.ToString())); |
|||
activityData.Add("CreatedAbpStudioVersion", infoJObject["createdAbpStudioVersion"]?.ToString()); |
|||
activityData.Add("IsTiered", infoJObject.Value<bool>("Tiered")); |
|||
activityData.Add("UiFramework", GetUiFramework(infoJObject["uiFramework"]?.ToString())); |
|||
activityData.Add("DatabaseProvider", GetDatabaseProvider(infoJObject["databaseProvider"]?.ToString())); |
|||
activityData.Add("DatabaseManagementSystem", GetDbms(infoJObject["databaseManagementSystem"]?.ToString())); |
|||
activityData.Add("IsSeparateTenantSchema", infoJObject.Value<bool>("separateTenantSchema")); |
|||
activityData.Add("Theme", GetUiTheme(infoJObject["theme"]?.ToString())); |
|||
activityData.Add("ThemeStyle", GetUiThemeStyle(infoJObject["themeStyle"]?.ToString())); |
|||
activityData.Add("MobileFramework", GetMobileApp(infoJObject["mobileFramework"]?.ToString())); |
|||
activityData.Add("HasPublicWebsite", infoJObject.Value<bool>("publicWebsite")); |
|||
activityData.Add("IncludeTests", infoJObject.Value<bool>("includeTests")); |
|||
activityData.Add("MultiTenancy", infoJObject.Value<bool>("multiTenancy")); |
|||
activityData.Add("DynamicLocalization", infoJObject.Value<bool>("dynamicLocalization")); |
|||
activityData.Add("KubernetesConfiguration", infoJObject.Value<bool>("kubernetesConfiguration")); |
|||
activityData.Add("GrafanaDashboard", infoJObject.Value<bool>("grafanaDashboard")); |
|||
activityData.Add("SocialLogins", infoJObject.Value<bool>("socialLogin")); |
|||
} |
|||
} |
|||
} |
|||
private Dbms GetDbms(string? databaseManagementSystem) |
|||
{ |
|||
return databaseManagementSystem switch |
|||
{ |
|||
"mysql" => Dbms.MySql, |
|||
"oracle" => Dbms.Oracle, |
|||
"oracle-devart" => Dbms.OracleDevart, |
|||
"postgresql" => Dbms.PostgreSql, |
|||
"sqlserver" => Dbms.SqlServer, |
|||
"sqlite" => Dbms.Sqlite, |
|||
_ => Dbms.Unknown |
|||
}; |
|||
} |
|||
|
|||
private UiTheme GetUiTheme(string? theme) |
|||
{ |
|||
return theme switch |
|||
{ |
|||
"none" => UiTheme.None, |
|||
"basic" => UiTheme.Basic, |
|||
"leptonx" => UiTheme.LeptonX, |
|||
"leptonx-lite" => UiTheme.LeptonXLite, |
|||
_ => UiTheme.Unknown |
|||
}; |
|||
} |
|||
|
|||
private MobileApp GetMobileApp(string? mobileFramework) |
|||
{ |
|||
return mobileFramework switch |
|||
{ |
|||
"maui" => MobileApp.Maui, |
|||
"react-native" => MobileApp.ReactNative, |
|||
"none" => MobileApp.None, |
|||
_ => MobileApp.Unknown |
|||
}; |
|||
} |
|||
|
|||
private UiThemeStyle GetUiThemeStyle(string? themeStyle) |
|||
{ |
|||
return themeStyle switch |
|||
{ |
|||
"dim" => UiThemeStyle.Dim, |
|||
"style" => UiThemeStyle.System, |
|||
"dark" => UiThemeStyle.Dark, |
|||
"light" => UiThemeStyle.Light, |
|||
_ => UiThemeStyle.Unknown |
|||
}; |
|||
} |
|||
private SolutionTemplate GetSolutionTemplate(string? templateName) |
|||
{ |
|||
return templateName switch |
|||
{ |
|||
"microservice" => SolutionTemplate.Microservice, |
|||
"app-nolayers" => SolutionTemplate.AppNoLayers, |
|||
"app" => SolutionTemplate.AppLayered, |
|||
_ => SolutionTemplate.Unknown |
|||
}; |
|||
} |
|||
|
|||
private UiFramework GetUiFramework(string? uiFramework) |
|||
{ |
|||
return uiFramework switch |
|||
{ |
|||
"mvc" => UiFramework.MvcRazorPages, |
|||
"blazor" => UiFramework.BlazorWasm, |
|||
"angular" => UiFramework.Angular, |
|||
"blazor-server" => UiFramework.BlazorServer, |
|||
"blazor-webapp" => UiFramework.BlazorWebApp, |
|||
"maui-blazor" => UiFramework.BlazorMaUI, |
|||
"none" => UiFramework.None, |
|||
_ => UiFramework.Unknown, |
|||
}; |
|||
} |
|||
|
|||
private DatabaseProvider GetDatabaseProvider(string? databaseProvider) |
|||
{ |
|||
return databaseProvider switch |
|||
{ |
|||
"ef" => DatabaseProvider.EfCore, |
|||
"mongodb" => DatabaseProvider.MongoDb, |
|||
"none" => DatabaseProvider.None, |
|||
_ => DatabaseProvider.Unknown |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,215 @@ |
|||
using System.Text; |
|||
using Volo.Abp.Cli; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace Volo.Abp.Telemetry.Activity; |
|||
|
|||
public class ActivityStorage : IActivityStorage, ISingletonDependency |
|||
{ |
|||
private readonly IJsonSerializer _jsonSerializer; |
|||
private readonly string _filePath; |
|||
private ActivityStorageState? _cachedState; |
|||
|
|||
public ActivityStorage(IJsonSerializer jsonSerializer) |
|||
{ |
|||
_jsonSerializer = jsonSerializer; |
|||
_filePath = Path.Combine(CliPaths.AbpRootPath, "activity-storage.json"); |
|||
} |
|||
|
|||
public async Task BufferActivityAsync(ActivityData activityData, CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.Activities.Add(activityData); |
|||
await SaveAsync(); |
|||
} |
|||
|
|||
public async Task<List<ActivityData>> GetBufferedActivitiesAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
return state.Activities; |
|||
} |
|||
|
|||
public async Task RemoveActivityAsync(string activityName, CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
|
|||
var existing = state.Activities |
|||
.FirstOrDefault(x => string.Equals(x.ActivityName, activityName, StringComparison.OrdinalIgnoreCase)); |
|||
|
|||
if (existing is not null) |
|||
{ |
|||
state.Activities.Remove(existing); |
|||
await SaveAsync(); |
|||
} |
|||
} |
|||
|
|||
public async Task MarkApplicationInfoAsSentAsync(Guid applicationId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.ApplicationInfos[applicationId] = DateTimeOffset.UtcNow; |
|||
await SaveAsync(); |
|||
} |
|||
|
|||
public async Task<DateTimeOffset?> GetApplicationInfoLastActivitySendTimeAsync(Guid applicationId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
if (state.ApplicationInfos.TryGetValue(applicationId, out var lastActivitySendTime)) |
|||
{ |
|||
return lastActivitySendTime; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public async Task<DateTimeOffset?> GetLastActivitySendTimeAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
return state.ActivitySendTime; |
|||
} |
|||
|
|||
public async Task<Guid?> GetSessionIdAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
return state.SessionId ?? null; |
|||
} |
|||
|
|||
public async Task<(bool isFirstSession, Guid sessionId)> GetOrCreateSessionInfoAsync( |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.IsFirstSession ??= true; |
|||
state.SessionId ??= Guid.NewGuid(); |
|||
await SaveAsync(); |
|||
return (state.IsFirstSession.Value, state.SessionId.Value); |
|||
} |
|||
|
|||
public async Task SetSessionIdAsync(Guid sessionId, bool isFirstSession = false, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.SessionId = sessionId; |
|||
state.IsFirstSession = isFirstSession; |
|||
await SaveAsync(); |
|||
} |
|||
|
|||
public async Task MarkActivitiesAsSentAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.ActivitySendTime = DateTimeOffset.UtcNow; |
|||
state.Activities.Clear(); |
|||
state.SessionId = null; |
|||
|
|||
await SaveAsync(); |
|||
} |
|||
|
|||
public async Task<DateTimeOffset?> GetLastSolutionInfoSendTimeAsync(Guid id, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
|
|||
if (state.Solutions.TryGetValue(id, out var date)) |
|||
{ |
|||
return date; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public async Task<DateTimeOffset?> GetLastDeviceInfoSendTimeAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
return state.LastDeviceInfoSendTime; |
|||
} |
|||
|
|||
public async Task MarkSolutionInfoAsSentAsync(Guid id, CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.Solutions[id] = DateTimeOffset.UtcNow; |
|||
await SaveAsync(); |
|||
} |
|||
|
|||
public async Task MarkDeviceInfoAsSentAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
var state = await GetStateAsync(); |
|||
state.LastDeviceInfoSendTime = DateTimeOffset.UtcNow; |
|||
await SaveAsync(); |
|||
} |
|||
|
|||
private async Task<ActivityStorageState> GetStateAsync() |
|||
{ |
|||
if (_cachedState != null) |
|||
return _cachedState; |
|||
|
|||
await EnsureFileExistsAsync(); |
|||
_cachedState = await WithExclusiveFileLockAsync(async stream => |
|||
{ |
|||
using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: true); |
|||
var json = await reader.ReadToEndAsync(); |
|||
return _jsonSerializer.Deserialize<ActivityStorageState?>(json) ?? new ActivityStorageState(); |
|||
}); |
|||
return _cachedState; |
|||
} |
|||
|
|||
private async Task<TResult> WithExclusiveFileLockAsync<TResult>(Func<FileStream, Task<TResult>> action) |
|||
{ |
|||
const int maxRetries = 10; |
|||
const int retryDelayMs = 100; |
|||
|
|||
for (int i = 0; i < maxRetries; i++) |
|||
{ |
|||
try |
|||
{ |
|||
await using var stream = new FileStream( |
|||
_filePath, |
|||
FileMode.OpenOrCreate, |
|||
FileAccess.ReadWrite, |
|||
FileShare.None |
|||
); |
|||
|
|||
return await action(stream); |
|||
} |
|||
catch (IOException) |
|||
{ |
|||
if (i == maxRetries - 1) |
|||
throw; |
|||
|
|||
await Task.Delay(retryDelayMs); |
|||
} |
|||
} |
|||
|
|||
throw new IOException("Unable to acquire file lock for ActivityStorage."); |
|||
} |
|||
|
|||
private async Task SaveAsync() |
|||
{ |
|||
var json = _jsonSerializer.Serialize(_cachedState ?? new ActivityStorageState(), indented: true); |
|||
await File.WriteAllTextAsync(_filePath, json, Encoding.UTF8); |
|||
} |
|||
|
|||
|
|||
private async Task EnsureFileExistsAsync() |
|||
{ |
|||
try |
|||
{ |
|||
var directory = Path.GetDirectoryName(_filePath); |
|||
|
|||
if (!Directory.Exists(directory)) |
|||
{ |
|||
Directory.CreateDirectory(directory); |
|||
} |
|||
|
|||
if (!File.Exists(_filePath)) |
|||
{ |
|||
var json = _jsonSerializer.Serialize(new ActivityStorageState(), indented: true); |
|||
await File.WriteAllTextAsync(_filePath, json, Encoding.UTF8); |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
//ignored
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
namespace Volo.Abp.Telemetry.Activity; |
|||
|
|||
public class ActivityStorageState |
|||
{ |
|||
public DateTimeOffset? ActivitySendTime { get; set; } |
|||
public DateTimeOffset? LastDeviceInfoSendTime { get; set; } |
|||
public Guid? SessionId { get; set; } |
|||
public bool? IsFirstSession { get; set; } |
|||
public List<ActivityData> Activities { get; set; } = new(); |
|||
public Dictionary<Guid,DateTimeOffset> Solutions { get; set; } = new(); |
|||
|
|||
public Dictionary<Guid, DateTimeOffset> ApplicationInfos { get; set; } = new(); |
|||
} |
|||
|
|||
|
|||
|
|||
public class ApplicationInfo |
|||
{ |
|||
public Guid ApplicationId { get; set; } |
|||
public ApplicationType Type { get; set; } |
|||
public int EntityCount { get; set; } // Number of entities in the application, to understand its size
|
|||
public int AppServiceCount { get; set; } // ...
|
|||
public int ControllerCount { get; set; } // ...
|
|||
public int PermissionCount { get; set; } // ...
|
|||
public int AbpModuleCount { get; set; } |
|||
|
|||
} |
|||
|
|||
public enum ApplicationType |
|||
{ |
|||
Unknown = 0, |
|||
Console = 1, |
|||
MvcUi = 2, |
|||
BlazorServerUi = 3, |
|||
BlazorWebAssemblyUi = 4, |
|||
BlazorWebAppUi = 5, |
|||
HttpApi = 6, |
|||
AspNetCore = 7 //We could not understand if it is HTTP API or MVC UI or whatever
|
|||
//TODO: More?
|
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Telemetry.Activity; |
|||
|
|||
public interface IActivityDataProvider : ITransientDependency |
|||
{ |
|||
Task<ActivityData> AddExtraInformationAsync(ActivityData activityData, CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
namespace Volo.Abp.Telemetry.Activity; |
|||
|
|||
public interface IActivityStorage |
|||
{ |
|||
Task<DateTimeOffset?> GetLastActivitySendTimeAsync(CancellationToken cancellationToken = default); |
|||
Task<(bool isFirstSession, Guid sessionId)> GetOrCreateSessionInfoAsync(CancellationToken cancellationToken = default); |
|||
Task MarkActivitiesAsSentAsync(CancellationToken cancellationToken = default); |
|||
Task<DateTimeOffset?> GetLastSolutionInfoSendTimeAsync(Guid solutionId, CancellationToken cancellationToken = default); |
|||
Task<DateTimeOffset?> GetLastDeviceInfoSendTimeAsync(CancellationToken cancellationToken = default); |
|||
Task MarkDeviceInfoAsSentAsync(CancellationToken cancellationToken = default); |
|||
Task MarkSolutionInfoAsSentAsync(Guid solutionId , CancellationToken cancellationToken = default); |
|||
Task BufferActivityAsync(ActivityData activityData, CancellationToken cancellationToken = default); |
|||
Task<List<ActivityData>> GetBufferedActivitiesAsync(CancellationToken cancellationToken = default); |
|||
Task MarkApplicationInfoAsSentAsync(Guid applicationId, CancellationToken cancellationToken = default); |
|||
Task<DateTimeOffset?> GetApplicationInfoLastActivitySendTimeAsync(Guid applicationId, CancellationToken cancellationToken = default); |
|||
} |
|||
|
|||
@ -0,0 +1,41 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.Telemetry.Activity; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public class ActivityEventHandler : ILocalEventHandler<ActivityData>, ITransientDependency |
|||
{ |
|||
private readonly IActivityStorage _activityStorage; |
|||
private readonly IActivityDataProvider _activityDataProvider; |
|||
private readonly ITelemetryDataSender _telemetryDataSender; |
|||
|
|||
public ActivityEventHandler(IActivityDataProvider activityDataProvider, IActivityStorage activityStorage, ITelemetryDataSender telemetryDataSender) |
|||
{ |
|||
_activityDataProvider = activityDataProvider; |
|||
_activityStorage = activityStorage; |
|||
_telemetryDataSender = telemetryDataSender; |
|||
} |
|||
|
|||
public async Task HandleEventAsync(ActivityData eventData) |
|||
{ |
|||
var build = await _activityDataProvider.AddExtraInformationAsync(eventData); |
|||
await _activityStorage.BufferActivityAsync(build); |
|||
|
|||
await CheckIfActivitySendTimeIsDueAsync(); |
|||
} |
|||
|
|||
private async Task CheckIfActivitySendTimeIsDueAsync() |
|||
{ |
|||
var lastActivitySendTime = await _activityStorage.GetLastActivitySendTimeAsync(); |
|||
if (lastActivitySendTime is null) |
|||
{ |
|||
await _telemetryDataSender.SendAsync(); |
|||
} |
|||
|
|||
if (lastActivitySendTime is not null && lastActivitySendTime > DateTimeOffset.UtcNow.AddDays(7) ) |
|||
{ |
|||
await _telemetryDataSender.SendAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
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 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 AbpStudioMonitoringLogsFiltered = "AbpStudio.Monitoring.Logs.Filtered"; |
|||
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 ApplicationRun = "Application.Run"; |
|||
public const string AbpStudioBrowserOpen = "AbpStudio.Browser.Open"; |
|||
public const string Error = "Error"; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public class ApplicationInfo |
|||
{ |
|||
public int EntityCount { get; set; } |
|||
public int AppServiceCount { get; set; } |
|||
public int ControllerCount { get; set; } |
|||
public int PermissionCount { get; set; } |
|||
public int AbpModuleCount { get; set; } |
|||
public int ProjectCount { get; set; } // TODO: implement if needed
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Reflection; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public class ApplicationInfoProvider : ISingletonDependency |
|||
{ |
|||
private readonly ITypeFinder _typeFinder; |
|||
|
|||
|
|||
public ApplicationInfoProvider(ITypeFinder typeFinder) |
|||
{ |
|||
_typeFinder = typeFinder; |
|||
} |
|||
|
|||
public ApplicationInfo Scan() |
|||
{ |
|||
var types = _typeFinder.Types; |
|||
|
|||
return new ApplicationInfo |
|||
{ |
|||
EntityCount = types.Count(t => typeof(IEntity).IsAssignableFrom(t) && !t.IsAbstract), |
|||
AppServiceCount = types.Count(t => typeof(IApplicationService).IsAssignableFrom(t) && !t.IsAbstract), |
|||
ControllerCount = types.Count(t => typeof(AbpController).IsAssignableFrom(t) && !t.IsAbstract), |
|||
PermissionCount = types.Count(t => typeof(PermissionDefinitionProvider).IsAssignableFrom(t) && !t.IsAbstract), |
|||
AbpModuleCount = types.Count(t => typeof(AbpModule).IsAssignableFrom(t) && !t.IsAbstract), |
|||
ProjectCount = types.Select(t => t.Assembly.GetName().Name).Distinct().Count() |
|||
|
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
internal interface ISoftwareDetector : IScopedDependency |
|||
{ |
|||
string Name { get; } |
|||
Task<SoftwareInfo?> DetectAsync(); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
public interface ISoftwareInfoProvider |
|||
{ |
|||
Task<List<SoftwareInfo>> GetSoftwareInfoAsync(CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
public 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,51 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
using Volo.Abp.Cli; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class AbpStudioDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "Abp Studio"; |
|||
|
|||
public async override Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
try |
|||
{ |
|||
var uiTheme = await GetAbpStudioUiThemeAsync(); |
|||
var version = await GetAbpStudioVersionAsync(); |
|||
|
|||
return new SoftwareInfo(Name, version, uiTheme, SoftwareType.AbpStudio); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<string?> GetAbpStudioUiThemeAsync() |
|||
{ |
|||
var ideStateJsonPath = Path.Combine( |
|||
CliPaths.AbpRootPath, |
|||
"studio", |
|||
"ui", |
|||
"ide-state.json" |
|||
); |
|||
|
|||
var jObject = JObject.Parse(await File.ReadAllTextAsync(ideStateJsonPath)); |
|||
return jObject["theme"]?.Value<string>(); |
|||
} |
|||
|
|||
private async Task<string?> GetAbpStudioVersionAsync() |
|||
{ |
|||
var extensionsFilePath = Path.Combine( |
|||
CliPaths.AbpRootPath, |
|||
"studio", |
|||
"extensions.json" |
|||
); |
|||
|
|||
var jObject = JObject.Parse(await File.ReadAllTextAsync(extensionsFilePath)); |
|||
|
|||
return jObject["version"]?.Value<string>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System.Runtime.InteropServices; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class ChromeDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "Chrome"; |
|||
|
|||
public override async Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
string 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)) |
|||
{ |
|||
string 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)) |
|||
{ |
|||
string 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,19 @@ |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class DotnetSdkDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "DotnetSdk"; |
|||
|
|||
public override async Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
var output = await ExecuteCommandAsync("dotnet", "--version"); |
|||
if (output.IsNullOrWhiteSpace()) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return new SoftwareInfo(Name, output, null, SoftwareType.DotnetSdk); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System.Runtime.InteropServices; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class FireFoxDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "Firefox"; |
|||
public override async Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
string 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)) |
|||
{ |
|||
string 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)) |
|||
{ |
|||
string 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,42 @@ |
|||
using System.Runtime.InteropServices; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class MsEdgeDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "MsEdge"; |
|||
public override async Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
string 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)) |
|||
{ |
|||
string 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)) |
|||
{ |
|||
string 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,27 @@ |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class NodeJsDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "Node.js"; |
|||
|
|||
public override async 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,92 @@ |
|||
using System.Diagnostics; |
|||
using Microsoft.Win32; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class OperatingSystemDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => OperatingSystem.IsWindows() ? "Windows" : OperatingSystem.IsMacOS() ? "macOS" : "Linux"; |
|||
|
|||
public override async Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
if (OperatingSystem.IsWindows()) |
|||
{ |
|||
return new SoftwareInfo(Name, Environment.OSVersion.Version.ToString(),GetWindowsUiTheme(), SoftwareType.OperatingSystem); |
|||
} |
|||
|
|||
if (OperatingSystem.IsMacOS()) |
|||
{ |
|||
var version = await ExecuteCommandAsync("sw_vers", "-productVersion"); |
|||
return new SoftwareInfo(Name, version, GetMacUiTheme(), SoftwareType.OperatingSystem); |
|||
} |
|||
|
|||
if (OperatingSystem.IsLinux()) |
|||
{ |
|||
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"; |
|||
} |
|||
} |
|||
private string? GetWindowsUiTheme() |
|||
{ |
|||
try |
|||
{ |
|||
const string key = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; |
|||
const string value = "AppsUseLightTheme"; |
|||
|
|||
using var registryKey = Registry.CurrentUser.OpenSubKey(key); |
|||
var result = registryKey?.GetValue(value); |
|||
|
|||
return result switch |
|||
{ |
|||
0 => "Dark", |
|||
1 => "Light", |
|||
_ => null |
|||
}; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using System.Runtime.InteropServices; |
|||
using System.Xml.Linq; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class RiderDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
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 = "Unknown"; |
|||
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 (!string.IsNullOrEmpty(schemeName)) |
|||
{ |
|||
theme = schemeName.IndexOf("dark", StringComparison.OrdinalIgnoreCase) >= 0 |
|||
? "Dark" |
|||
: "Light"; |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
// Hata olursa Unknown bırakılsın
|
|||
} |
|||
} |
|||
|
|||
return Task.FromResult<SoftwareInfo?>(new SoftwareInfo(Name, latest.Version?.ToString(), theme, |
|||
SoftwareType.Ide)); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return Task.FromResult<SoftwareInfo?>(null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
using System.Runtime.InteropServices; |
|||
using System.Text.Json; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class VisualStudioCodeDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
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(await File.ReadAllTextAsync(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,13 @@ |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection.Detectors; |
|||
|
|||
internal class VisualStudioDetector : SoftwareDetector, ISoftwareDetector |
|||
{ |
|||
public override string Name => "Visual Studio"; |
|||
|
|||
public async override Task<SoftwareInfo?> DetectAsync() |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System.Diagnostics; |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
using CliWrap; |
|||
using CliWrap.Buffered; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection; |
|||
|
|||
abstract internal class SoftwareDetector : ISoftwareDetector |
|||
{ |
|||
public abstract string Name { get; } |
|||
public abstract Task<SoftwareInfo?> DetectAsync(); |
|||
|
|||
protected async Task<string?> ExecuteCommandAsync(string command, string? arg) |
|||
{ |
|||
var result = await CliWrap.Cli.Wrap(command) |
|||
.WithArguments(arg) |
|||
.WithValidation(CommandResultValidation.None) |
|||
.ExecuteBufferedAsync(); |
|||
|
|||
return result.StandardOutput.IsNullOrEmpty() ? null : result.StandardOutput.Trim(); |
|||
} |
|||
|
|||
protected static string? GetFileVersion(string filePath) |
|||
{ |
|||
try |
|||
{ |
|||
var versionInfo = FileVersionInfo.GetVersionInfo(filePath); |
|||
return versionInfo.FileVersion; |
|||
} |
|||
catch |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection; |
|||
|
|||
internal class SoftwareInfoProvider : ISoftwareInfoProvider |
|||
{ |
|||
private readonly IEnumerable<ISoftwareDetector> _softwareDetectors; |
|||
|
|||
public SoftwareInfoProvider(IEnumerable<ISoftwareDetector> softwareDetectors) |
|||
{ |
|||
_softwareDetectors = softwareDetectors; |
|||
} |
|||
|
|||
public async Task<List<SoftwareInfo>> GetSoftwareInfoAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
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,251 @@ |
|||
using System.Diagnostics; |
|||
using System.Globalization; |
|||
using System.Management; |
|||
using System.Runtime.InteropServices; |
|||
using Volo.Abp.Cli; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Telemetry.EnvironmentInspection; |
|||
|
|||
|
|||
public enum MobileApp |
|||
{ |
|||
Unknown = 0, |
|||
None = 1, |
|||
Maui = 2, |
|||
ReactNative = 3 |
|||
|
|||
} |
|||
|
|||
|
|||
public enum SolutionTemplate |
|||
{ |
|||
Unknown, |
|||
AppNoLayers, |
|||
AppLayered, |
|||
Microservice |
|||
} |
|||
|
|||
public enum UiFramework |
|||
{ |
|||
Unknown = 0, |
|||
None = 1, |
|||
MvcRazorPages = 2, |
|||
Angular = 3, |
|||
BlazorWasm = 4, |
|||
BlazorServer = 5, |
|||
BlazorWebApp = 6, |
|||
BlazorMaUI = 7, |
|||
} |
|||
|
|||
public enum LicenseType |
|||
{ |
|||
Unknown, |
|||
Free, |
|||
Team, |
|||
Business, |
|||
Enterprise |
|||
} |
|||
public enum DatabaseProvider |
|||
{ |
|||
Unknown = 0, |
|||
None = 1, |
|||
EfCore = 2, |
|||
MongoDb = 3 |
|||
} |
|||
|
|||
public enum Dbms |
|||
{ |
|||
Unknown = 0, |
|||
None = 1, |
|||
SqlServer = 2, |
|||
PostgreSql = 3, |
|||
Oracle = 4, |
|||
OracleDevart = 5, |
|||
MySql = 6, |
|||
Sqlite = 7, |
|||
} |
|||
public enum UiTheme |
|||
{ |
|||
Unknown = 0, |
|||
None = 1, |
|||
Basic = 2, |
|||
LeptonX = 3, |
|||
LeptonXLite = 4 |
|||
} |
|||
|
|||
public enum UiThemeStyle |
|||
{ |
|||
Unknown = 0, |
|||
System = 1, |
|||
Dim = 2, |
|||
Dark = 3, |
|||
Light = 4 |
|||
} |
|||
|
|||
|
|||
public enum AbpTool : byte |
|||
{ |
|||
Unknown = 0, |
|||
StudioUI = 1, |
|||
StudioCli = 2, |
|||
OldCli = 3 |
|||
} |
|||
public enum SoftwareType : byte |
|||
{ |
|||
Others = 0, |
|||
AbpStudio = 1, |
|||
DotnetSdk = 2, |
|||
OperatingSystem = 3, |
|||
Ide = 4, |
|||
Browser = 5 |
|||
} |
|||
|
|||
public enum OperationSystem |
|||
{ |
|||
Unknown = 0, |
|||
Windows = 1, |
|||
MacOS = 2, |
|||
Linux = 3, |
|||
} |
|||
|
|||
public enum DeviceType |
|||
{ |
|||
Unknown = 0, |
|||
Desktop = 1, |
|||
Laptop = 2 |
|||
} |
|||
|
|||
public interface IDeviceInfoProvider |
|||
{ |
|||
Task<Guid> GetDeviceIdAsync(); |
|||
OperationSystem GetOperatingSystem(); |
|||
DeviceType GetDeviceType(); |
|||
string GetLanguage(); |
|||
string? GetCountry(); |
|||
} |
|||
|
|||
public class DeviceInfoProvider : IDeviceInfoProvider , ISingletonDependency |
|||
{ |
|||
public async Task<Guid> GetDeviceIdAsync() |
|||
{ |
|||
var deviceId = |
|||
await File.ReadAllTextAsync(Path.Combine(CliPaths.AbpRootPath, "cli", "computer-id.bin")); |
|||
return deviceId.To<Guid>(); |
|||
} |
|||
|
|||
public OperationSystem GetOperatingSystem() |
|||
{ |
|||
if (OperatingSystem.IsWindows()) return OperationSystem.Windows; |
|||
if (OperatingSystem.IsLinux()) return OperationSystem.Linux; |
|||
if (OperatingSystem.IsMacOS()) return OperationSystem.MacOS; |
|||
return OperationSystem.Unknown; |
|||
} |
|||
|
|||
public DeviceType GetDeviceType() |
|||
{ |
|||
if (OperatingSystem.IsWindows()) |
|||
{ |
|||
return DetectDeviceTypeOnWindows(); |
|||
} |
|||
|
|||
if (OperatingSystem.IsLinux()) |
|||
{ |
|||
return DetectDeviceTypeOnLinux(); |
|||
} |
|||
|
|||
if (OperatingSystem.IsMacOS()) |
|||
{ |
|||
return DetectDeviceTypeOnMacOS(); |
|||
} |
|||
|
|||
if (RuntimeInformation.OSDescription.Contains("iOS", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return DeviceType.Laptop; |
|||
} |
|||
|
|||
return DeviceType.Unknown; |
|||
} |
|||
|
|||
public string GetLanguage() |
|||
{ |
|||
return CultureInfo.CurrentUICulture.Name; |
|||
} |
|||
|
|||
public string? GetCountry() |
|||
{ |
|||
try |
|||
{ |
|||
var region = new RegionInfo(CultureInfo.CurrentUICulture.Name); |
|||
return region.TwoLetterISORegionName; |
|||
} |
|||
catch |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private DeviceType DetectDeviceTypeOnWindows() |
|||
{ |
|||
try |
|||
{ |
|||
using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_SystemEnclosure"); |
|||
foreach (var obj in searcher.Get()) |
|||
{ |
|||
var chassisTypes = obj["ChassisTypes"] as ushort[]; |
|||
if (chassisTypes != null && chassisTypes.Any(t => t is 8 or 9 or 10 or 14)) |
|||
return DeviceType.Laptop; |
|||
} |
|||
} |
|||
catch { } |
|||
|
|||
return DeviceType.Desktop; |
|||
} |
|||
|
|||
private DeviceType DetectDeviceTypeOnLinux() |
|||
{ |
|||
try |
|||
{ |
|||
var type = File.ReadAllText("/sys/class/dmi/id/chassis_type"); |
|||
if (int.TryParse(type.Trim(), out var code) && code is 8 or 9 or 10 or 14) |
|||
return DeviceType.Laptop; |
|||
} |
|||
catch |
|||
{ |
|||
// ignored
|
|||
} |
|||
|
|||
return DeviceType.Desktop; |
|||
} |
|||
|
|||
private DeviceType DetectDeviceTypeOnMacOS() |
|||
{ |
|||
try |
|||
{ |
|||
var process = new Process |
|||
{ |
|||
StartInfo = new ProcessStartInfo |
|||
{ |
|||
FileName = "system_profiler", |
|||
Arguments = "SPHardwareDataType", |
|||
RedirectStandardOutput = true, |
|||
UseShellExecute = false, |
|||
CreateNoWindow = true |
|||
} |
|||
}; |
|||
|
|||
process.Start(); |
|||
var output = process.StandardOutput.ReadToEnd(); |
|||
process.WaitForExit(); |
|||
|
|||
if (output.Contains("MacBook")) return DeviceType.Laptop; |
|||
if (output.Contains("iMac") || output.Contains("Mac Pro")) return DeviceType.Desktop; |
|||
} |
|||
catch |
|||
{ |
|||
// ignored
|
|||
} |
|||
|
|||
return DeviceType.Unknown; |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System.Reflection; |
|||
using System.Text.Json; |
|||
|
|||
namespace Volo.Abp.Telemetry.Helpers; |
|||
|
|||
public static class AbpPackageMetadataHelper |
|||
{ |
|||
public static AbpPackageMetadata? GetMetaData(Assembly assembly) |
|||
{ |
|||
var assemblyPath = assembly.Location; |
|||
|
|||
if (string.IsNullOrWhiteSpace(assemblyPath)) |
|||
return null; |
|||
|
|||
var dir = Path.GetDirectoryName(assemblyPath); |
|||
if (dir == null) |
|||
return null; |
|||
|
|||
var abppkgPath = Directory.GetFiles(dir, "*.abppkg").FirstOrDefault(); |
|||
if (abppkgPath == null) |
|||
return null; |
|||
|
|||
return ReadOrCreateMetadata(abppkgPath); |
|||
} |
|||
|
|||
private static AbpPackageMetadata ReadOrCreateMetadata(string path) |
|||
{ |
|||
var json = File.ReadAllText(path); |
|||
var doc = JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new(); |
|||
|
|||
var metadata = new AbpPackageMetadata(); |
|||
|
|||
if (doc.TryGetValue("projectId", out var existingProjectId) && existingProjectId is string id && |
|||
!string.IsNullOrWhiteSpace(id)) |
|||
{ |
|||
metadata.ProjectId = id; |
|||
} |
|||
else |
|||
{ |
|||
metadata.ProjectId = Guid.NewGuid().ToString(); |
|||
doc["projectId"] = metadata.ProjectId; |
|||
} |
|||
|
|||
// Role
|
|||
if (doc.TryGetValue("role", out var existingRole) && existingRole is string role) |
|||
{ |
|||
metadata.Role = role; |
|||
} |
|||
|
|||
// Save if new ProjectId was generated
|
|||
var updatedJson = JsonSerializer.Serialize(doc, new JsonSerializerOptions {WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase}); |
|||
File.WriteAllText(path, updatedJson); |
|||
|
|||
return metadata; |
|||
} |
|||
} |
|||
|
|||
public class AbpPackageMetadata |
|||
{ |
|||
public string ProjectId { get; set; } |
|||
public string Role { get; set; } |
|||
|
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public interface IApplicationScanner |
|||
{ |
|||
ApplicationInfo Scan(); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public interface ITelemetryDataSender : IScopedDependency |
|||
{ |
|||
Task SendAsync(); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Telemetry.Activity; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public interface ITelemetryService : IScopedDependency |
|||
{ |
|||
IAsyncDisposable TrackActivity(string activityName, Action<ActivityData>? configure = null); |
|||
IAsyncDisposable TrackActivity(ActivityData activityData); |
|||
Task AddActivityAsync(ActivityData data, CancellationToken cancellationToken = default); |
|||
Task AddActivityAsync(string activityName, string? details = null, CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System.Net.Mime; |
|||
using System.Text; |
|||
using System.Text.Json; |
|||
using Volo.Abp.Cli; |
|||
using Volo.Abp.Cli.Http; |
|||
using Volo.Abp.Telemetry.Activity; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public class TelemetryDataSender : ITelemetryDataSender |
|||
{ |
|||
private readonly CliHttpClientFactory _cliHttpClientFactory; |
|||
private readonly IActivityStorage _activityStorage; |
|||
private readonly IActivityDataProvider _activityDataProvider; |
|||
|
|||
public TelemetryDataSender(CliHttpClientFactory cliHttpClientFactory, IActivityStorage activityStorage, IActivityDataProvider activityDataProvider) |
|||
{ |
|||
_cliHttpClientFactory = cliHttpClientFactory; |
|||
_activityStorage = activityStorage; |
|||
_activityDataProvider = activityDataProvider; |
|||
} |
|||
|
|||
public async Task SendAsync() |
|||
{ |
|||
using var client = _cliHttpClientFactory.CreateClient(); |
|||
client.AddAbpAuthenticationToken(); |
|||
|
|||
var activities = await _activityStorage.GetBufferedActivitiesAsync(); |
|||
|
|||
if (activities.Count > 0) |
|||
{ |
|||
foreach (var activity in activities) |
|||
{ |
|||
await _activityDataProvider.AddExtraInformationAsync(activity); |
|||
await client.PostAsync(CliUrls.TelemetryAbpIo, |
|||
new StringContent(JsonSerializer.Serialize(activity), Encoding.UTF8, |
|||
MediaTypeNames.Application.Json)); |
|||
|
|||
} |
|||
|
|||
await _activityStorage.MarkActivitiesAsSentAsync(); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System.Diagnostics; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.EventBus.Local; |
|||
using Volo.Abp.Telemetry.Activity; |
|||
|
|||
namespace Volo.Abp.Telemetry; |
|||
|
|||
public class TelemetryService : ApplicationService, ITelemetryService |
|||
{ |
|||
|
|||
private readonly ILocalEventBus _localEventBus; |
|||
public TelemetryService( ILocalEventBus localEventBus) |
|||
{ |
|||
_localEventBus = localEventBus; |
|||
} |
|||
|
|||
|
|||
public IAsyncDisposable TrackActivity(string activityName, Action<ActivityData>? configure = null) |
|||
{ |
|||
var stopwatch = Stopwatch.StartNew(); |
|||
var activityData = new ActivityData(activityName) |
|||
{ |
|||
Time = DateTimeOffset.UtcNow |
|||
}; |
|||
|
|||
configure?.Invoke(activityData); |
|||
|
|||
return new AsyncDisposeFunc(async () => |
|||
{ |
|||
stopwatch.Stop(); |
|||
activityData.ActivityDuration = stopwatch.ElapsedMilliseconds; |
|||
|
|||
await AddActivityAsync(activityData); |
|||
}); |
|||
} |
|||
|
|||
public IAsyncDisposable TrackActivity(ActivityData activityData) |
|||
{ |
|||
var stopwatch = Stopwatch.StartNew(); |
|||
activityData.Time = DateTimeOffset.UtcNow; |
|||
|
|||
return new AsyncDisposeFunc(async () => |
|||
{ |
|||
stopwatch.Stop(); |
|||
activityData.ActivityDuration = stopwatch.ElapsedMilliseconds; |
|||
await AddActivityAsync(activityData); |
|||
}); |
|||
} |
|||
|
|||
|
|||
|
|||
public async Task AddActivityAsync(ActivityData data, CancellationToken cancellationToken = default) |
|||
{ |
|||
await _localEventBus.PublishAsync(data); |
|||
} |
|||
|
|||
public async Task AddActivityAsync(string activityName, string? details = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
await AddActivityAsync(new ActivityData(activityName, details),cancellationToken); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue