From 7ed2bd97cc623af50bf49abe435781a01cdf1377 Mon Sep 17 00:00:00 2001 From: Emre KARA Date: Mon, 26 May 2025 14:24:04 +0300 Subject: [PATCH] Refactor telemetry modules and improve resilience. --- .../Activity/SolutionInfoProvider.cs | 162 +++++++++++------- .../SolutionModuleInstallationInfo.cs | 10 -- .../Volo/Abp/Telemetry/TelemetryService.cs | 53 +++--- 3 files changed, 119 insertions(+), 106 deletions(-) delete mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Contracts/SolutionModuleInstallationInfo.cs diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/SolutionInfoProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/SolutionInfoProvider.cs index 3575c07c7a..f14209be95 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/SolutionInfoProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/SolutionInfoProvider.cs @@ -13,31 +13,40 @@ using Volo.Abp.Telemetry.Shared.Enums; namespace Volo.Abp.Telemetry.Activity; -public class SolutionInfoProvider : ISolutionInfoProvider , ISingletonDependency +public class SolutionInfoProvider : ISolutionInfoProvider, ISingletonDependency { public async Task> GetSolutionInfoAsync(string solutionPath) { - var dictionary = new Dictionary(); + try + { + var dictionary = new Dictionary(); - var solutionJson = File.ReadAllText(solutionPath); - using var solutionDoc = JsonDocument.Parse(solutionJson); - var root = solutionDoc.RootElement; + var solutionJson = File.ReadAllText(solutionPath); + using var solutionDoc = JsonDocument.Parse(solutionJson); + var root = solutionDoc.RootElement; - if (root.TryGetProperty("creatingStudioConfiguration", out var config)) - { - AddCreatingStudioConfiguration(dictionary, config); - } + if (root.TryGetProperty("creatingStudioConfiguration", out var config)) + { + AddCreatingStudioConfiguration(dictionary, config); + } + + dictionary[ActivityPropertyName.LicenseType] = await GetLicenseTypeAsync(); - dictionary[ActivityPropertyName.LicenseType] = await GetLicenseTypeAsync(); + if (root.TryGetProperty("modules", out var modulesElement) && + modulesElement.ValueKind == JsonValueKind.Object) + { + var directoryPath = Path.GetDirectoryName(solutionPath); + var modules = ExtractModules(directoryPath!, modulesElement); + dictionary[ActivityPropertyName.InstalledModules] = modules; + } - if (root.TryGetProperty("modules", out var modulesElement) && modulesElement.ValueKind == JsonValueKind.Object) + return dictionary; + } + catch (Exception) { - var modules = ExtractModules(modulesElement); - dictionary[ActivityPropertyName.InstalledModules] = modules; + return new Dictionary(); } - - return dictionary; } public Guid? GetSolutionId(string solutionPath) @@ -48,7 +57,7 @@ public class SolutionInfoProvider : ISolutionInfoProvider , ISingletonDependency return ReadSolutionIdFromJson(root); } - + private Guid? ReadSolutionIdFromJson(JsonElement root) { return root.TryGetProperty("id", out var idElement) && Guid.TryParse(idElement.GetString(), out var id) @@ -56,57 +65,52 @@ public class SolutionInfoProvider : ISolutionInfoProvider , ISingletonDependency : null; } - protected virtual void AddCreatingStudioConfiguration(Dictionary dict, JsonElement config) + protected virtual void AddCreatingStudioConfiguration(Dictionary dict, JsonElement config) { - var mappings = new Dictionary> - { - { "template", value => dict[ActivityPropertyName.Template] = MapSolutionTemplate(value.GetString()) }, - { "createdAbpStudioVersion", value => dict[ActivityPropertyName.CreatedAbpStudioVersion] = value.GetString()! }, - { "multiTenancy", value => dict[ActivityPropertyName.MultiTenancy] = ParseBool(value) }, - { "uiFramework", value => dict[ActivityPropertyName.UiFramework] = MapUiFramework(value.GetString()) }, - { "databaseProvider", value => dict[ActivityPropertyName.DatabaseProvider] = MapDatabaseProvider(value.GetString()) }, - { "theme", value => dict[ActivityPropertyName.Theme] = MapUiTheme(value.GetString()) }, - { "themeStyle", value => dict[ActivityPropertyName.ThemeStyle] = MapUiThemeStyle(value.GetString()) }, - { "publicWebsite", value => dict[ActivityPropertyName.HasPublicWebsite] = ParseBool(value) }, - { "tiered", value => dict[ActivityPropertyName.IsTiered] = ParseBool(value) }, - { "socialLogin", value => dict[ActivityPropertyName.SocialLogins] = ParseBool(value) }, - { "databaseManagementSystem", value => dict[ActivityPropertyName.DatabaseManagementSystem] = MapDbms(value.GetString()) }, - { "separateTenantSchema", value => dict[ActivityPropertyName.IsSeparateTenantSchema] = ParseBool(value) }, - { "mobileFramework", value => dict[ActivityPropertyName.MobileFramework] = MapMobileApp(value.GetString()) }, - { "includeTests", value => dict[ActivityPropertyName.IncludeTests] = ParseBool(value) }, - { "dynamicLocalization", value => dict[ActivityPropertyName.DynamicLocalization] = ParseBool(value) }, - { "kubernetesConfiguration", value => dict[ActivityPropertyName.KubernetesConfiguration] = ParseBool(value) }, - { "grafanaDashboard", value => dict[ActivityPropertyName.GrafanaDashboard] = ParseBool(value) }, - }; - foreach (var mapping in mappings) + var mappings = new Dictionary> + { + { "template", value => dict[ActivityPropertyName.Template] = MapSolutionTemplate(value.GetString()) }, + { "createdAbpStudioVersion", value => dict[ActivityPropertyName.CreatedAbpStudioVersion] = value.GetString()! }, + { "multiTenancy", value => dict[ActivityPropertyName.MultiTenancy] = ParseBool(value) }, + { "uiFramework", value => dict[ActivityPropertyName.UiFramework] = MapUiFramework(value.GetString()) }, + { "databaseProvider", value => dict[ActivityPropertyName.DatabaseProvider] = MapDatabaseProvider(value.GetString()) }, + { "theme", value => dict[ActivityPropertyName.Theme] = MapUiTheme(value.GetString()) }, + { "themeStyle", value => dict[ActivityPropertyName.ThemeStyle] = MapUiThemeStyle(value.GetString()) }, + { "publicWebsite", value => dict[ActivityPropertyName.HasPublicWebsite] = ParseBool(value) }, + { "tiered", value => dict[ActivityPropertyName.IsTiered] = ParseBool(value) }, + { "socialLogin", value => dict[ActivityPropertyName.SocialLogins] = ParseBool(value) }, + { "databaseManagementSystem", value => dict[ActivityPropertyName.DatabaseManagementSystem] = MapDbms(value.GetString()) }, + { "separateTenantSchema", value => dict[ActivityPropertyName.IsSeparateTenantSchema] = ParseBool(value) }, + { "mobileFramework", value => dict[ActivityPropertyName.MobileFramework] = MapMobileApp(value.GetString()) }, + { "includeTests", value => dict[ActivityPropertyName.IncludeTests] = ParseBool(value) }, + { "dynamicLocalization", value => dict[ActivityPropertyName.DynamicLocalization] = ParseBool(value) }, + { "kubernetesConfiguration", value => dict[ActivityPropertyName.KubernetesConfiguration] = ParseBool(value) }, + { "grafanaDashboard", value => dict[ActivityPropertyName.GrafanaDashboard] = ParseBool(value) }, + }; + foreach (var mapping in mappings) + { + if (config.TryGetProperty(mapping.Key, out var propertyValue)) { - try - { - if (config.TryGetProperty(mapping.Key, out var propertyValue)) - { - mapping.Value(propertyValue); - } - } - catch - { - // ignored - } + mapping.Value(propertyValue); } + } } - private static bool ParseBool(JsonElement element) => - element.ValueKind switch + private static bool ParseBool(JsonElement element) + { + return element.ValueKind switch { JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.String when bool.TryParse(element.GetString(), out var b) => b, _ => false }; + } - protected virtual List ExtractModules(JsonElement modulesElement) + protected virtual List> ExtractModules(string directoryPath, JsonElement modulesElement) { - var modules = new List(); + var modules = new List>(); foreach (var module in modulesElement.EnumerateObject()) { @@ -117,12 +121,18 @@ public class SolutionInfoProvider : ISolutionInfoProvider , ISingletonDependency } var path = pathElement.GetString(); - if (path.IsNullOrEmpty() || !File.Exists(path)) + if (path.IsNullOrEmpty()) { continue; } + var modulePath = Path.Combine(directoryPath, path); - var moduleJson = File.ReadAllText(path); + if (!File.Exists(modulePath)) + { + continue; + } + + var moduleJson = File.ReadAllText(modulePath); using var moduleDoc = JsonDocument.Parse(moduleJson); if (!moduleDoc.RootElement.TryGetProperty("imports", out var imports) || @@ -138,14 +148,25 @@ public class SolutionInfoProvider : ISolutionInfoProvider , ISingletonDependency ? DateTimeOffset.Parse(ct.GetString()!) : (DateTimeOffset?)null; - if (modules.Any(x => x.ModuleName == name && x.Version == version)) + if (modules.Any(x => + x.ContainsKey("ModuleName") && (string)x["ModuleName"] == name && + x.ContainsKey("Version") && (string)x["Version"] == version)) { continue; } - modules.Add(new SolutionModuleInstallationInfo + if (modules.Any(x => + x.TryGetValue("ModuleName", out var moduleNameObj) && moduleNameObj as string == name && + x.TryGetValue("Version", out var versionObj) && versionObj as string == version)) { - ModuleName = name, Version = version, InstallationTime = creationTime + continue; + } + + modules.Add(new Dictionary + { + { "ModuleName", name }, + { "Version", version ?? string.Empty }, + { "InstallationTime", creationTime ?? DateTime.MinValue } }); } } @@ -160,14 +181,27 @@ public class SolutionInfoProvider : ISolutionInfoProvider , ISingletonDependency return 0; } - using var httpClient = new HttpClient(); - var accessToken = File.ReadAllText(AbpTelemetryPaths.AccessToken); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + try + { + using var httpClient = new HttpClient(); + var accessToken = File.ReadAllText(AbpTelemetryPaths.AccessToken); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var httpResponse = await httpClient.GetAsync($"{AbpPlatformUrls.AbpIoUrl}api/license/api-key"); + if (!httpResponse.IsSuccessStatusCode) + { + return 0; + } - var json = await httpClient.GetStringAsync($"{AbpPlatformUrls.AbpIoUrl}api/license/api-key"); - using var doc = JsonDocument.Parse(json); + var responseContent = await httpResponse.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(responseContent); - return doc.RootElement.GetProperty("licenseType").GetInt32(); + return doc.RootElement.GetProperty("licenseType").GetInt32(); + } + catch + { + return 0; + } } protected virtual UiTheme MapUiTheme(string? theme) => theme switch diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Contracts/SolutionModuleInstallationInfo.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Contracts/SolutionModuleInstallationInfo.cs deleted file mode 100644 index 43a59c8c98..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Contracts/SolutionModuleInstallationInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Volo.Abp.Telemetry.EnvironmentInspection.Contracts; - -public class SolutionModuleInstallationInfo -{ - public string? ModuleName { get; set; } - public string? Version { get; set; } - public DateTimeOffset? InstallationTime { get; set; } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs index ef60105b83..bc8e0196d9 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs @@ -7,13 +7,14 @@ using Volo.Abp.Telemetry.Activity; namespace Volo.Abp.Telemetry; -public class TelemetryService : ITelemetryService , IScopedDependency +public class TelemetryService : ITelemetryService, IScopedDependency { private readonly IActivityStorage _activityStorage; private readonly ITelemetryDataSender _telemetryDataSender; private readonly IActivityDataProvider _activityDataProvider; - public TelemetryService(IActivityStorage activityStorage, ITelemetryDataSender telemetryDataSender, IActivityDataProvider activityDataProvider) + public TelemetryService(IActivityStorage activityStorage, ITelemetryDataSender telemetryDataSender, + IActivityDataProvider activityDataProvider) { _activityStorage = activityStorage; _telemetryDataSender = telemetryDataSender; @@ -23,10 +24,7 @@ public class TelemetryService : ITelemetryService , IScopedDependency public IAsyncDisposable TrackActivity(string activityName, Action? configure = null) { var stopwatch = Stopwatch.StartNew(); - var activityData = new ActivityData(activityName) - { - Time = DateTimeOffset.UtcNow - }; + var activityData = new ActivityData(activityName) { Time = DateTimeOffset.UtcNow }; configure?.Invoke(activityData); @@ -59,36 +57,30 @@ public class TelemetryService : ITelemetryService , IScopedDependency // { // await _telemetryDataSender.SendAsync(); // } - - if (lastActivitySendTime is not null && lastActivitySendTime < DateTimeOffset.UtcNow.AddDays(-1) ) + + if (lastActivitySendTime is not null && lastActivitySendTime < DateTimeOffset.UtcNow.AddDays(-1)) { await _telemetryDataSender.SendAsync(); } } - public Task AddActivityAsync(ActivityData data) - { - Task.Run(async () => + public async Task AddActivityAsync(ActivityData data) + { + try { - try - { - await _activityDataProvider.AddExtraInformationAsync(data); - await _activityStorage.BufferActivityAsync(data); - await CheckIfActivitySendTimeIsDueAsync(); - - if (data.ActivityName == ActivityNameConsts.AbpStudioClose) - { - await _activityStorage.EndSessionAsync(); - } - } - catch + await _activityDataProvider.AddExtraInformationAsync(data); + await _activityStorage.BufferActivityAsync(data); + await CheckIfActivitySendTimeIsDueAsync(); + + if (data.ActivityName == ActivityNameConsts.AbpStudioClose) { - + await _activityStorage.EndSessionAsync(); } - }); - - - return Task.CompletedTask; + } + catch + { + // ignored + } } public async Task AddActivityAsync(string activityName, string? details = null) @@ -98,10 +90,7 @@ public class TelemetryService : ITelemetryService , IScopedDependency public async Task AddActivityAsync(string activityName, Action configure) { - var activityData = new ActivityData(activityName) - { - Time = DateTimeOffset.UtcNow - }; + var activityData = new ActivityData(activityName) { Time = DateTimeOffset.UtcNow }; configure?.Invoke(activityData);