diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5d9885ddbd..9e0440965a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -184,5 +184,6 @@
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/TelemetryApplicationMetricsEnricher.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/TelemetryApplicationMetricsEnricher.cs
new file mode 100644
index 0000000000..fa95f5c298
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/TelemetryApplicationMetricsEnricher.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Services;
+using Volo.Abp.DependencyInjection;
+using System.Linq;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.Internal.Telemetry.Activity;
+using Volo.Abp.Internal.Telemetry.Activity.Contracts;
+using Volo.Abp.Internal.Telemetry.Activity.Providers;
+using Volo.Abp.Internal.Telemetry.Constants;
+using Volo.Abp.Internal.Telemetry.Constants.Enums;
+using Volo.Abp.Reflection;
+
+namespace Volo.Abp.AspNetCore.Mvc;
+
+[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))]
+public sealed class TelemetryApplicationMetricsEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher
+{
+ private readonly ITypeFinder _typeFinder;
+ public TelemetryApplicationMetricsEnricher(ITypeFinder typeFinder, IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ _typeFinder = typeFinder;
+ }
+
+ protected override Task CanExecuteAsync(ActivityContext context)
+ {
+ return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime);
+ }
+
+ protected override Task ExecuteAsync(ActivityContext context)
+ {
+ var appServiceCount = _typeFinder.Types.Count(t =>
+ typeof(IApplicationService).IsAssignableFrom(t) &&
+ t is { IsAbstract: false, IsInterface: false } &&
+ !t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter));
+
+ var controllerCount = _typeFinder.Types.Count(t =>
+ typeof(ControllerBase).IsAssignableFrom(t) &&
+ !t.IsAbstract &&
+ !t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter));
+
+
+ context.Current[ActivityPropertyNames.AppServiceCount] = appServiceCount;
+ context.Current[ActivityPropertyNames.ControllerCount] = controllerCount;
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Properties/AssemblyInfo.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..f9e35084b9
--- /dev/null
+++ b/framework/src/Volo.Abp.Authorization.Abstractions/Properties/AssemblyInfo.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Volo.Abp.Authorization.Abstractions")]
+[assembly: AssemblyTrademark("")]
+[assembly: InternalsVisibleTo("Volo.Abp.Authorization")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("83632bec-2f60-4c2b-b964-30e8b37fa9d8")]
diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs
index ab49e4aad4..0f5d713e2b 100644
--- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs
+++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs
@@ -108,6 +108,8 @@ public class PermissionDefinition :
Parent = this
};
+ child[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName] = this[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName];
+
_children.Add(child);
return child;
diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs
index 85d771a6ab..394cdb9d82 100644
--- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs
+++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs
@@ -11,6 +11,13 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext
public Dictionary Groups { get; }
+ internal IPermissionDefinitionProvider? CurrentProvider { get; set; }
+
+ public static class KnownPropertyNames
+ {
+ public const string CurrentProviderName = "_CurrentProviderName";
+ }
+
public PermissionDefinitionContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
@@ -28,7 +35,16 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext
throw new AbpException($"There is already an existing permission group with name: {name}");
}
- return Groups[name] = new PermissionGroupDefinition(name, displayName);
+ var group = new PermissionGroupDefinition(name, displayName);
+
+ if (CurrentProvider != null)
+ {
+ group[KnownPropertyNames.CurrentProviderName] = CurrentProvider.GetType().FullName;
+ }
+
+ Groups[name] = group;
+
+ return group;
}
[NotNull]
diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs
index bbc6e96cdf..0ad2dca099 100644
--- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs
+++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs
@@ -61,6 +61,8 @@ public class PermissionGroupDefinition : ICanAddChildPermission
isEnabled
);
+ permission[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName] = this[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName];
+
_permissions.Add(permission);
return permission;
diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs
index 2d0a9d668c..4e6ff0d11c 100644
--- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs
+++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs
@@ -84,18 +84,23 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore,
foreach (var provider in providers)
{
+ context.CurrentProvider = provider;
provider.PreDefine(context);
}
foreach (var provider in providers)
{
+ context.CurrentProvider = provider;
provider.Define(context);
}
foreach (var provider in providers)
{
+ context.CurrentProvider = provider;
provider.PostDefine(context);
}
+
+ context.CurrentProvider = null;
return context.Groups;
}
diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/TelemetryPermissionInfoEnricher.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/TelemetryPermissionInfoEnricher.cs
new file mode 100644
index 0000000000..4fab06ba8f
--- /dev/null
+++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/TelemetryPermissionInfoEnricher.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Internal.Telemetry.Activity;
+using Volo.Abp.Internal.Telemetry.Activity.Contracts;
+using Volo.Abp.Internal.Telemetry.Activity.Providers;
+using Volo.Abp.Internal.Telemetry.Constants;
+
+namespace Volo.Abp.Authorization.Permissions;
+
+[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))]
+public sealed class TelemetryPermissionInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher
+{
+ private readonly IPermissionDefinitionManager _permissionDefinitionManager;
+
+ public TelemetryPermissionInfoEnricher(IPermissionDefinitionManager permissionDefinitionManager,
+ IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ _permissionDefinitionManager = permissionDefinitionManager;
+ }
+
+ protected override Task CanExecuteAsync(ActivityContext context)
+ {
+ return Task.FromResult(context.ProjectId.HasValue);
+ }
+
+ protected async override Task ExecuteAsync(ActivityContext context)
+ {
+ var permissions = await _permissionDefinitionManager.GetPermissionsAsync();
+
+ var userDefinedPermissionsCount = permissions.Count(IsUserDefinedPermission);
+
+ context.Current[ActivityPropertyNames.PermissionCount] = userDefinedPermissionsCount;
+ }
+
+ private static bool IsUserDefinedPermission(PermissionDefinition permission)
+ {
+ return permission.Properties.TryGetValue(PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName, out var providerName) &&
+ providerName is string &&
+ !providerName.ToString()!.StartsWith(TelemetryConsts.VoloNameSpaceFilter);
+ }
+
+
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
index caa6b6be94..5afcca9fbe 100644
--- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
+++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
@@ -22,6 +22,7 @@
+
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
index ec20467ac9..6811e2a9a1 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
@@ -4,13 +4,17 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Internal;
+using Volo.Abp.Internal.Telemetry;
+using Volo.Abp.Internal.Telemetry.Constants;
using Volo.Abp.Logging;
using Volo.Abp.Modularity;
+using Volo.Abp.Threading;
namespace Volo.Abp;
@@ -149,6 +153,56 @@ public abstract class AbpApplicationBase : IAbpApplication
options.PlugInSources
);
}
+ protected void SetupTelemetryTracking()
+ {
+ if (!ShouldSendTelemetryData())
+ {
+ return;
+ }
+
+ AsyncHelper.RunSync(InitializeTelemetryTracking);
+ }
+
+ protected async Task SetupTelemetryTrackingAsync()
+ {
+ if (!ShouldSendTelemetryData())
+ {
+ return;
+ }
+
+ await InitializeTelemetryTracking();
+ }
+
+ private async Task InitializeTelemetryTracking()
+ {
+ try
+ {
+ using var scope = ServiceProvider.CreateScope();
+ var telemetryService = scope.ServiceProvider.GetRequiredService();
+ await telemetryService.AddActivityAsync(ActivityNameConsts.ApplicationRun);
+ }
+ catch (Exception ex)
+ {
+ try
+ {
+ using var scope = ServiceProvider.CreateScope();
+ var logger = scope.ServiceProvider.GetRequiredService>();
+ logger.LogException(ex, LogLevel.Trace);
+ }
+ catch
+ {
+ /* ignored */
+ }
+ }
+ }
+
+ private bool ShouldSendTelemetryData()
+ {
+ using var scope = ServiceProvider.CreateScope();
+ var abpHostEnvironment = scope.ServiceProvider.GetRequiredService();
+ var configuration = scope.ServiceProvider.GetRequiredService();
+ return abpHostEnvironment.IsDevelopment() && configuration.GetValue("Abp:Telemetry:IsEnabled") == true;
+ }
//TODO: We can extract a new class for this
public virtual async Task ConfigureServicesAsync()
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs
index d80e46c5d7..faf4f7fa1a 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs
@@ -44,6 +44,8 @@ internal class AbpApplicationWithExternalServiceProvider : AbpApplicationBase, I
SetServiceProvider(serviceProvider);
await InitializeModulesAsync();
+
+ await SetupTelemetryTrackingAsync();
}
public void Initialize([NotNull] IServiceProvider serviceProvider)
@@ -53,6 +55,8 @@ internal class AbpApplicationWithExternalServiceProvider : AbpApplicationBase, I
SetServiceProvider(serviceProvider);
InitializeModules();
+
+ SetupTelemetryTracking();
}
public override void Dispose()
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs
index c5e5eccc83..b652999a7a 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs
@@ -50,12 +50,15 @@ internal class AbpApplicationWithInternalServiceProvider : AbpApplicationBase, I
{
CreateServiceProvider();
await InitializeModulesAsync();
+ await SetupTelemetryTrackingAsync();
+
}
public void Initialize()
{
CreateServiceProvider();
InitializeModules();
+ SetupTelemetryTracking();
}
public override void Dispose()
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs
new file mode 100644
index 0000000000..8a03a66e65
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using Volo.Abp.Internal.Telemetry.Constants;
+using Volo.Abp.Internal.Telemetry.Constants.Enums;
+
+namespace Volo.Abp.Internal.Telemetry.Activity;
+
+public class ActivityContext
+{
+ public ActivityEvent Current { get; }
+ public Dictionary ExtraProperties { get; } = new();
+ public bool IsTerminated { get; private set; }
+
+ public Guid? ProjectId => Current.Get(ActivityPropertyNames.ProjectId);
+
+ public Guid? SolutionId => Current.Get(ActivityPropertyNames.SolutionId);
+
+ public SessionType? SessionType => Current.Get(ActivityPropertyNames.SessionType);
+
+ public string? DeviceId => Current.Get(ActivityPropertyNames.DeviceId);
+
+ public string? SolutionPath => ExtraProperties.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath)
+ ? solutionPath?.ToString()
+ : null;
+
+ private ActivityContext(ActivityEvent current)
+ {
+ Current = current;
+ }
+
+ public static ActivityContext Create(string activityName, string? details = null,
+ Action>? additionalProperties = null)
+ {
+ var activity = new ActivityEvent(activityName, details);
+
+ if (additionalProperties is not null)
+ {
+ var additionalPropertiesDict = new Dictionary();
+ activity[ActivityPropertyNames.AdditionalProperties] = additionalPropertiesDict;
+ additionalProperties.Invoke(additionalPropertiesDict);
+ }
+
+ return new ActivityContext(activity);
+ }
+
+ public void Terminate()
+ {
+ IsTerminated = true;
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityEvent.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityEvent.cs
new file mode 100644
index 0000000000..22ad4c0da3
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityEvent.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using Volo.Abp.Internal.Telemetry.Constants;
+
+namespace Volo.Abp.Internal.Telemetry.Activity;
+
+public class ActivityEvent : Dictionary
+{
+ public ActivityEvent()
+ {
+ this[ActivityPropertyNames.Id] = Guid.NewGuid();
+ this[ActivityPropertyNames.Time] = DateTimeOffset.UtcNow;
+ }
+
+ public ActivityEvent(string activityName, string? details = null) : this()
+ {
+ Check.NotNullOrWhiteSpace(activityName, nameof(activityName));
+
+ this[ActivityPropertyNames.ActivityName] = activityName;
+ this[ActivityPropertyNames.ActivityDetails] = details;
+ }
+
+ public bool HasSolutionInfo()
+ {
+ return this.ContainsKey(ActivityPropertyNames.HasSolutionInfo);
+ }
+
+ public bool HasDeviceInfo()
+ {
+ return this.ContainsKey(ActivityPropertyNames.HasDeviceInfo);
+ }
+
+ public bool HasProjectInfo()
+ {
+ return this.ContainsKey(ActivityPropertyNames.HasProjectInfo);
+ }
+
+ public T Get(string key)
+ {
+ return TryConvert(key, out var value) ? value : default!;
+ }
+
+ public bool TryGetValue(string key, out T value)
+ {
+ return TryConvert(key, out value);
+ }
+
+ private bool TryConvert(string key, out T result)
+ {
+ result = default!;
+ if (!this.TryGetValue(key, out var value) || value is null)
+ {
+ return false;
+ }
+
+ try
+ {
+ if (value is T tValue)
+ {
+ result = tValue;
+ return true;
+ }
+
+ if (value is JsonElement jsonElement)
+ {
+ value = ExtractFromJsonElement(jsonElement);
+ if (value is null)
+ {
+ return false;
+ }
+ }
+
+ var targetType = typeof(T);
+ var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
+ if (underlyingType.IsEnum)
+ {
+ if (value is string str)
+ {
+ result = (T)Enum.Parse(underlyingType, str, ignoreCase: true);
+ }
+ else if (value is int intValue)
+ {
+ result = (T)Enum.ToObject(underlyingType, intValue);
+ }
+
+ return true;
+ }
+
+
+ if (underlyingType == typeof(Dictionary[]))
+ {
+ result = (T)value;
+ return true;
+ }
+
+ if (underlyingType == typeof(Guid))
+ {
+ result = (T)(object)Guid.Parse(value.ToString()!);
+ return true;
+ }
+
+ if (underlyingType == typeof(DateTimeOffset))
+ {
+ result = (T)(object)DateTimeOffset.Parse(value.ToString()!);
+ return true;
+ }
+
+ // Nullable types
+ result = (T)Convert.ChangeType(value, underlyingType);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static object? ExtractFromJsonElement(JsonElement element)
+ {
+ return element.ValueKind switch
+ {
+ JsonValueKind.String => element.GetString(),
+ JsonValueKind.Number => element.GetInt32(),
+ JsonValueKind.True => true,
+ JsonValueKind.False => false,
+ JsonValueKind.Null => null,
+ JsonValueKind.Array => element.EnumerateArray()
+ .Select(item =>
+ {
+ if (item.ValueKind == JsonValueKind.Object)
+ {
+ return item.EnumerateObject()
+ .ToDictionary(prop => prop.Name, prop => ExtractFromJsonElement(prop.Value));
+ }
+
+ return new Dictionary { { "value", ExtractFromJsonElement(item) } };
+ })
+ .ToArray(),
+
+ JsonValueKind.Object => element.EnumerateObject()
+ .ToDictionary(prop => prop.Name, prop => ExtractFromJsonElement(prop.Value)),
+ _ => element.ToString()
+ };
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/IHasParentTelemetryActivityEventEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/IHasParentTelemetryActivityEventEnricher.cs
new file mode 100644
index 0000000000..5547445d54
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/IHasParentTelemetryActivityEventEnricher.cs
@@ -0,0 +1,7 @@
+using Volo.Abp.Internal.Telemetry.Activity.Providers;
+
+namespace Volo.Abp.Internal.Telemetry.Activity.Contracts;
+
+public interface IHasParentTelemetryActivityEventEnricher where TParent: TelemetryActivityEventEnricher
+{
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventBuilder.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventBuilder.cs
new file mode 100644
index 0000000000..0542b9d676
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventBuilder.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Internal.Telemetry.Activity.Contracts;
+
+public interface ITelemetryActivityEventBuilder
+{
+ Task BuildAsync(ActivityContext context);
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventEnricher.cs
new file mode 100644
index 0000000000..b83741cd88
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventEnricher.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Internal.Telemetry.Activity.Contracts;
+
+public interface ITelemetryActivityEventEnricher
+{
+ int ExecutionOrder { get; }
+
+ Task EnrichAsync(ActivityContext context);
+
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs
new file mode 100644
index 0000000000..870876ffbd
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.Internal.Telemetry.Activity.Contracts;
+
+public interface ITelemetryActivityStorage
+{
+ Guid InitializeOrGetSession();
+ void DeleteActivities(ActivityEvent[] activities);
+ void SaveActivity(ActivityEvent activityEvent);
+ List GetActivities();
+ bool ShouldAddDeviceInfo();
+ bool ShouldAddSolutionInformation(Guid solutionId);
+ bool ShouldAddProjectInfo(Guid projectId);
+ bool ShouldSendActivities();
+ void MarkActivitiesAsFailed(ActivityEvent[] activities);
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventBuilder.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventBuilder.cs
new file mode 100644
index 0000000000..201ac8c3b6
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventBuilder.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.DynamicProxy;
+using Volo.Abp.Internal.Telemetry.Activity.Contracts;
+using Volo.Abp.Internal.Telemetry.Constants;
+
+namespace Volo.Abp.Internal.Telemetry.Activity.Providers;
+
+public class TelemetryActivityEventBuilder : ITelemetryActivityEventBuilder, ISingletonDependency
+{
+ private readonly List _activityEnrichers;
+
+ public TelemetryActivityEventBuilder(IEnumerable activityDataEnrichers)
+ {
+ _activityEnrichers = activityDataEnrichers
+ .Where(FilterEnricher)
+ .OrderByDescending(x => x.ExecutionOrder)
+ .ToList();
+ }
+ public virtual async Task BuildAsync(ActivityContext context)
+ {
+ foreach (var enricher in _activityEnrichers)
+ {
+ try
+ {
+ await enricher.EnrichAsync(context);
+ }
+ catch
+ {
+ //ignored
+ }
+
+ if (context.IsTerminated)
+ {
+ return null;
+ }
+ }
+
+ return context.Current;
+ }
+
+ private static bool FilterEnricher(ITelemetryActivityEventEnricher enricher)
+ {
+ return ProxyHelper.GetUnProxiedType(enricher).Assembly.FullName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter) &&
+ enricher is not IHasParentTelemetryActivityEventEnricher;
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventEnricher.cs
new file mode 100644
index 0000000000..d25e9c58f8
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventEnricher.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.DynamicProxy;
+using Volo.Abp.Internal.Telemetry.Activity.Contracts;
+
+namespace Volo.Abp.Internal.Telemetry.Activity.Providers;
+
+public abstract class TelemetryActivityEventEnricher : ITelemetryActivityEventEnricher, IScopedDependency
+{
+ public virtual int ExecutionOrder { get; set; } = 0;
+ protected bool IgnoreChildren { get; set; }
+ protected virtual Type? ReplaceParentType { get; set; }
+
+ private readonly IServiceProvider _serviceProvider;
+
+ protected TelemetryActivityEventEnricher(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public async Task EnrichAsync(ActivityContext context)
+ {
+ if (!await CanExecuteAsync(context))
+ {
+ return;
+ }
+
+ await ExecuteAsync(context);
+ await ExecuteChildrenAsync(context);
+ }
+
+ protected virtual Task CanExecuteAsync(ActivityContext context)
+ {
+ return Task.FromResult(true);
+ }
+
+ protected abstract Task ExecuteAsync(ActivityContext context);
+
+ protected virtual async Task ExecuteChildrenAsync(ActivityContext context)
+ {
+ if (IgnoreChildren)
+ {
+ return;
+ }
+
+ using var scope = _serviceProvider.CreateScope();
+
+ foreach (var child in GetChildren(scope.ServiceProvider))
+ {
+ await child.EnrichAsync(context);
+ }
+ }
+
+ private ITelemetryActivityEventEnricher[] GetChildren(IServiceProvider serviceProvider)
+ {
+ var targetType = ReplaceParentType ?? ProxyHelper.GetUnProxiedType(this);
+ var genericInterfaceType = typeof(IHasParentTelemetryActivityEventEnricher<>).MakeGenericType(targetType);
+ var enumerableType = typeof(IEnumerable<>).MakeGenericType(genericInterfaceType);
+
+ var childServices = (IEnumerable