Browse Source

Refactor telemetry data handling for enhanced structure and function.

pull/23059/head
Emre KARA 1 year ago
parent
commit
37aa169954
  1. 47
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/ActivityData.cs
  2. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataBuilder.cs
  3. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataEnricher.cs
  4. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataProvider.cs
  5. 5
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs
  6. 2
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetrySolutionInfoProvider.cs
  7. 37
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataBuilder.cs
  8. 76
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataProvider.cs
  9. 35
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs
  10. 47
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs
  11. 279
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs
  12. 188
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoProvider.cs
  13. 63
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorage.cs
  14. 2
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs
  15. 3
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Constants/ActivityPropertyName.cs
  16. 3
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetryService.cs
  17. 10
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionContextProvider.cs
  18. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionTypeProvider.cs
  19. 39
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryApplicationRuntimeSessionTypeProvider.cs
  20. 90
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryService.cs
  21. 10
      framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetrySessionTypeProvider.cs

47
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/ActivityData.cs

@ -7,10 +7,9 @@ public class ActivityData : Dictionary<string, object>
{
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<string, object>
}
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<string, object>? ActivityDetails {
get => (Dictionary<string, object>?)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<string, object>
}
public DateTimeOffset Time = DateTimeOffset.UtcNow;
}

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

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

8
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Contracts/ITelemetryActivityDataProvider.cs

@ -1,8 +0,0 @@
using System.Threading.Tasks;
namespace Volo.Abp.Telemetry.Activity.Contracts;
public interface ITelemetryActivityDataProvider
{
Task AddExtraInformationAsync(ActivityData activity);
}

5
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<DateTimeOffset?> GetLastActivitySendTimeAsync();
Task<(bool isFirstSession, Guid sessionId)> GetOrCreateSessionInfoAsync();
Task<Guid> GetOrCreateSessionInfoAsync();
Task MarkActivitiesAsSentAsync();
Task MarkDeviceInfoAsSentAsync();
Task MarkDeviceInfoAsAddedAsync();
Task MarkSolutionInfoAsAddedAsync(Guid solutionId);
Task<DateTimeOffset?> GetLastSolutionInfoSendTimeAsync(Guid solutionId);
Task<DateTimeOffset?> GetLastDeviceInfoSendTimeAsync();
Task BufferActivityAsync(ActivityData activityData);

2
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<IDictionary<string, object>> GetSolutionInfoAsync(string solutionPath);
Task FillSolutionInfoAsync(ActivityData activityData, string solutionPath);
Guid? GetSolutionId(string solutionPath);
}

37
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<ITelemetrySessionTypeProvider>();
var telemetryActivityStorage = _serviceProvider.GetRequiredService<ITelemetryActivityStorage>();
activity[ActivityPropertyName.SessionType] = telemetrySessionTypeProvider.SessionType;
activity[ActivityPropertyName.SessionId] = await telemetryActivityStorage.GetOrCreateSessionInfoAsync();
activity[ActivityPropertyName.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage);
var activityDataEnrichers = _serviceProvider.GetRequiredService<IEnumerable<ITelemetryActivityDataEnricher>>();
foreach (var activityDataEnricher in activityDataEnrichers)
{
await activityDataEnricher.EnrichAsync(activity);
}
}
}

76
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetryActivityDataProvider.cs

@ -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<ITelemetryActivityStorage>();
var sessionContext = _serviceProvider.GetRequiredService<ITelemetrySessionContextProvider>();
var deviceInfoProvider = _serviceProvider.GetRequiredService<IDeviceInfoProvider>();
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<ISoftwareInfoProvider>();
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<IEnumerable<ITelemetryApplicationInfoContributor>>();
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<ITelemetrySolutionInfoProvider>();
var solutionInfo = await solutionProvider.GetSolutionInfoAsync(activity[ActivityPropertyName.SolutionPath].ToString()!);
foreach (var entry in solutionInfo)
{
activity[entry.Key] = entry.Value;
}
}
}
}
}

35
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<ITelemetryApplicationInfoContributor> _telemetryApplicationInfoContributors;
public TelemetryApplicationInfoEnricher(ITelemetrySessionTypeProvider telemetrySessionTypeProvider,
IEnumerable<ITelemetryApplicationInfoContributor> 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);
}
}
}

47
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/EnvironmentInspection/Providers/DeviceInfoProvider.cs → 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<Guid> _deviceId;
public DeviceInfoProvider()
public TelemetryDeviceInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, ISoftwareInfoProvider softwareInfoProvider)
{
_telemetryActivityStorage = telemetryActivityStorage;
_softwareInfoProvider = softwareInfoProvider;
_deviceId = new Lazy<Guid>(() =>
{
var deviceIdText = File.ReadAllText(TelemetryPaths.ComputerId);
return deviceIdText.To<Guid>();
return deviceIdText.To<Guid>();
});
}
public Guid GetDeviceId()
public async Task<bool> 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;
}
}

279
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<string, object> activity, JsonElement config)
{
var map = new (string Key, Action<JsonElement> 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<Dictionary<string, object>> ExtractModules(string directoryPath, JsonElement modulesElement)
{
var modules = new List<Dictionary<string, object>>();
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<string, object> { { 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<int> 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;
}
}
}

188
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/Activity/Providers/TelemetrySolutionInfoProvider.cs

@ -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<IDictionary<string, object>> GetSolutionInfoAsync(string solutionPath)
{
try
{
var result = new Dictionary<string, object>();
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<string, object>();
}
}
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<string, object> dict, JsonElement config)
{
var map = new (string Key, Action<JsonElement> 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<Dictionary<string, object>> ExtractModules(string directoryPath, JsonElement modulesElement)
{
var modules = new List<Dictionary<string, object>>();
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<string, object> { { 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<int> 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;
}
}
}

63
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<DateTimeOffset?> GetApplicationInfoLastActivitySendTimeAsync(Guid applicationId)
{
var state = await GetStateAsync();
if (state.ApplicationInfos.TryGetValue(applicationId, out var lastActivitySendTime))
{
return lastActivitySendTime;
}
return null;
}
public async Task<DateTimeOffset?> 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<Guid> 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<DateTimeOffset?> GetLastSolutionInfoSendTimeAsync(Guid id)
{
var state = await GetStateAsync();
@ -96,20 +84,13 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep
public async Task<DateTimeOffset?> 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<TelemetryActivityStorageState?>(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}) ?? new TelemetryActivityStorageState();
return JsonSerializer.Deserialize<TelemetryActivityStorageState?>(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<bool> ShouldAddDeviceInfoAsync()
{
var lastSend = await GetLastDeviceInfoSendTimeAsync();

2
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<ActivityData> Activities { get; set; } = new();
public Dictionary<Guid,DateTimeOffset> Solutions { get; set; } = new();

3
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);

3
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<ActivityData> configure);
Task AddErrorActivityAsync(Action<Dictionary<string, object>> configure);
Task AddErrorForActivityAsync(string failingActivity, string errorMessage);
}

10
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/ITelemetrySessionContextProvider.cs

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

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

39
framework/src/Volo.Abp.Core/Volo/Abp/Telemetry/TelemetryApplicationRuntimeSessionTypeProvider.cs

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

90
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<ActivityData>? 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<ActivityData> 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<Dictionary<string,object>> configure)
{
var activityData = new ActivityData(ActivityNameConsts.Error)
{
ActivityDetails = new Dictionary<string, object>()
};
configure?.Invoke(activityData.ActivityDetails);
await AddActivityAsync(activityData);
}
public async Task AddErrorForActivityAsync(string failingActivity, string errorMessage)
{
var activityData = new ActivityData(ActivityNameConsts.Error)
{
ActivityDetails = new Dictionary<string, object>
{
{ "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();
}
}
}

10
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;
}
Loading…
Cancel
Save