Browse Source

Add telemetry module with activity tracking and software inspection

.
pull/23059/head
Emre KARA 2 years ago
parent
commit
c58d87ad24
  1. 5
      Directory.Packages.props
  2. 14
      framework/Volo.Abp.sln
  3. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs
  4. 48
      framework/src/Volo.Abp.Telemetry/AbpTelemetryModule.cs
  5. 76
      framework/src/Volo.Abp.Telemetry/Activity/ActivityData.cs
  6. 205
      framework/src/Volo.Abp.Telemetry/Activity/ActivityDataProvider.cs
  7. 215
      framework/src/Volo.Abp.Telemetry/Activity/ActivityStorage.cs
  8. 40
      framework/src/Volo.Abp.Telemetry/Activity/ActivityStorageState.cs
  9. 8
      framework/src/Volo.Abp.Telemetry/Activity/IActivityDataProvider.cs
  10. 17
      framework/src/Volo.Abp.Telemetry/Activity/IActivityStorage.cs
  11. 41
      framework/src/Volo.Abp.Telemetry/ActivityEventHandler.cs
  12. 74
      framework/src/Volo.Abp.Telemetry/ActivityNameConsts.cs
  13. 11
      framework/src/Volo.Abp.Telemetry/ApplicationInfo.cs
  14. 36
      framework/src/Volo.Abp.Telemetry/ApplicationInfoProvider.cs
  15. 9
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Contracts/ISoftwareDetector.cs
  16. 6
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Contracts/ISoftwareInfoProvider.cs
  17. 9
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Contracts/SoftwareInfo.cs
  18. 51
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs
  19. 43
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/ChromeDetector.cs
  20. 19
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/DotnetSdkDetector.cs
  21. 40
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/FireFoxDetector.cs
  22. 42
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/MsEdgeDetector.cs
  23. 27
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/NodeJsDetector.cs
  24. 92
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/OperatingSystemDetector.cs
  25. 94
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/RiderDetector.cs
  26. 118
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs
  27. 13
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/VisualStudioDetector.cs
  28. 35
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/SoftwareDetector.cs
  29. 37
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/SoftwareInfoProvider.cs
  30. 251
      framework/src/Volo.Abp.Telemetry/EnvironmentInspection/SoftwareType.cs
  31. 63
      framework/src/Volo.Abp.Telemetry/Helpers/AbpPackageMetadataHelper.cs
  32. 6
      framework/src/Volo.Abp.Telemetry/IApplicationScanner.cs
  33. 8
      framework/src/Volo.Abp.Telemetry/ITelemetryDataSender.cs
  34. 12
      framework/src/Volo.Abp.Telemetry/ITelemetryService.cs
  35. 47
      framework/src/Volo.Abp.Telemetry/TelemetryDataSender.cs
  36. 61
      framework/src/Volo.Abp.Telemetry/TelemetryService.cs

5
Directory.Packages.props

@ -167,6 +167,7 @@
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.1" />
<PackageVersion Include="System.Linq.Queryable" Version="4.3.0" />
<PackageVersion Include="System.Management" Version="7.0.2" />
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
<PackageVersion Include="System.Security.Permissions" Version="9.0.2" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
@ -183,5 +184,7 @@
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.2" />
<PackageVersion Include="CliWrap" Version="3.8.2"/>
<PackageVersion Include="System.IO.Abstractions" Version="22.0.14"/>
</ItemGroup>
</Project>
</Project>

14
framework/Volo.Abp.sln

@ -485,6 +485,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny.Tests", "test\Volo.Abp.BlobStoring.Bunny.Tests\Volo.Abp.BlobStoring.Bunny.Tests.csproj", "{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Telemetry", "src\Volo.Abp.Telemetry\Volo.Abp.Telemetry.csproj", "{C71F0E34-A7D8-49D0-B9FB-8B617CD1FC95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Telemetry.Tests", "test\Volo.Abp.Telemetry.Tests\Volo.Abp.Telemetry.Tests.csproj", "{C4E7CFFE-6770-4A92-928C-FC6E91EA6296}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1447,6 +1451,14 @@ Global
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Release|Any CPU.Build.0 = Release|Any CPU
{C71F0E34-A7D8-49D0-B9FB-8B617CD1FC95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C71F0E34-A7D8-49D0-B9FB-8B617CD1FC95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C71F0E34-A7D8-49D0-B9FB-8B617CD1FC95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C71F0E34-A7D8-49D0-B9FB-8B617CD1FC95}.Release|Any CPU.Build.0 = Release|Any CPU
{C4E7CFFE-6770-4A92-928C-FC6E91EA6296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4E7CFFE-6770-4A92-928C-FC6E91EA6296}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4E7CFFE-6770-4A92-928C-FC6E91EA6296}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4E7CFFE-6770-4A92-928C-FC6E91EA6296}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1691,6 +1703,8 @@ Global
{C753DDD6-5699-45F8-8669-08CE0BB816DE} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{1BBCBA72-CDB6-4882-96EE-D4CD149433A2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{C71F0E34-A7D8-49D0-B9FB-8B617CD1FC95} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{C4E7CFFE-6770-4A92-928C-FC6E91EA6296} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs

@ -7,6 +7,7 @@ public static class CliUrls
{
public const string WwwAbpIo = WwwAbpIoProduction;
public const string AccountAbpIo = AccountAbpIoProduction;
public const string TelemetryAbpIo = TelemetryAbpIoDevelopment;
public const string NuGetRootPath = NuGetRootPathProduction;
public const string LatestVersionCheckFullPath =
"https://raw.githubusercontent.com/abpframework/abp/dev/latest-versions.json";
@ -14,10 +15,12 @@ public static class CliUrls
public const string WwwAbpIoProduction = "https://abp.io/";
public const string AccountAbpIoProduction = "https://account.abp.io/";
public const string NuGetRootPathProduction = "https://nuget.abp.io/";
public const string TelemetryAbpIoProduction = "https://telemetry.abp.io/";
public const string WwwAbpIoDevelopment = "https://localhost:44328/";
public const string AccountAbpIoDevelopment = "https://localhost:44333/";
public const string NuGetRootPathDevelopment = "https://localhost:44373/";
public const string TelemetryAbpIoDevelopment = "https://localhost:44393/";
public static string GetNuGetServiceIndexUrl(string apiKey)
{

48
framework/src/Volo.Abp.Telemetry/AbpTelemetryModule.cs

@ -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);
});
}
}
}
}

76
framework/src/Volo.Abp.Telemetry/Activity/ActivityData.cs

@ -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;
}
}

205
framework/src/Volo.Abp.Telemetry/Activity/ActivityDataProvider.cs

@ -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
};
}
}

215
framework/src/Volo.Abp.Telemetry/Activity/ActivityStorage.cs

@ -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
}
}
}

40
framework/src/Volo.Abp.Telemetry/Activity/ActivityStorageState.cs

@ -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?
}

8
framework/src/Volo.Abp.Telemetry/Activity/IActivityDataProvider.cs

@ -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);
}

17
framework/src/Volo.Abp.Telemetry/Activity/IActivityStorage.cs

@ -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);
}

41
framework/src/Volo.Abp.Telemetry/ActivityEventHandler.cs

@ -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();
}
}
}

74
framework/src/Volo.Abp.Telemetry/ActivityNameConsts.cs

@ -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";
}

11
framework/src/Volo.Abp.Telemetry/ApplicationInfo.cs

@ -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
}

36
framework/src/Volo.Abp.Telemetry/ApplicationInfoProvider.cs

@ -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()
};
}
}

9
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Contracts/ISoftwareDetector.cs

@ -0,0 +1,9 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Telemetry.EnvironmentInspection.Contracts;
internal interface ISoftwareDetector : IScopedDependency
{
string Name { get; }
Task<SoftwareInfo?> DetectAsync();
}

6
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Contracts/ISoftwareInfoProvider.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.Telemetry.EnvironmentInspection.Contracts;
public interface ISoftwareInfoProvider
{
Task<List<SoftwareInfo>> GetSoftwareInfoAsync(CancellationToken cancellationToken = default);
}

9
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Contracts/SoftwareInfo.cs

@ -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;
}

51
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs

@ -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>();
}
}

43
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/ChromeDetector.cs

@ -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;
}
}

19
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/DotnetSdkDetector.cs

@ -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);
}
}

40
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/FireFoxDetector.cs

@ -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;
}
}

42
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/MsEdgeDetector.cs

@ -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;
}
}

27
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/NodeJsDetector.cs

@ -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;
}
}
}

92
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/OperatingSystemDetector.cs

@ -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;
}
}
}

94
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/RiderDetector.cs

@ -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);
}
}
}

118
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs

@ -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);
}
}

13
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/Detectors/VisualStudioDetector.cs

@ -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;
}
}

35
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/SoftwareDetector.cs

@ -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;
}
}
}

37
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/SoftwareInfoProvider.cs

@ -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;
}
}

251
framework/src/Volo.Abp.Telemetry/EnvironmentInspection/SoftwareType.cs

@ -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;
}
}

63
framework/src/Volo.Abp.Telemetry/Helpers/AbpPackageMetadataHelper.cs

@ -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; }
}

6
framework/src/Volo.Abp.Telemetry/IApplicationScanner.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.Telemetry;
public interface IApplicationScanner
{
ApplicationInfo Scan();
}

8
framework/src/Volo.Abp.Telemetry/ITelemetryDataSender.cs

@ -0,0 +1,8 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Telemetry;
public interface ITelemetryDataSender : IScopedDependency
{
Task SendAsync();
}

12
framework/src/Volo.Abp.Telemetry/ITelemetryService.cs

@ -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);
}

47
framework/src/Volo.Abp.Telemetry/TelemetryDataSender.cs

@ -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();
}
}
}

61
framework/src/Volo.Abp.Telemetry/TelemetryService.cs

@ -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…
Cancel
Save