diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/ActivityData.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/ActivityData.cs index 8098188552..b9191aea4a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/ActivityData.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/ActivityData.cs @@ -7,10 +7,9 @@ public class ActivityData : Dictionary { public ActivityData() { - } - - public ActivityData(string activityName, string? details = null) + + public ActivityData(string activityName, string? detail = null) { if (activityName.IsNullOrWhiteSpace()) { @@ -18,29 +17,37 @@ public class ActivityData : Dictionary } ActivityName = activityName; - ActivityDetails = details; + ActivityDetail = detail; } - - public string ActivityName - { - get => (string) this[nameof(ActivityName)]; + + public string ActivityName { + get => (string)this[nameof(ActivityName)]; set => this[nameof(ActivityName)] = value; } - public string? ActivityDetails - { - get => (string?) this[nameof(ActivityDetails)]; - internal set - { - if (value is not null) this[nameof(ActivityDetails)] = value; + public string? ActivityDetail { + get => (string?)this[nameof(ActivityDetail)]; + internal set { + if (value is not null) + { + this[nameof(ActivityDetail)] = value; + } } } - public long? ActivityDuration - { - get => (long?) this[nameof(ActivityDuration)]; - internal set - { + public Dictionary? ActivityDetails { + get => (Dictionary?)this[nameof(ActivityDetail)]; + internal set { + if (value is not null) + { + this[nameof(ActivityDetail)] = value; + } + } + } + + public long? ActivityDuration { + get => (long?)this[nameof(ActivityDuration)]; + internal set { if (value is not null) { this[nameof(ActivityDuration)] = value; @@ -49,6 +56,4 @@ public class ActivityData : Dictionary } public DateTimeOffset Time = DateTimeOffset.UtcNow; - - } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataBuilder.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataBuilder.cs new file mode 100644 index 0000000000..84117ab971 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataBuilder.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Telemetry.Activity.Contracts; + +public interface ITelemetryActivityDataBuilder +{ + Task BuildAsync(ActivityData activity); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataEnricher.cs new file mode 100644 index 0000000000..bf2a3c54d2 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataEnricher.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Telemetry.Activity.Contracts; + +public interface ITelemetryActivityDataEnricher +{ + Task EnrichAsync(ActivityData activity); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataProvider.cs deleted file mode 100644 index c1e5b9a2d0..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Telemetry.Activity.Contracts; - -public interface ITelemetryActivityDataProvider -{ - Task AddExtraInformationAsync(ActivityData activity); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs index b180b27ba3..dafa727e0d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs @@ -7,9 +7,10 @@ namespace Volo.Abp.Telemetry.Activity.Contracts; public interface ITelemetryActivityStorage { Task GetLastActivitySendTimeAsync(); - Task<(bool isFirstSession, Guid sessionId)> GetOrCreateSessionInfoAsync(); + Task GetOrCreateSessionInfoAsync(); Task MarkActivitiesAsSentAsync(); - Task MarkDeviceInfoAsSentAsync(); + Task MarkDeviceInfoAsAddedAsync(); + Task MarkSolutionInfoAsAddedAsync(Guid solutionId); Task GetLastSolutionInfoSendTimeAsync(Guid solutionId); Task GetLastDeviceInfoSendTimeAsync(); Task BufferActivityAsync(ActivityData activityData); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetrySolutionInfoProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetrySolutionInfoProvider.cs index a91c0846d9..b7e8171497 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetrySolutionInfoProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetrySolutionInfoProvider.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Telemetry.Activity.Contracts; public interface ITelemetrySolutionInfoProvider { - Task> GetSolutionInfoAsync(string solutionPath); + Task FillSolutionInfoAsync(ActivityData activityData, string solutionPath); Guid? GetSolutionId(string solutionPath); } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataBuilder.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataBuilder.cs new file mode 100644 index 0000000000..e2e8038374 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataBuilder.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Telemetry.Activity.Contracts; +using Volo.Abp.Telemetry.Constants; + +namespace Volo.Abp.Telemetry.Activity.Providers; + +public class TelemetryActivityDataBuilder : ITelemetryActivityDataBuilder, ISingletonDependency +{ + private readonly IServiceProvider _serviceProvider; + + public TelemetryActivityDataBuilder(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public virtual async Task BuildAsync(ActivityData activity) + { + var telemetrySessionTypeProvider = _serviceProvider.GetRequiredService(); + var telemetryActivityStorage = _serviceProvider.GetRequiredService(); + + activity[ActivityPropertyName.SessionType] = telemetrySessionTypeProvider.SessionType; + activity[ActivityPropertyName.SessionId] = await telemetryActivityStorage.GetOrCreateSessionInfoAsync(); + activity[ActivityPropertyName.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); + + var activityDataEnrichers = _serviceProvider.GetRequiredService>(); + + foreach (var activityDataEnricher in activityDataEnrichers) + { + await activityDataEnricher.EnrichAsync(activity); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataProvider.cs deleted file mode 100644 index 7e809205a0..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataProvider.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Telemetry.Activity.Contracts; -using Volo.Abp.Telemetry.Constants; -using Volo.Abp.Telemetry.Constants.Enums; -using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; - -namespace Volo.Abp.Telemetry.Activity.Providers; - -public class TelemetryActivityDataProvider : ITelemetryActivityDataProvider, ISingletonDependency -{ - private readonly IServiceProvider _serviceProvider; - - public TelemetryActivityDataProvider(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public virtual async Task AddExtraInformationAsync(ActivityData activity) - { - var activityStorage = _serviceProvider.GetRequiredService(); - var sessionContext = _serviceProvider.GetRequiredService(); - var deviceInfoProvider = _serviceProvider.GetRequiredService(); - - var (isFirstSession, sessionId) = await activityStorage.GetOrCreateSessionInfoAsync(); - - sessionContext.SetSolutionContext(activity); - activity[ActivityPropertyName.SessionType] = sessionContext.SessionType; - activity[ActivityPropertyName.SessionId] = sessionId; - activity[ActivityPropertyName.IsFirstSession] = isFirstSession; - activity[ActivityPropertyName.DeviceId] = deviceInfoProvider.GetDeviceId(); - - if (await activityStorage.ShouldAddDeviceInfoAsync()) - { - activity[ActivityPropertyName.DeviceType] = deviceInfoProvider.GetDeviceType(); - activity[ActivityPropertyName.DeviceLanguage] = deviceInfoProvider.GetLanguage(); - activity[ActivityPropertyName.OperatingSystem] = deviceInfoProvider.GetOperatingSystem(); - activity[ActivityPropertyName.CountryIsoCode] = deviceInfoProvider.GetCountry(); - - var softwareInfo = _serviceProvider.GetRequiredService(); - var softwareList = await softwareInfo.GetSoftwareInfoAsync(); - activity[ActivityPropertyName.InstalledSoftwares] = softwareList; - - await activityStorage.MarkDeviceInfoAsSentAsync(); - } - - if (activity.ContainsKey(ActivityPropertyName.Assembly) && sessionContext.SessionType == SessionType.ApplicationRuntime) - { - var contributors = _serviceProvider.GetRequiredService>(); - foreach (var contributor in contributors) - { - await contributor.ContributeAsync(activity); - } - activity.Remove(ActivityPropertyName.Assembly); - } - - if (activity.TryGetValue(ActivityPropertyName.SolutionId, out var rawSolutionId) && Guid.TryParse(rawSolutionId?.ToString(), out var solutionId)) - { - - if (await activityStorage.ShouldAddSolutionInformation(solutionId)) - { - var solutionProvider = _serviceProvider.GetRequiredService(); - var solutionInfo = await solutionProvider.GetSolutionInfoAsync(activity[ActivityPropertyName.SolutionPath].ToString()!); - - foreach (var entry in solutionInfo) - { - activity[entry.Key] = entry.Value; - } - } - } - } - -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs new file mode 100644 index 0000000000..9de497f367 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Telemetry.Activity.Contracts; +using Volo.Abp.Telemetry.Constants; +using Volo.Abp.Telemetry.Constants.Enums; + +namespace Volo.Abp.Telemetry.Activity.Providers; + +[ExposeServices(typeof(ITelemetryActivityDataEnricher))] +public class TelemetryApplicationInfoEnricher : ITelemetryActivityDataEnricher, ISingletonDependency +{ + private readonly ITelemetrySessionTypeProvider _telemetrySessionTypeProvider; + private readonly IEnumerable _telemetryApplicationInfoContributors; + + public TelemetryApplicationInfoEnricher(ITelemetrySessionTypeProvider telemetrySessionTypeProvider, + IEnumerable telemetryApplicationInfoContributors) + { + _telemetrySessionTypeProvider = telemetrySessionTypeProvider; + _telemetryApplicationInfoContributors = telemetryApplicationInfoContributors; + } + + public async Task EnrichAsync(ActivityData activity) + { + if (activity.ContainsKey(ActivityPropertyName.Assembly) && _telemetrySessionTypeProvider.SessionType == SessionType.ApplicationRuntime) + { + foreach (var contributor in _telemetryApplicationInfoContributors) + { + await contributor.ContributeAsync(activity); + } + + activity.Remove(ActivityPropertyName.Assembly); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Providers/DeviceInfoProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs similarity index 71% rename from framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Providers/DeviceInfoProvider.cs rename to framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs index 1b2ad33b4d..3cefb7ce5e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Providers/DeviceInfoProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs @@ -3,33 +3,60 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -#if WINDOWS -using System.Management; -#endif using System.Runtime.InteropServices; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Telemetry.Activity.Contracts; using Volo.Abp.Telemetry.Constants; using Volo.Abp.Telemetry.Constants.Enums; using Volo.Abp.Telemetry.EnvironmentInspection.Contracts; +#if WINDOWS +using System.Management; +#endif -namespace Volo.Abp.Telemetry.EnvironmentInspection.Providers; +namespace Volo.Abp.Telemetry.Activity.Providers; -public class DeviceInfoProvider : IDeviceInfoProvider, ISingletonDependency +[ExposeServices(typeof(ITelemetryActivityDataEnricher))] +public class TelemetryDeviceInfoEnricher : ITelemetryActivityDataEnricher, ISingletonDependency { + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + private readonly ISoftwareInfoProvider _softwareInfoProvider; private readonly Lazy _deviceId; - public DeviceInfoProvider() + public TelemetryDeviceInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, ISoftwareInfoProvider softwareInfoProvider) { + _telemetryActivityStorage = telemetryActivityStorage; + _softwareInfoProvider = softwareInfoProvider; _deviceId = new Lazy(() => { var deviceIdText = File.ReadAllText(TelemetryPaths.ComputerId); - return deviceIdText.To(); + return deviceIdText.To(); }); } - public Guid GetDeviceId() + public async Task ShouldEnrichAsync(ActivityData activity) + { + return await _telemetryActivityStorage.ShouldAddDeviceInfoAsync(); + } + + public async Task EnrichAsync(ActivityData activity) { - return _deviceId.Value; + + activity[ActivityPropertyName.DeviceId] = _deviceId.Value; + if (!await _telemetryActivityStorage.ShouldAddDeviceInfoAsync()) + { + return; + } + + activity[ActivityPropertyName.DeviceType] = GetDeviceType(); + activity[ActivityPropertyName.DeviceLanguage] = GetLanguage(); + activity[ActivityPropertyName.OperatingSystem] = GetOperatingSystem(); + activity[ActivityPropertyName.CountryIsoCode] = GetCountry(); + + var softwareList = await _softwareInfoProvider.GetSoftwareInfoAsync(); + activity[ActivityPropertyName.InstalledSoftwares] = softwareList; + + await _telemetryActivityStorage.MarkDeviceInfoAsAddedAsync(); } public OperationSystem GetOperatingSystem() @@ -168,4 +195,6 @@ public class DeviceInfoProvider : IDeviceInfoProvider, ISingletonDependency return DeviceType.Unknown; } + + } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs new file mode 100644 index 0000000000..c1aa631c7b --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Telemetry.Activity.Contracts; +using Volo.Abp.Telemetry.Constants; + +namespace Volo.Abp.Telemetry.Activity.Providers; + +[ExposeServices(typeof(ITelemetryActivityDataEnricher))] +public class TelemetrySolutionInfoEnricher : ITelemetryActivityDataEnricher, ISingletonDependency +{ + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + + public TelemetrySolutionInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage) + { + _telemetryActivityStorage = telemetryActivityStorage; + } + + + + public async Task EnrichAsync(ActivityData activity) + { + var solutionId = GetSolutionId(activity); + if (solutionId.HasValue) + { + if (await _telemetryActivityStorage.ShouldAddSolutionInformation(solutionId.Value)) + { + await FillSolutionInfoAsync(activity); + await _telemetryActivityStorage.MarkSolutionInfoAsAddedAsync(solutionId.Value); + } + + activity.Remove(ActivityPropertyName.SolutionPath); + } + } + + private Guid? GetSolutionId(ActivityData activity) + { + if (TryGetSolutionIdFromActivity(activity, out var solutionId)) + { + return solutionId; + } + + if (TryGetSolutionIdFromFile(activity, out var idFromFile)) + { + activity[ActivityPropertyName.SolutionId] = idFromFile; + return idFromFile; + } + + return null; + } + private async Task FillSolutionInfoAsync(ActivityData activityData) + { + try + { + if (!activityData.TryGetValue(ActivityPropertyName.SolutionPath, out var rawSolutionPath) + || string.IsNullOrWhiteSpace(rawSolutionPath?.ToString()) + || !File.Exists(rawSolutionPath?.ToString())) + { + return; + } + + var solutionPath = rawSolutionPath!.ToString(); + + if (!File.Exists(solutionPath)) + { + return; + } + + var jsonContent = File.ReadAllText(solutionPath); + using var doc = JsonDocument.Parse(jsonContent); + var root = doc.RootElement; + + AddSolutionInformation(activityData, root.GetProperty("creatingStudioConfiguration")); + + activityData[ActivityPropertyName.LicenseType] = await GetLicenseTypeAsync(); + + if (root.TryGetProperty("modules", out var modulesElement) && + modulesElement.ValueKind == JsonValueKind.Object) + { + var directoryPath = Path.GetDirectoryName(solutionPath)!; + activityData[ActivityPropertyName.InstalledModules] = ExtractModules(directoryPath, modulesElement); + } + } + catch + { + // ignored + } + } + + private bool TryGetSolutionIdFromActivity(ActivityData activity, out Guid solutionId) + { + solutionId = Guid.Empty; + + if (activity.TryGetValue(ActivityPropertyName.SolutionId, out var value) && + Guid.TryParse(value?.ToString(), out var parsedId)) + { + solutionId = parsedId; + return true; + } + + return false; + } + + private bool TryGetSolutionIdFromFile(ActivityData activity, out Guid solutionId) + { + solutionId = Guid.Empty; + + if (!activity.TryGetValue(ActivityPropertyName.SolutionPath, out var value)) + { + return false; + } + + var path = value?.ToString(); + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + { + return false; + } + + try + { + var json = File.ReadAllText(path); + using var doc = JsonDocument.Parse(json); + if (doc.RootElement.TryGetProperty("id", out var idProp) && + Guid.TryParse(idProp.GetString(), out var parsedId)) + { + solutionId = parsedId; + return true; + } + } + catch + { + // ignore malformed json etc. + } + + return false; + } + + private void AddSolutionInformation(Dictionary activity, JsonElement config) + { + var map = new (string Key, Action Apply)[] + { + ("template", v => activity[ActivityPropertyName.Template] = v.GetString() ?? string.Empty), + ("createdAbpStudioVersion", v => activity[ActivityPropertyName.CreatedAbpStudioVersion] = v.GetString()!), + ("multiTenancy", v => activity[ActivityPropertyName.MultiTenancy] = ParseBool(v)), + ("uiFramework", v => activity[ActivityPropertyName.UiFramework] = v.GetString() ?? string.Empty), + ("databaseProvider", v => activity[ActivityPropertyName.DatabaseProvider] = v.GetString() ?? string.Empty), + ("theme", v => activity[ActivityPropertyName.Theme] = v.GetString() ?? string.Empty), + ("themeStyle", v => activity[ActivityPropertyName.ThemeStyle] = v.GetString() ?? string.Empty), + ("publicWebsite", v => activity[ActivityPropertyName.HasPublicWebsite] = ParseBool(v)), + ("tiered", v => activity[ActivityPropertyName.IsTiered] = ParseBool(v)), + ("socialLogin", v => activity[ActivityPropertyName.SocialLogins] = ParseBool(v)), + ("databaseManagementSystem", v => activity[ActivityPropertyName.DatabaseManagementSystem] = v.GetString() ?? string.Empty), + ("separateTenantSchema", v => activity[ActivityPropertyName.IsSeparateTenantSchema] = ParseBool(v)), + ("mobileFramework", v => activity[ActivityPropertyName.MobileFramework] = v.GetString() ?? string.Empty), + ("includeTests", v => activity[ActivityPropertyName.IncludeTests] = ParseBool(v)), + ("dynamicLocalization", v => activity[ActivityPropertyName.DynamicLocalization] = ParseBool(v)), + ("kubernetesConfiguration", v => activity[ActivityPropertyName.KubernetesConfiguration] = ParseBool(v)), + ("grafanaDashboard", v => activity[ActivityPropertyName.GrafanaDashboard] = ParseBool(v)), + }; + + foreach (var (key, apply) in map) + { + if (config.TryGetProperty(key, out var prop)) + { + apply(prop); + } + } + } + + private List> ExtractModules(string directoryPath, JsonElement modulesElement) + { + var modules = new List>(); + + foreach (var module in modulesElement.EnumerateObject()) + { + if (!module.Value.TryGetProperty("path", out var pathElement)) + { + continue; + } + + var path = pathElement.GetString(); + if (string.IsNullOrWhiteSpace(path)) + { + continue; + } + + var fullPath = Path.Combine(directoryPath, path); + if (!File.Exists(fullPath)) + { + continue; + } + + var moduleJson = File.ReadAllText(fullPath); + using var moduleDoc = JsonDocument.Parse(moduleJson); + + if (!moduleDoc.RootElement.TryGetProperty("imports", out var imports) || + imports.ValueKind != JsonValueKind.Object) + { + continue; + } + + foreach (var import in imports.EnumerateObject()) + { + var version = import.Value.GetProperty("version").GetString(); + var creationTime = import.Value.TryGetProperty("creationTime", out var ct) + ? DateTimeOffset.Parse(ct.GetString()!) + : (DateTimeOffset?)null; + + if (modules.Any(x => + x.TryGetValue(ActivityPropertyName.ModuleName, out var n) && n as string == import.Name && + x.TryGetValue(ActivityPropertyName.ModuleVersion, out var v) && v as string == version)) + { + continue; + } + + var moduleEntry = new Dictionary { { ActivityPropertyName.ModuleName, import.Name } }; + if (!version.IsNullOrEmpty()) + { + moduleEntry[ActivityPropertyName.ModuleVersion] = version; + } + + if (creationTime.HasValue) + { + moduleEntry[ActivityPropertyName.ModuleInstallationTime] = creationTime.Value; + } + + modules.Add(moduleEntry); + } + } + + return modules; + } + + private 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 + }; + } + + private async Task GetLicenseTypeAsync() + { + if (!File.Exists(TelemetryPaths.AccessToken)) + { + return 0; + } + + try + { + using var httpClient = new HttpClient(); + var accessToken = File.ReadAllText(TelemetryPaths.AccessToken); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.GetAsync($"{AbpPlatformUrls.AbpIoUrl}api/license/api-key"); + if (!response.IsSuccessStatusCode) + { + return 0; + } + + var content = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(content); + return doc.RootElement.GetProperty("licenseType").GetInt32(); + } + catch + { + return 0; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoProvider.cs deleted file mode 100644 index 613e81e394..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoProvider.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text.Json; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Telemetry.Activity.Contracts; -using Volo.Abp.Telemetry.Constants; - -namespace Volo.Abp.Telemetry.Activity.Providers; - -public class TelemetrySolutionInfoProvider : ITelemetrySolutionInfoProvider, ISingletonDependency -{ - public async Task> GetSolutionInfoAsync(string solutionPath) - { - try - { - var result = new Dictionary(); - var jsonContent = File.ReadAllText(solutionPath); - using var doc = JsonDocument.Parse(jsonContent); - var root = doc.RootElement; - - AddSolutionInformation(result, root.GetProperty("creatingStudioConfiguration")); - - result[ActivityPropertyName.LicenseType] = await GetLicenseTypeAsync(); - - if (root.TryGetProperty("modules", out var modulesElement) && modulesElement.ValueKind == JsonValueKind.Object) - { - var directoryPath = Path.GetDirectoryName(solutionPath)!; - result[ActivityPropertyName.InstalledModules] = ExtractModules(directoryPath, modulesElement); - } - - return result; - } - catch - { - return new Dictionary(); - } - } - - public Guid? GetSolutionId(string solutionPath) - { - var jsonContent = File.ReadAllText(solutionPath); - using var doc = JsonDocument.Parse(jsonContent); - return TryGetGuid(doc.RootElement, "id"); - } - - private Guid? TryGetGuid(JsonElement root, string propertyName) - { - return root.TryGetProperty(propertyName, out var prop) && Guid.TryParse(prop.GetString(), out var id) ? id : null; - } - - private void AddSolutionInformation(Dictionary dict, JsonElement config) - { - var map = new (string Key, Action Apply)[] - { - ("template", v => dict[ActivityPropertyName.Template] = v.GetString() ?? string.Empty), - ("createdAbpStudioVersion", v => dict[ActivityPropertyName.CreatedAbpStudioVersion] = v.GetString()!), - ("multiTenancy", v => dict[ActivityPropertyName.MultiTenancy] = ParseBool(v)), - ("uiFramework", v => dict[ActivityPropertyName.UiFramework] = v.GetString() ?? string.Empty), - ("databaseProvider", v => dict[ActivityPropertyName.DatabaseProvider] = v.GetString() ?? string.Empty), - ("theme", v => dict[ActivityPropertyName.Theme] = v.GetString() ?? string.Empty), - ("themeStyle", v => dict[ActivityPropertyName.ThemeStyle] = v.GetString() ?? string.Empty), - ("publicWebsite", v => dict[ActivityPropertyName.HasPublicWebsite] = ParseBool(v)), - ("tiered", v => dict[ActivityPropertyName.IsTiered] = ParseBool(v)), - ("socialLogin", v => dict[ActivityPropertyName.SocialLogins] = ParseBool(v)), - ("databaseManagementSystem", v => dict[ActivityPropertyName.DatabaseManagementSystem] = v.GetString() ?? string.Empty), - ("separateTenantSchema", v => dict[ActivityPropertyName.IsSeparateTenantSchema] = ParseBool(v)), - ("mobileFramework", v => dict[ActivityPropertyName.MobileFramework] = v.GetString() ?? string.Empty), - ("includeTests", v => dict[ActivityPropertyName.IncludeTests] = ParseBool(v)), - ("dynamicLocalization", v => dict[ActivityPropertyName.DynamicLocalization] = ParseBool(v)), - ("kubernetesConfiguration", v => dict[ActivityPropertyName.KubernetesConfiguration] = ParseBool(v)), - ("grafanaDashboard", v => dict[ActivityPropertyName.GrafanaDashboard] = ParseBool(v)), - }; - - foreach (var (key, apply) in map) - { - if (config.TryGetProperty(key, out var prop)) - { - apply(prop); - } - } - } - - private List> ExtractModules(string directoryPath, JsonElement modulesElement) - { - var modules = new List>(); - - foreach (var module in modulesElement.EnumerateObject()) - { - if (!module.Value.TryGetProperty("path", out var pathElement)) - { - continue; - } - - var path = pathElement.GetString(); - if (string.IsNullOrWhiteSpace(path)) - { - continue; - } - - var fullPath = Path.Combine(directoryPath, path); - if (!File.Exists(fullPath)) - { - continue; - } - - var moduleJson = File.ReadAllText(fullPath); - using var moduleDoc = JsonDocument.Parse(moduleJson); - - if (!moduleDoc.RootElement.TryGetProperty("imports", out var imports) || imports.ValueKind != JsonValueKind.Object) - { - continue; - } - - foreach (var import in imports.EnumerateObject()) - { - var version = import.Value.GetProperty("version").GetString(); - var creationTime = import.Value.TryGetProperty("creationTime", out var ct) ? DateTimeOffset.Parse(ct.GetString()!) : (DateTimeOffset?)null; - - if (modules.Any(x => - x.TryGetValue(ActivityPropertyName.ModuleName, out var n) && n as string == module.Name && - x.TryGetValue(ActivityPropertyName.ModuleVersion, out var v) && v as string == version)) - { - continue; - } - - var moduleEntry = new Dictionary { { ActivityPropertyName.ModuleName, module.Name } }; - if (!version.IsNullOrEmpty()) - { - moduleEntry[ActivityPropertyName.ModuleVersion] = version; - } - - if (creationTime.HasValue) - { - moduleEntry[ActivityPropertyName.ModuleInstallationTime] = creationTime.Value; - } - - modules.Add(moduleEntry); - } - } - - return modules; - } - - 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 async Task GetLicenseTypeAsync() - { - if (!File.Exists(TelemetryPaths.AccessToken)) - { - return 0; - } - - try - { - using var httpClient = new HttpClient(); - var accessToken = File.ReadAllText(TelemetryPaths.AccessToken); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var response = await httpClient.GetAsync($"{AbpPlatformUrls.AbpIoUrl}api/license/api-key"); - if (!response.IsSuccessStatusCode) - { - return 0; - } - - var content = await response.Content.ReadAsStringAsync(); - using var doc = JsonDocument.Parse(content); - return doc.RootElement.GetProperty("licenseType").GetInt32(); - } - catch - { - return 0; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorage.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorage.cs index 8bd41cfbab..e2e15801ef 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorage.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorage.cs @@ -37,24 +37,6 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep } - public async Task MarkApplicationInfoAsSentAsync(Guid applicationId) - { - var state = await GetStateAsync(); - state.ApplicationInfos[applicationId] = DateTimeOffset.UtcNow; - await SaveAsync(); - } - - public async Task GetApplicationInfoLastActivitySendTimeAsync(Guid applicationId) - { - var state = await GetStateAsync(); - if (state.ApplicationInfos.TryGetValue(applicationId, out var lastActivitySendTime)) - { - return lastActivitySendTime; - } - - return null; - } - public async Task GetLastActivitySendTimeAsync() { var state = await GetStateAsync(); @@ -62,13 +44,12 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep } - - public async Task<(bool isFirstSession, Guid sessionId)> GetOrCreateSessionInfoAsync() + public async Task GetOrCreateSessionInfoAsync() { var state = await GetStateAsync(); state.SessionId ??= Guid.NewGuid(); await SaveAsync(); - return (!File.Exists(TelemetryPaths.ActivityStorage), state.SessionId.Value); + return state.SessionId.Value; } public async Task MarkActivitiesAsSentAsync() @@ -81,6 +62,13 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep await SaveAsync(); } + public async Task MarkSolutionInfoAsAddedAsync(Guid solutionId) + { + var state = await GetStateAsync(); + state.Solutions[solutionId] = DateTimeOffset.UtcNow; + await SaveAsync(); + } + public async Task GetLastSolutionInfoSendTimeAsync(Guid id) { var state = await GetStateAsync(); @@ -96,20 +84,13 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep public async Task GetLastDeviceInfoSendTimeAsync() { var state = await GetStateAsync(); - return state.LastDeviceInfoSendTime; - } - - public async Task MarkSolutionInfoAsSentAsync(Guid id) - { - var state = await GetStateAsync(); - state.Solutions[id] = DateTimeOffset.UtcNow; - await SaveAsync(); + return state.LastDeviceInfoAddTime; } - public async Task MarkDeviceInfoAsSentAsync() + public async Task MarkDeviceInfoAsAddedAsync() { var state = await GetStateAsync(); - state.LastDeviceInfoSendTime = DateTimeOffset.UtcNow; + state.LastDeviceInfoAddTime = DateTimeOffset.UtcNow; await SaveAsync(); } @@ -125,10 +106,9 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep { using var reader = new StreamReader(stream, Encoding.UTF8); var json = await reader.ReadToEndAsync(); - return JsonSerializer.Deserialize(json, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }) ?? new TelemetryActivityStorageState(); + return JsonSerializer.Deserialize(json, + new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) ?? + new TelemetryActivityStorageState(); }); return _cachedState; } @@ -168,11 +148,7 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep private Task SaveAsync() { var json = JsonSerializer.Serialize(_cachedState ?? new TelemetryActivityStorageState(), - new JsonSerializerOptions - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }); + new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); File.WriteAllText(TelemetryPaths.ActivityStorage, json, Encoding.UTF8); return Task.CompletedTask; } @@ -194,8 +170,7 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep var json = JsonSerializer.Serialize(_cachedState ?? new TelemetryActivityStorageState(), new JsonSerializerOptions { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); File.WriteAllText(TelemetryPaths.ActivityStorage, json, Encoding.UTF8); } @@ -207,8 +182,8 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep return Task.CompletedTask; } - - + + public virtual async Task ShouldAddDeviceInfoAsync() { var lastSend = await GetLastDeviceInfoSendTimeAsync(); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs index 0065b29502..e7d290492d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Telemetry.Activity.Storage; public class TelemetryActivityStorageState { public DateTimeOffset? ActivitySendTime { get; set; } - public DateTimeOffset? LastDeviceInfoSendTime { get; set; } + public DateTimeOffset? LastDeviceInfoAddTime { get; set; } public Guid? SessionId { get; set; } public List Activities { get; set; } = new(); public Dictionary Solutions { get; set; } = new(); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Constants/ActivityPropertyName.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Constants/ActivityPropertyName.cs index 30711f4eed..d590b081b8 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Constants/ActivityPropertyName.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Constants/ActivityPropertyName.cs @@ -3,6 +3,9 @@ public static class ActivityPropertyName { public const string SessionId = nameof(SessionId); + public const string ActivityName = nameof(ActivityName); + public const string Error = nameof(Error); + public const string ErrorDetail = nameof(ErrorDetail); public const string Id = nameof(Id); public const string UserId = nameof(UserId); public const string OrganizationId = nameof(OrganizationId); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetryService.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetryService.cs index 25a2567bc5..092cb02a61 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetryService.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetryService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Telemetry.Activity; @@ -12,4 +13,6 @@ public interface ITelemetryService Task AddActivityAsync(ActivityData data); Task AddActivityAsync(string activityName, string? details = null); Task AddActivityAsync(string activityName, Action configure); + Task AddErrorActivityAsync(Action> configure); + Task AddErrorForActivityAsync(string failingActivity, string errorMessage); } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionContextProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionContextProvider.cs deleted file mode 100644 index 0e4ef65442..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionContextProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Volo.Abp.Telemetry.Activity; -using Volo.Abp.Telemetry.Constants.Enums; - -namespace Volo.Abp.Telemetry; - -public interface ITelemetrySessionContextProvider -{ - public SessionType SessionType { get; } - void SetSolutionContext(ActivityData activity); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionTypeProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionTypeProvider.cs new file mode 100644 index 0000000000..0f98087e21 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionTypeProvider.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Telemetry.Constants.Enums; + +namespace Volo.Abp.Telemetry; + +public interface ITelemetrySessionTypeProvider +{ + SessionType SessionType { get; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryApplicationRuntimeSessionTypeProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryApplicationRuntimeSessionTypeProvider.cs deleted file mode 100644 index be71cf00dd..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryApplicationRuntimeSessionTypeProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.IO; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Telemetry.Activity; -using Volo.Abp.Telemetry.Activity.Contracts; -using Volo.Abp.Telemetry.Constants; -using Volo.Abp.Telemetry.Constants.Enums; - -namespace Volo.Abp.Telemetry; - -public class TelemetryApplicationRuntimeSessionTypeProvider : ITelemetrySessionContextProvider , ISingletonDependency -{ - private readonly ITelemetrySolutionInfoProvider _telemetrySolutionInfoProvider; - - public TelemetryApplicationRuntimeSessionTypeProvider(ITelemetrySolutionInfoProvider telemetrySolutionInfoProvider) - { - _telemetrySolutionInfoProvider = telemetrySolutionInfoProvider; - } - - public virtual SessionType SessionType => SessionType.ApplicationRuntime; - public virtual void SetSolutionContext(ActivityData activity) - { - if (activity.ContainsKey(ActivityPropertyName.SolutionId)) - { - return; - } - - if (!activity.TryGetValue(ActivityPropertyName.SolutionPath, out var path) || !File.Exists((string)path)) - { - return; - } - - var solutionId = _telemetrySolutionInfoProvider.GetSolutionId((string)path); - - if (solutionId.HasValue) - { - activity[ActivityPropertyName.SolutionId] = solutionId.Value; - } - } -} \ 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 ad74d01adf..910ffcdae6 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -13,20 +14,20 @@ public class TelemetryService : ITelemetryService, IScopedDependency { private readonly ITelemetryActivityStorage _telemetryActivityStorage; private readonly ITelemetryDataSender _telemetryDataSender; - private readonly ITelemetryActivityDataProvider _telemetryActivityDataProvider; + private readonly ITelemetryActivityDataBuilder _telemetryActivityDataBuilder; public TelemetryService(ITelemetryActivityStorage telemetryActivityStorage, ITelemetryDataSender telemetryDataSender, - ITelemetryActivityDataProvider telemetryActivityDataProvider) + ITelemetryActivityDataBuilder telemetryActivityDataBuilder) { _telemetryActivityStorage = telemetryActivityStorage; _telemetryDataSender = telemetryDataSender; - _telemetryActivityDataProvider = telemetryActivityDataProvider; + _telemetryActivityDataBuilder = telemetryActivityDataBuilder; } 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); configure?.Invoke(activityData); @@ -52,46 +53,79 @@ public class TelemetryService : ITelemetryService, IScopedDependency }); } - private async Task CheckIfActivitySendTimeIsDueAsync() - { - var lastActivitySendTime = await _telemetryActivityStorage.GetLastActivitySendTimeAsync(); - - if (lastActivitySendTime is not null && lastActivitySendTime < DateTimeOffset.UtcNow.AddDays(-1)) - { - await _telemetryDataSender.SendAsync(); - } - } + - public async Task AddActivityAsync(ActivityData data) + public Task AddActivityAsync(ActivityData data) { - try + _ = Task.Run(async () => { - await _telemetryActivityDataProvider.AddExtraInformationAsync(data); - await _telemetryActivityStorage.BufferActivityAsync(data); - await CheckIfActivitySendTimeIsDueAsync(); + try + { + await _telemetryActivityDataBuilder.BuildAsync(data); + await _telemetryActivityStorage.BufferActivityAsync(data); + + if (data.ActivityName == ActivityNameConsts.AbpStudioClose) + { + await _telemetryActivityStorage.EndSessionAsync(); + } - if (data.ActivityName == ActivityNameConsts.AbpStudioClose) + await SendActivityIfDueAsync(); + } + catch { - await _telemetryActivityStorage.EndSessionAsync(); + // ignored } - } - catch - { - // ignored - } + }); + + return Task.CompletedTask; } - public async Task AddActivityAsync(string activityName, string? details = null) + public async Task AddActivityAsync(string activityName, string? detail = null) { - await AddActivityAsync(new ActivityData(activityName, details)); + await AddActivityAsync(new ActivityData(activityName, detail)); } public async Task AddActivityAsync(string activityName, Action configure) { - var activityData = new ActivityData(activityName) { Time = DateTimeOffset.UtcNow }; + var activityData = new ActivityData(activityName); configure?.Invoke(activityData); await AddActivityAsync(activityData); } + + public async Task AddErrorActivityAsync(Action> configure) + { + var activityData = new ActivityData(ActivityNameConsts.Error) + { + ActivityDetails = new Dictionary() + }; + + configure?.Invoke(activityData.ActivityDetails); + + await AddActivityAsync(activityData); + } + + public async Task AddErrorForActivityAsync(string failingActivity, string errorMessage) + { + var activityData = new ActivityData(ActivityNameConsts.Error) + { + ActivityDetails = new Dictionary + { + { "ErrorMessage", errorMessage }, + { "FailingActivity", failingActivity }, + } + }; + + await AddActivityAsync(activityData); + } + private async Task SendActivityIfDueAsync() + { + var lastActivitySendTime = await _telemetryActivityStorage.GetLastActivitySendTimeAsync(); + + if (lastActivitySendTime is not null && lastActivitySendTime < DateTimeOffset.UtcNow.AddDays(-1)) + { + await _telemetryDataSender.SendAsync(); + } + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetrySessionTypeProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetrySessionTypeProvider.cs new file mode 100644 index 0000000000..a730fcabb0 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetrySessionTypeProvider.cs @@ -0,0 +1,10 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Telemetry.Constants.Enums; + +namespace Volo.Abp.Telemetry; + +[ExposeServices(typeof(ITelemetrySessionTypeProvider))] +public class TelemetrySessionTypeProvider : ITelemetrySessionTypeProvider, ISingletonDependency +{ + public SessionType SessionType => SessionType.ApplicationRuntime; +} \ No newline at end of file