From 3903c40c725d5bb08863186354a4c04f24012d82 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 9 Jun 2025 20:12:02 +0200 Subject: [PATCH 01/17] work in progress --- .../src/ComponentsActivitySource.cs | 103 ++++++---------- .../Microsoft.AspNetCore.Components.csproj | 1 - .../Components/src/RenderTree/Renderer.cs | 27 +++-- .../Components/src/Routing/Router.cs | 23 ++-- .../test/ComponentsActivitySourceTest.cs | 113 +++--------------- .../src/Circuits/CircuitActivitySource.cs | 73 +++++++++++ .../Server/src/Circuits/CircuitFactory.cs | 9 +- .../Server/src/Circuits/CircuitHost.cs | 21 ++-- .../Server/src/Circuits/RemoteRenderer.cs | 8 +- src/Components/Server/src/ComponentHub.cs | 6 +- .../ComponentServiceCollectionExtensions.cs | 2 + .../Circuits/CircuitActivitySourceTest.cs | 93 ++++++++++++++ .../Server/test/Circuits/TestCircuitHost.cs | 8 +- 13 files changed, 279 insertions(+), 208 deletions(-) create mode 100644 src/Components/Server/src/Circuits/CircuitActivitySource.cs create mode 100644 src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 7079bf7743a4..696088669a93 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -5,76 +5,31 @@ namespace Microsoft.AspNetCore.Components; +internal struct ComponentsActivityWrapper +{ + public Activity? Previous; + public Activity? Activity; +} + /// /// This is instance scoped per renderer /// internal class ComponentsActivitySource { internal const string Name = "Microsoft.AspNetCore.Components"; - internal const string OnCircuitName = $"{Name}.CircuitStart"; internal const string OnRouteName = $"{Name}.RouteChange"; internal const string OnEventName = $"{Name}.HandleEvent"; - private ActivityContext _httpContext; - private ActivityContext _circuitContext; - private string? _circuitId; + internal ActivityContext _httpContext; + internal ActivityContext _circuitContext; + internal string? _circuitId; + private ActivityContext _routeContext; private ActivitySource ActivitySource { get; } = new ActivitySource(Name); - public static ActivityContext CaptureHttpContext() - { - var parentActivity = Activity.Current; - if (parentActivity is not null && parentActivity.OperationName == "Microsoft.AspNetCore.Hosting.HttpRequestIn" && parentActivity.Recorded) - { - return parentActivity.Context; - } - return default; - } - - public Activity? StartCircuitActivity(string circuitId, ActivityContext httpContext) - { - _circuitId = circuitId; - - var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId: null, null, null); - if (activity is not null) - { - if (activity.IsAllDataRequested) - { - if (_circuitId != null) - { - activity.SetTag("aspnetcore.components.circuit.id", _circuitId); - } - if (httpContext != default) - { - activity.AddLink(new ActivityLink(httpContext)); - } - } - activity.DisplayName = $"Circuit {circuitId ?? ""}"; - activity.Start(); - _circuitContext = activity.Context; - } - return activity; - } - - public void FailCircuitActivity(Activity? activity, Exception ex) - { - _circuitContext = default; - if (activity != null && !activity.IsStopped) - { - activity.SetTag("error.type", ex.GetType().FullName); - activity.SetStatus(ActivityStatusCode.Error); - activity.Stop(); - } - } - - public Activity? StartRouteActivity(string componentType, string route) + public ComponentsActivityWrapper StartRouteActivity(string componentType, string route) { - if (_httpContext == default) - { - _httpContext = CaptureHttpContext(); - } - var activity = ActivitySource.CreateActivity(OnRouteName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) { @@ -103,13 +58,16 @@ public void FailCircuitActivity(Activity? activity, Exception ex) } activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; + var previousActivity = Activity.Current; + Activity.Current = null; // do not inherit the parent activity activity.Start(); _routeContext = activity.Context; + return new ComponentsActivityWrapper { Activity = activity, Previous = previousActivity }; } - return activity; + return default; } - public Activity? StartEventActivity(string? componentType, string? methodName, string? attributeName) + public ComponentsActivityWrapper StartEventActivity(string? componentType, string? methodName, string? attributeName) { var activity = ActivitySource.CreateActivity(OnEventName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) @@ -147,31 +105,42 @@ public void FailCircuitActivity(Activity? activity, Exception ex) } activity.DisplayName = $"Event {attributeName ?? "[unknown attribute]"} -> {componentType ?? "[unknown component]"}.{methodName ?? "[unknown method]"}"; + var previousActivity = Activity.Current; + Activity.Current = null; // do not inherit the parent activity activity.Start(); + return new ComponentsActivityWrapper { Activity = activity, Previous = previousActivity }; } - return activity; + return default; } - public static void FailEventActivity(Activity? activity, Exception ex) + public static void StopComponentActivity(ComponentsActivityWrapper wrapper, Exception? ex) { - if (activity != null && !activity.IsStopped) + if (wrapper.Activity != null && !wrapper.Activity.IsStopped) { - activity.SetTag("error.type", ex.GetType().FullName); - activity.SetStatus(ActivityStatusCode.Error); - activity.Stop(); + if (ex != null) + { + wrapper.Activity.SetTag("error.type", ex.GetType().FullName); + wrapper.Activity.SetStatus(ActivityStatusCode.Error); + } + wrapper.Activity.Stop(); + + if (Activity.Current == null && wrapper.Previous != null && !wrapper.Previous.IsStopped) + { + Activity.Current = wrapper.Previous; + } } } - public static async Task CaptureEventStopAsync(Task task, Activity? activity) + public static async Task CaptureEventStopAsync(Task task, ComponentsActivityWrapper wrapper) { try { await task; - activity?.Stop(); + StopComponentActivity(wrapper, null); } catch (Exception ex) { - FailEventActivity(activity, ex); + StopComponentActivity(wrapper, ex); } } } diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index 07fcc360fd7e..ca3286f8b6c2 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -79,7 +79,6 @@ - diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index ce14869a5d45..48e84e3d8141 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -96,7 +96,6 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, _componentFactory = new ComponentFactory(componentActivator, this); _componentsMetrics = serviceProvider.GetService(); _componentsActivitySource = serviceProvider.GetService(); - ServiceProviderCascadingValueSuppliers = serviceProvider.GetService() is null ? Array.Empty() : serviceProvider.GetServices().ToArray(); @@ -115,6 +114,16 @@ private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvid ?? new DefaultComponentActivator(serviceProvider); } + internal void SetCircuitActivityContext(ActivityContext httpContext, ActivityContext circuitContext, string circuitId) + { + if (ComponentActivitySource != null) + { + ComponentActivitySource._httpContext = httpContext; + ComponentActivitySource._circuitContext = circuitContext; + ComponentActivitySource._circuitId = circuitId; + } + } + /// /// Gets the associated with this . /// @@ -448,14 +457,14 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie var (renderedByComponentId, callback, attributeName) = GetRequiredEventBindingEntry(eventHandlerId); // collect trace - Activity? activity = null; + ComponentsActivityWrapper wrapper = default; string receiverName = null; string methodName = null; if (ComponentActivitySource != null) { receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; methodName ??= callback.Delegate.Method?.Name; - activity = ComponentActivitySource.StartEventActivity(receiverName, methodName, attributeName); + wrapper = ComponentActivitySource.StartEventActivity(receiverName, methodName, attributeName); } var eventStartTimestamp = ComponentMetrics != null && ComponentMetrics.IsEventEnabled ? Stopwatch.GetTimestamp() : 0; @@ -506,13 +515,13 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie { receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; methodName ??= callback.Delegate.Method?.Name; - _ = ComponentMetrics.CaptureEventDuration(task, eventStartTimestamp, receiverName, methodName, attributeName); + _ = ComponentMetrics.CaptureEventDuration(task, eventStartTimestamp, null, null, attributeName); } // stop activity/trace - if (ComponentActivitySource != null && activity != null) + if (ComponentActivitySource != null && wrapper.Activity != null) { - _ = ComponentsActivitySource.CaptureEventStopAsync(task, activity); + _ = ComponentsActivitySource.CaptureEventStopAsync(task, wrapper); } } catch (Exception e) @@ -521,12 +530,12 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie { receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; methodName ??= callback.Delegate.Method?.Name; - ComponentMetrics.FailEventSync(e, eventStartTimestamp, receiverName, methodName, attributeName); + ComponentMetrics.FailEventSync(e, eventStartTimestamp, null, null, attributeName); } - if (ComponentActivitySource != null && activity != null) + if (ComponentActivitySource != null && wrapper.Activity != null) { - ComponentsActivitySource.FailEventActivity(activity, e); + ComponentsActivitySource.StopComponentActivity(wrapper, e); } HandleExceptionViaErrorBoundary(e, receiverComponentState); diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index 51fb3fc823d2..f2eb174552cf 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -223,12 +223,12 @@ internal virtual void Refresh(bool isNavigationIntercepted) var relativePath = NavigationManager.ToBaseRelativePath(_locationAbsolute.AsSpan()); var locationPathSpan = TrimQueryOrHash(relativePath); var locationPath = $"/{locationPathSpan}"; - Activity? activity; + ComponentsActivityWrapper activityWrapper; // In order to avoid routing twice we check for RouteData if (RoutingStateProvider?.RouteData is { } endpointRouteData) { - activity = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); + activityWrapper = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); // Other routers shouldn't provide RouteData, this is specific to our router component // and must abide by our syntax and behaviors. @@ -241,7 +241,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) endpointRouteData = RouteTable.ProcessParameters(endpointRouteData); _renderHandle.Render(Found(endpointRouteData)); - activity?.Stop(); + ComponentsActivitySource.StopComponentActivity(activityWrapper, null); return; } @@ -258,7 +258,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) $"does not implement {typeof(IComponent).FullName}."); } - activity = RecordDiagnostics(context.Handler.FullName, context.Entry.RoutePattern.RawText); + activityWrapper = RecordDiagnostics(context.Handler.FullName, context.Entry.RoutePattern.RawText); Log.NavigatingToComponent(_logger, context.Handler, locationPath, _baseUri); @@ -279,7 +279,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) { if (!isNavigationIntercepted) { - activity = RecordDiagnostics("NotFound", "NotFound"); + activityWrapper = RecordDiagnostics("NotFound", "NotFound"); Log.DisplayingNotFound(_logger, locationPath, _baseUri); @@ -290,22 +290,21 @@ internal virtual void Refresh(bool isNavigationIntercepted) } else { - activity = RecordDiagnostics("External", "External"); + activityWrapper = RecordDiagnostics("External", "External"); Log.NavigatingToExternalUri(_logger, _locationAbsolute, locationPath, _baseUri); NavigationManager.NavigateTo(_locationAbsolute, forceLoad: true); } } - activity?.Stop(); - + ComponentsActivitySource.StopComponentActivity(activityWrapper, null); } - private Activity? RecordDiagnostics(string componentType, string template) + private ComponentsActivityWrapper RecordDiagnostics(string componentType, string template) { - Activity? activity = null; + ComponentsActivityWrapper activityWrapper = default; if (_renderHandle.ComponentActivitySource != null) { - activity = _renderHandle.ComponentActivitySource.StartRouteActivity(componentType, template); + activityWrapper = _renderHandle.ComponentActivitySource.StartRouteActivity(componentType, template); } if (_renderHandle.ComponentMetrics != null && _renderHandle.ComponentMetrics.IsNavigationEnabled) @@ -313,7 +312,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) _renderHandle.ComponentMetrics.Navigation(componentType, template); } - return activity; + return activityWrapper; } private static void DefaultNotFoundContent(RenderTreeBuilder builder) diff --git a/src/Components/Components/test/ComponentsActivitySourceTest.cs b/src/Components/Components/test/ComponentsActivitySourceTest.cs index 660bf9428264..cc061df8bc48 100644 --- a/src/Components/Components/test/ComponentsActivitySourceTest.cs +++ b/src/Components/Components/test/ComponentsActivitySourceTest.cs @@ -33,74 +33,6 @@ public void Constructor_CreatesActivitySourceCorrectly() Assert.NotNull(componentsActivitySource); } - [Fact] - public void CaptureHttpContext_ReturnsDefault_WhenNoCurrentActivity() - { - // Arrange - Activity.Current = null; - - // Act - var result = ComponentsActivitySource.CaptureHttpContext(); - - // Assert - Assert.Equal(default, result); - } - - [Fact] - public void CaptureHttpContext_ReturnsDefault_WhenActivityHasWrongName() - { - // Arrange - using var activity = new ActivitySource("Test").StartActivity("WrongName"); - Activity.Current = activity; - - // Act - var result = ComponentsActivitySource.CaptureHttpContext(); - - // Assert - Assert.Equal(default, result); - } - - [Fact] - public void StartCircuitActivity_CreatesAndStartsActivity() - { - // Arrange - var componentsActivitySource = new ComponentsActivitySource(); - var circuitId = "test-circuit-id"; - var httpContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); - - // Act - var activity = componentsActivitySource.StartCircuitActivity(circuitId, httpContext); - - // Assert - Assert.NotNull(activity); - Assert.Equal(ComponentsActivitySource.OnCircuitName, activity.OperationName); - Assert.Equal($"Circuit {circuitId}", activity.DisplayName); - Assert.Equal(ActivityKind.Internal, activity.Kind); - Assert.True(activity.IsAllDataRequested); - Assert.Equal(circuitId, activity.GetTagItem("aspnetcore.components.circuit.id")); - Assert.Contains(activity.Links, link => link.Context == httpContext); - Assert.False(activity.IsStopped); - } - - [Fact] - public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() - { - // Arrange - var componentsActivitySource = new ComponentsActivitySource(); - var circuitId = "test-circuit-id"; - var httpContext = default(ActivityContext); - var activity = componentsActivitySource.StartCircuitActivity(circuitId, httpContext); - var exception = new InvalidOperationException("Test exception"); - - // Act - componentsActivitySource.FailCircuitActivity(activity, exception); - - // Assert - Assert.True(activity!.IsStopped); - Assert.Equal(ActivityStatusCode.Error, activity.Status); - Assert.Equal(exception.GetType().FullName, activity.GetTagItem("error.type")); - } - [Fact] public void StartRouteActivity_CreatesAndStartsActivity() { @@ -110,10 +42,11 @@ public void StartRouteActivity_CreatesAndStartsActivity() var route = "/test-route"; // First set up a circuit context - componentsActivitySource.StartCircuitActivity("test-circuit-id", default); + componentsActivitySource._circuitId = "test-circuit-id"; // Act - var activity = componentsActivitySource.StartRouteActivity(componentType, route); + var wrapper = componentsActivitySource.StartRouteActivity(componentType, route); + var activity = wrapper.Activity; // Assert Assert.NotNull(activity); @@ -137,11 +70,12 @@ public void StartEventActivity_CreatesAndStartsActivity() var attributeName = "onclick"; // First set up a circuit and route context - componentsActivitySource.StartCircuitActivity("test-circuit-id", default); + componentsActivitySource._circuitId = "test-circuit-id"; componentsActivitySource.StartRouteActivity("ParentComponent", "/parent"); // Act - var activity = componentsActivitySource.StartEventActivity(componentType, methodName, attributeName); + var wrapper = componentsActivitySource.StartEventActivity(componentType, methodName, attributeName); + var activity = wrapper.Activity; // Assert Assert.NotNull(activity); @@ -161,11 +95,12 @@ public void FailEventActivity_SetsErrorStatusAndStopsActivity() { // Arrange var componentsActivitySource = new ComponentsActivitySource(); - var activity = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var wrapper = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var activity = wrapper.Activity; var exception = new InvalidOperationException("Test exception"); // Act - ComponentsActivitySource.FailEventActivity(activity, exception); + ComponentsActivitySource.StopComponentActivity(wrapper, exception); // Assert Assert.True(activity!.IsStopped); @@ -178,11 +113,12 @@ public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() { // Arrange var componentsActivitySource = new ComponentsActivitySource(); - var activity = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var wrapper = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var activity = wrapper.Activity; var task = Task.CompletedTask; // Act - await ComponentsActivitySource.CaptureEventStopAsync(task, activity); + await ComponentsActivitySource.CaptureEventStopAsync(task, wrapper); // Assert Assert.True(activity!.IsStopped); @@ -194,12 +130,13 @@ public async Task CaptureEventStopAsync_FailsActivityOnException() { // Arrange var componentsActivitySource = new ComponentsActivitySource(); - var activity = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var wrapper = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var activity = wrapper.Activity; var exception = new InvalidOperationException("Test exception"); var task = Task.FromException(exception); // Act - await ComponentsActivitySource.CaptureEventStopAsync(task, activity); + await ComponentsActivitySource.CaptureEventStopAsync(task, wrapper); // Assert Assert.True(activity!.IsStopped); @@ -207,20 +144,6 @@ public async Task CaptureEventStopAsync_FailsActivityOnException() Assert.Equal(exception.GetType().FullName, activity.GetTagItem("error.type")); } - [Fact] - public void StartCircuitActivity_HandlesNullValues() - { - // Arrange - var componentsActivitySource = new ComponentsActivitySource(); - - // Act - var activity = componentsActivitySource.StartCircuitActivity(null, default); - - // Assert - Assert.NotNull(activity); - Assert.Equal("Circuit ", activity.DisplayName); - } - [Fact] public void StartRouteActivity_HandlesNullValues() { @@ -228,7 +151,8 @@ public void StartRouteActivity_HandlesNullValues() var componentsActivitySource = new ComponentsActivitySource(); // Act - var activity = componentsActivitySource.StartRouteActivity(null, null); + var wrapper = componentsActivitySource.StartRouteActivity(null, null); + var activity = wrapper.Activity; // Assert Assert.NotNull(activity); @@ -242,7 +166,8 @@ public void StartEventActivity_HandlesNullValues() var componentsActivitySource = new ComponentsActivitySource(); // Act - var activity = componentsActivitySource.StartEventActivity(null, null, null); + var wrapper = componentsActivitySource.StartEventActivity(null, null, null); + var activity = wrapper.Activity; // Assert Assert.NotNull(activity); diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs new file mode 100644 index 000000000000..c958485b857f --- /dev/null +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.Server.Circuits; + +internal struct CircuitActivityWrapper +{ + public Activity? Previous; + public Activity? Activity; +} + +internal class CircuitActivitySource +{ + internal const string Name = "Microsoft.AspNetCore.Components"; + internal const string OnCircuitName = $"{Name}.CircuitStart"; + + private ActivitySource ActivitySource { get; } = new ActivitySource(Name); + + public CircuitActivityWrapper StartCircuitActivity(string circuitId, ActivityContext httpActivityContext, Renderer? renderer) + { + var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId:null, null, null); + if (activity is not null) + { + if (activity.IsAllDataRequested) + { + if (circuitId != null) + { + activity.SetTag("aspnetcore.components.circuit.id", circuitId); + } + if (httpActivityContext != default) + { + activity.AddLink(new ActivityLink(httpActivityContext)); + } + } + activity.DisplayName = $"Circuit {circuitId ?? ""}"; + var previousActivity = Activity.Current; + Activity.Current = null; // do not inherit the parent activity + activity.Start(); + + if (renderer != null) + { + SetCircuitActivityContext(renderer, httpActivityContext, activity.Context, circuitId); + } + return new CircuitActivityWrapper { Previous = previousActivity, Activity = activity }; + } + return default; + } + + public static void StopCircuitActivity(CircuitActivityWrapper wrapper, Exception? ex) + { + if (wrapper.Activity != null && !wrapper.Activity.IsStopped) + { + if (ex != null) + { + wrapper.Activity.SetTag("error.type", ex.GetType().FullName); + wrapper.Activity.SetStatus(ActivityStatusCode.Error); + } + wrapper.Activity.Stop(); + + if (Activity.Current == null && wrapper.Previous != null && !wrapper.Previous.IsStopped) + { + Activity.Current = wrapper.Previous; + } + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "SetCircuitActivityContext")] + static extern void SetCircuitActivityContext(Renderer type, ActivityContext httpContext, ActivityContext circuitContext, string circuitId); +} diff --git a/src/Components/Server/src/Circuits/CircuitFactory.cs b/src/Components/Server/src/Circuits/CircuitFactory.cs index 6683c2e20d75..010b8f9050e2 100644 --- a/src/Components/Server/src/Circuits/CircuitFactory.cs +++ b/src/Components/Server/src/Circuits/CircuitFactory.cs @@ -20,13 +20,13 @@ internal sealed partial class CircuitFactory : ICircuitFactory private readonly CircuitIdFactory _circuitIdFactory; private readonly CircuitOptions _options; private readonly ILogger _logger; - private readonly CircuitMetrics? _circuitMetrics; + private readonly CircuitMetrics _circuitMetrics; public CircuitFactory( IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory, CircuitIdFactory circuitIdFactory, - CircuitMetrics? circuitMetrics, + CircuitMetrics circuitMetrics, IOptions options) { _scopeFactory = scopeFactory; @@ -54,6 +54,8 @@ public async ValueTask CreateCircuitHostAsync( var navigationManager = (RemoteNavigationManager)scope.ServiceProvider.GetRequiredService(); var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService(); var scrollToLocationHash = (RemoteScrollToLocationHash)scope.ServiceProvider.GetRequiredService(); + var circuitActivitySource = scope.ServiceProvider.GetRequiredService(); + if (client.Connected) { navigationManager.AttachJsRuntime(jsRuntime); @@ -66,7 +68,6 @@ public async ValueTask CreateCircuitHostAsync( { navigationManager.Initialize(baseUri, uri); } - var componentsActivitySource = scope.ServiceProvider.GetService(); if (components.Count > 0) { @@ -110,7 +111,7 @@ public async ValueTask CreateCircuitHostAsync( navigationManager, circuitHandlers, _circuitMetrics, - componentsActivitySource, + circuitActivitySource, _loggerFactory.CreateLogger()); Log.CreatedCircuit(_logger, circuitHost); diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index 38b50461ce3e..8d7f5a6adeb2 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -24,8 +24,8 @@ internal partial class CircuitHost : IAsyncDisposable private readonly CircuitOptions _options; private readonly RemoteNavigationManager _navigationManager; private readonly ILogger _logger; - private readonly CircuitMetrics? _circuitMetrics; - private readonly ComponentsActivitySource? _componentsActivitySource; + private readonly CircuitMetrics _circuitMetrics; + private readonly CircuitActivitySource _circuitActivitySource; private Func, Task> _dispatchInboundActivity; private CircuitHandler[] _circuitHandlers; private bool _initialized; @@ -51,8 +51,8 @@ public CircuitHost( RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, - CircuitMetrics? circuitMetrics, - ComponentsActivitySource? componentsActivitySource, + CircuitMetrics circuitMetrics, + CircuitActivitySource circuitActivitySource, ILogger logger) { CircuitId = circuitId; @@ -71,7 +71,7 @@ public CircuitHost( _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager)); _circuitHandlers = circuitHandlers ?? throw new ArgumentNullException(nameof(circuitHandlers)); _circuitMetrics = circuitMetrics; - _componentsActivitySource = componentsActivitySource; + _circuitActivitySource = circuitActivitySource; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); Services = scope.ServiceProvider; @@ -108,7 +108,7 @@ public CircuitHost( // InitializeAsync is used in a fire-and-forget context, so it's responsible for its own // error handling. - public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, ActivityContext httpContext, CancellationToken cancellationToken) + public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, ActivityContext httpActivityContext, CancellationToken cancellationToken) { Log.InitializationStarted(_logger); @@ -118,13 +118,14 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, A { throw new InvalidOperationException("The circuit host is already initialized."); } - Activity? activity = null; + + CircuitActivityWrapper circuitActivity = default; try { _initialized = true; // We're ready to accept incoming JSInterop calls from here on - activity = _componentsActivitySource?.StartCircuitActivity(CircuitId.Id, httpContext); + circuitActivity = _circuitActivitySource.StartCircuitActivity(CircuitId.Id, httpActivityContext, Renderer); _startTime = (_circuitMetrics != null && _circuitMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0; // We only run the handlers in case we are in a Blazor Server scenario, which renders @@ -170,11 +171,11 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, A Log.InitializationSucceeded(_logger); - activity?.Stop(); + CircuitActivitySource.StopCircuitActivity(circuitActivity, null); } catch (Exception ex) { - _componentsActivitySource?.FailCircuitActivity(activity, ex); + CircuitActivitySource.StopCircuitActivity(circuitActivity, ex); // Report errors asynchronously. InitializeAsync is designed not to throw. Log.InitializationFailed(_logger, ex); diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index 7f2345bff74a..f6701819edbc 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -60,11 +60,11 @@ public RemoteRenderer( public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); - protected internal override ResourceAssetCollection Assets => _resourceCollection ?? base.Assets; + protected override ResourceAssetCollection Assets => _resourceCollection ?? base.Assets; - protected internal override RendererInfo RendererInfo => _componentPlatform; + protected override RendererInfo RendererInfo => _componentPlatform; - protected internal override IComponentRenderMode? GetComponentRenderMode(IComponent component) => RenderMode.InteractiveServer; + protected override IComponentRenderMode? GetComponentRenderMode(IComponent component) => RenderMode.InteractiveServer; public Task AddComponentAsync(Type componentType, ParameterView parameters, string domElementSelector) { @@ -306,7 +306,7 @@ public Task OnRenderCompletedAsync(long incomingBatchId, string? errorMessageOrN } } - protected internal override IComponent ResolveComponentForRenderMode([DynamicallyAccessedMembers(Component)] Type componentType, int? parentComponentId, IComponentActivator componentActivator, IComponentRenderMode renderMode) + protected override IComponent ResolveComponentForRenderMode([DynamicallyAccessedMembers(Component)] Type componentType, int? parentComponentId, IComponentActivator componentActivator, IComponentRenderMode renderMode) => renderMode switch { InteractiveServerRenderMode or InteractiveAutoRenderMode => componentActivator.CreateInstance(componentType), diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index 84561349ee48..f0da1ca16e86 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; @@ -44,7 +45,6 @@ internal sealed partial class ComponentHub : Hub private readonly CircuitRegistry _circuitRegistry; private readonly ICircuitHandleRegistry _circuitHandleRegistry; private readonly ILogger _logger; - private readonly ActivityContext _httpContext; public ComponentHub( IServerComponentDeserializer serializer, @@ -62,7 +62,6 @@ public ComponentHub( _circuitRegistry = circuitRegistry; _circuitHandleRegistry = circuitHandleRegistry; _logger = logger; - _httpContext = ComponentsActivitySource.CaptureHttpContext(); } /// @@ -140,7 +139,8 @@ public async ValueTask StartCircuit(string baseUri, string uri, string s // SignalR message loop (we'd get a deadlock if any of the initialization // logic relied on receiving a subsequent message from SignalR), and it will // take care of its own errors anyway. - _ = circuitHost.InitializeAsync(store, _httpContext, Context.ConnectionAborted); + var httpActivityContext = Context.GetHttpContext().Features.Get()?.Activity.Context ?? default; + _ = circuitHost.InitializeAsync(store, httpActivityContext, Context.ConnectionAborted); // It's safe to *publish* the circuit now because nothing will be able // to run inside it until after InitializeAsync completes. diff --git a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs index 4c6eb34d27f6..8e7662b1712b 100644 --- a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs +++ b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs @@ -89,6 +89,8 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti services.TryAddEnumerable(ServiceDescriptor.Singleton, CircuitOptionsJSInteropDetailedErrorsConfiguration>()); services.TryAddEnumerable(ServiceDescriptor.Singleton, CircuitOptionsJavaScriptInitializersConfiguration>()); + services.TryAddSingleton(); + if (configure != null) { services.Configure(configure); diff --git a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs new file mode 100644 index 000000000000..4edb168c54e3 --- /dev/null +++ b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Microsoft.JSInterop; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.InternalTesting; +using Moq; + +namespace Microsoft.AspNetCore.Components.Server.Circuits; + +public class CircuitActivitySourceTest +{ + private readonly ActivityListener _listener; + private readonly List _activities; + + public CircuitActivitySourceTest() + { + _activities = new List(); + _listener = new ActivityListener + { + ShouldListenTo = source => source.Name == CircuitActivitySource.Name, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + ActivityStarted = activity => _activities.Add(activity), + ActivityStopped = activity => { } + }; + ActivitySource.AddActivityListener(_listener); + } + + [Fact] + public void StartCircuitActivity_CreatesAndStartsActivity() + { + // Arrange + var circuitActivitySource = new CircuitActivitySource(); + var circuitId = "test-circuit-id"; + var httpContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); + + // Act + var wrapper = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); + var activity = wrapper.Activity; + + // Assert + Assert.NotNull(activity); + Assert.Equal(CircuitActivitySource.OnCircuitName, activity.OperationName); + Assert.Equal($"Circuit {circuitId}", activity.DisplayName); + Assert.Equal(ActivityKind.Internal, activity.Kind); + Assert.True(activity.IsAllDataRequested); + Assert.Equal(circuitId, activity.GetTagItem("aspnetcore.components.circuit.id")); + Assert.Contains(activity.Links, link => link.Context == httpContext); + Assert.False(activity.IsStopped); + } + + [Fact] + public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() + { + // Arrange + var circuitActivitySource = new CircuitActivitySource(); + var circuitId = "test-circuit-id"; + var httpContext = default(ActivityContext); + var wrapper = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); + var activity = wrapper.Activity; + var exception = new InvalidOperationException("Test exception"); + + // Act + CircuitActivitySource.StopCircuitActivity(wrapper, exception); + + // Assert + Assert.True(activity!.IsStopped); + Assert.Equal(ActivityStatusCode.Error, activity.Status); + Assert.Equal(exception.GetType().FullName, activity.GetTagItem("error.type")); + } + + [Fact] + public void StartCircuitActivity_HandlesNullValues() + { + // Arrange + var circuitActivitySource = new CircuitActivitySource(); + + // Act + var wrapper = circuitActivitySource.StartCircuitActivity(null, default, null); + var activity = wrapper.Activity; + + // Assert + Assert.NotNull(activity); + Assert.Equal("Circuit ", activity.DisplayName); + } + +} diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index 11e9f7ed4d65..9852cab9a82f 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -16,8 +16,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits; internal class TestCircuitHost : CircuitHost { - private TestCircuitHost(CircuitId circuitId, AsyncServiceScope scope, CircuitOptions options, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList descriptors, RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, CircuitMetrics circuitMetrics, ComponentsActivitySource componentsActivitySource, ILogger logger) - : base(circuitId, scope, options, client, renderer, descriptors, jsRuntime, navigationManager, circuitHandlers, circuitMetrics, componentsActivitySource, logger) + private TestCircuitHost(CircuitId circuitId, AsyncServiceScope scope, CircuitOptions options, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList descriptors, RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, CircuitMetrics circuitMetrics, CircuitActivitySource circuitActivitySource, ILogger logger) + : base(circuitId, scope, options, client, renderer, descriptors, jsRuntime, navigationManager, circuitHandlers, circuitMetrics, circuitActivitySource, logger) { } @@ -39,7 +39,7 @@ public static CircuitHost Create( .Returns(jsRuntime); var serverComponentDeserializer = Mock.Of(); var circuitMetrics = new CircuitMetrics(new TestMeterFactory()); - var componentsActivitySource = new ComponentsActivitySource(); + var circuitActivitySource = new CircuitActivitySource(); if (remoteRenderer == null) { @@ -66,7 +66,7 @@ public static CircuitHost Create( navigationManager, handlers, circuitMetrics, - componentsActivitySource, + circuitActivitySource, NullLogger.Instance); } } From 5a001ed10212fae5c84ebeaa549b57f0968ddb49 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 9 Jun 2025 20:24:33 +0200 Subject: [PATCH 02/17] more --- src/Components/Components/src/Routing/Router.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index f2eb174552cf..d394fecc71fd 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -3,7 +3,6 @@ #nullable disable warnings -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Metadata; From b3f3857b3c17b66d0efe5f92d27e3728a0ad691d Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 10 Jun 2025 08:34:22 +0200 Subject: [PATCH 03/17] more --- src/Components/Server/src/Circuits/CircuitActivitySource.cs | 2 -- src/Components/Server/src/Circuits/RemoteRenderer.cs | 2 +- src/Components/Server/src/ComponentHub.cs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index c958485b857f..cfb97e99215b 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -3,9 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Server.Circuits; internal struct CircuitActivityWrapper { diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index f6701819edbc..31b29206212b 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -369,7 +369,7 @@ private async Task CaptureAsyncExceptions(Task task) } } - private static new partial class Log + private static partial class Log { [LoggerMessage(100, LogLevel.Warning, "Unhandled exception rendering component: {Message}", EventName = "ExceptionRenderingComponent")] private static partial void UnhandledExceptionRenderingComponent(ILogger logger, string message, Exception exception); diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index f0da1ca16e86..89a38cfeebf9 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; -using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.AspNetCore.DataProtection; From efd2aeeeb957efe361a37726cf42ee1f4ca6f7cf Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 10 Jun 2025 13:05:53 +0200 Subject: [PATCH 04/17] more --- .../src/ComponentsActivitySource.cs | 58 +++++++++---------- .../Components/src/RenderTree/Renderer.cs | 13 +++-- .../Components/src/Routing/Router.cs | 4 +- .../test/ComponentsActivitySourceTest.cs | 6 +- .../src/RazorComponentEndpointInvoker.cs | 11 ++++ .../src/Circuits/CircuitActivitySource.cs | 12 ++-- 6 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 696088669a93..51d0c87a7512 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -20,11 +20,11 @@ internal class ComponentsActivitySource internal const string OnRouteName = $"{Name}.RouteChange"; internal const string OnEventName = $"{Name}.HandleEvent"; - internal ActivityContext _httpContext; - internal ActivityContext _circuitContext; + internal ActivityContext _httpActivityContext; + internal ActivityContext _routeContext; + internal ActivityContext _circuitActivityContext; internal string? _circuitId; - private ActivityContext _routeContext; private ActivitySource ActivitySource { get; } = new ActivitySource(Name); @@ -47,14 +47,6 @@ public ComponentsActivityWrapper StartRouteActivity(string componentType, string { activity.SetTag("aspnetcore.components.route", route); } - if (_httpContext != default) - { - activity.AddLink(new ActivityLink(_httpContext)); - } - if (_circuitContext != default) - { - activity.AddLink(new ActivityLink(_circuitContext)); - } } activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; @@ -90,18 +82,6 @@ public ComponentsActivityWrapper StartEventActivity(string? componentType, strin { activity.SetTag("aspnetcore.components.attribute.name", attributeName); } - if (_httpContext != default) - { - activity.AddLink(new ActivityLink(_httpContext)); - } - if (_circuitContext != default) - { - activity.AddLink(new ActivityLink(_circuitContext)); - } - if (_routeContext != default) - { - activity.AddLink(new ActivityLink(_routeContext)); - } } activity.DisplayName = $"Event {attributeName ?? "[unknown attribute]"} -> {componentType ?? "[unknown component]"}.{methodName ?? "[unknown method]"}"; @@ -113,16 +93,36 @@ public ComponentsActivityWrapper StartEventActivity(string? componentType, strin return default; } - public static void StopComponentActivity(ComponentsActivityWrapper wrapper, Exception? ex) + public void StopComponentActivity(ComponentsActivityWrapper wrapper, Exception? ex) { - if (wrapper.Activity != null && !wrapper.Activity.IsStopped) + var activity = wrapper.Activity; + if (activity != null && !activity.IsStopped) { + if (activity.IsAllDataRequested) + { + if (_circuitId != null) + { + activity.SetTag("aspnetcore.components.circuit.id", _circuitId); + } + if (_httpActivityContext != default) + { + activity.AddLink(new ActivityLink(_httpActivityContext)); + } + if (_circuitActivityContext != default) + { + activity.AddLink(new ActivityLink(_circuitActivityContext)); + } + if (_routeContext != default && activity.Context != _routeContext) + { + activity.AddLink(new ActivityLink(_routeContext)); + } + } if (ex != null) { - wrapper.Activity.SetTag("error.type", ex.GetType().FullName); - wrapper.Activity.SetStatus(ActivityStatusCode.Error); + activity.SetTag("error.type", ex.GetType().FullName); + activity.SetStatus(ActivityStatusCode.Error); } - wrapper.Activity.Stop(); + activity.Stop(); if (Activity.Current == null && wrapper.Previous != null && !wrapper.Previous.IsStopped) { @@ -131,7 +131,7 @@ public static void StopComponentActivity(ComponentsActivityWrapper wrapper, Exce } } - public static async Task CaptureEventStopAsync(Task task, ComponentsActivityWrapper wrapper) + public async Task CaptureEventStopAsync(Task task, ComponentsActivityWrapper wrapper) { try { diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 48e84e3d8141..32ebe08a3b32 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -96,6 +96,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, _componentFactory = new ComponentFactory(componentActivator, this); _componentsMetrics = serviceProvider.GetService(); _componentsActivitySource = serviceProvider.GetService(); + ServiceProviderCascadingValueSuppliers = serviceProvider.GetService() is null ? Array.Empty() : serviceProvider.GetServices().ToArray(); @@ -114,14 +115,16 @@ private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvid ?? new DefaultComponentActivator(serviceProvider); } - internal void SetCircuitActivityContext(ActivityContext httpContext, ActivityContext circuitContext, string circuitId) + internal ActivityContext LinkActivityContexts(ActivityContext httpActivityContext, ActivityContext circuitActivityContext, string? circuitId) { if (ComponentActivitySource != null) { - ComponentActivitySource._httpContext = httpContext; - ComponentActivitySource._circuitContext = circuitContext; + ComponentActivitySource._httpActivityContext = httpActivityContext; + ComponentActivitySource._circuitActivityContext = circuitActivityContext; ComponentActivitySource._circuitId = circuitId; + return ComponentActivitySource._routeContext; } + return default; } /// @@ -521,7 +524,7 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie // stop activity/trace if (ComponentActivitySource != null && wrapper.Activity != null) { - _ = ComponentsActivitySource.CaptureEventStopAsync(task, wrapper); + _ = ComponentActivitySource.CaptureEventStopAsync(task, wrapper); } } catch (Exception e) @@ -535,7 +538,7 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie if (ComponentActivitySource != null && wrapper.Activity != null) { - ComponentsActivitySource.StopComponentActivity(wrapper, e); + ComponentActivitySource.StopComponentActivity(wrapper, e); } HandleExceptionViaErrorBoundary(e, receiverComponentState); diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index d394fecc71fd..da372dee4556 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -240,7 +240,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) endpointRouteData = RouteTable.ProcessParameters(endpointRouteData); _renderHandle.Render(Found(endpointRouteData)); - ComponentsActivitySource.StopComponentActivity(activityWrapper, null); + _renderHandle.ComponentActivitySource?.StopComponentActivity(activityWrapper, null); return; } @@ -295,7 +295,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) NavigationManager.NavigateTo(_locationAbsolute, forceLoad: true); } } - ComponentsActivitySource.StopComponentActivity(activityWrapper, null); + _renderHandle.ComponentActivitySource?.StopComponentActivity(activityWrapper, null); } private ComponentsActivityWrapper RecordDiagnostics(string componentType, string template) diff --git a/src/Components/Components/test/ComponentsActivitySourceTest.cs b/src/Components/Components/test/ComponentsActivitySourceTest.cs index cc061df8bc48..7fc0298a7fe2 100644 --- a/src/Components/Components/test/ComponentsActivitySourceTest.cs +++ b/src/Components/Components/test/ComponentsActivitySourceTest.cs @@ -100,7 +100,7 @@ public void FailEventActivity_SetsErrorStatusAndStopsActivity() var exception = new InvalidOperationException("Test exception"); // Act - ComponentsActivitySource.StopComponentActivity(wrapper, exception); + componentsActivitySource.StopComponentActivity(wrapper, exception); // Assert Assert.True(activity!.IsStopped); @@ -118,7 +118,7 @@ public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() var task = Task.CompletedTask; // Act - await ComponentsActivitySource.CaptureEventStopAsync(task, wrapper); + await componentsActivitySource.CaptureEventStopAsync(task, wrapper); // Assert Assert.True(activity!.IsStopped); @@ -136,7 +136,7 @@ public async Task CaptureEventStopAsync_FailsActivityOnException() var task = Task.FromException(exception); // Act - await ComponentsActivitySource.CaptureEventStopAsync(task, wrapper); + await componentsActivitySource.CaptureEventStopAsync(task, wrapper); // Assert Assert.True(activity!.IsStopped); diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 245f811d7f76..f0f8264328da 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -3,10 +3,12 @@ using System.Buffers; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Components.Endpoints.Rendering; +using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -41,6 +43,7 @@ private async Task RenderComponentCore(HttpContext context) var isErrorHandler = context.Features.Get() is not null; var hasStatusCodePage = context.Features.Get() is not null; var isReExecuted = context.Features.Get() is not null; + var httpActivityContext = context.Features.Get()?.Activity.Context ?? default; if (isErrorHandler) { Log.InteractivityDisabledForErrorHandling(_logger); @@ -80,6 +83,11 @@ private async Task RenderComponentCore(HttpContext context) return Task.CompletedTask; }); + if (httpActivityContext != default) + { + LinkActivityContexts(_renderer, default, httpActivityContext, null); + } + await _renderer.InitializeStandardComponentServicesAsync( context, componentType: pageComponent, @@ -273,6 +281,9 @@ private async Task ValidateRequestAsync(HttpContext cont return null; } + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "LinkActivityContexts")] + static extern ActivityContext LinkActivityContexts(Renderer type, ActivityContext httpContext, ActivityContext circuitContext, string? circuitId); + [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] private readonly struct RequestValidationState(bool isValid, bool isPost, string? handlerName) { diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index cfb97e99215b..42dd8cfbd407 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -13,7 +13,7 @@ internal struct CircuitActivityWrapper internal class CircuitActivitySource { - internal const string Name = "Microsoft.AspNetCore.Components"; + internal const string Name = "Microsoft.AspNetCore.Components.Server.Circuits"; internal const string OnCircuitName = $"{Name}.CircuitStart"; private ActivitySource ActivitySource { get; } = new ActivitySource(Name); @@ -41,7 +41,11 @@ public CircuitActivityWrapper StartCircuitActivity(string circuitId, ActivityCon if (renderer != null) { - SetCircuitActivityContext(renderer, httpActivityContext, activity.Context, circuitId); + var routeActivityContext = LinkActivityContexts(renderer, httpActivityContext, activity.Context, circuitId); + if (routeActivityContext != default) + { + activity.AddLink(new ActivityLink(routeActivityContext)); + } } return new CircuitActivityWrapper { Previous = previousActivity, Activity = activity }; } @@ -66,6 +70,6 @@ public static void StopCircuitActivity(CircuitActivityWrapper wrapper, Exception } } - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "SetCircuitActivityContext")] - static extern void SetCircuitActivityContext(Renderer type, ActivityContext httpContext, ActivityContext circuitContext, string circuitId); + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "LinkActivityContexts")] + static extern ActivityContext LinkActivityContexts(Renderer type, ActivityContext httpContext, ActivityContext circuitContext, string? circuitId); } From deb81656411a9b274c14ab91d30aa5fccd780eb8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 10 Jun 2025 13:31:21 +0200 Subject: [PATCH 05/17] more --- src/Components/Components/src/ComponentsActivitySource.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 51d0c87a7512..52e403a59b8d 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -25,7 +25,6 @@ internal class ComponentsActivitySource internal ActivityContext _circuitActivityContext; internal string? _circuitId; - private ActivitySource ActivitySource { get; } = new ActivitySource(Name); public ComponentsActivityWrapper StartRouteActivity(string componentType, string route) From a978c27224402bbc7003ec8164537bfa99af9d75 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 10 Jun 2025 13:53:02 +0200 Subject: [PATCH 06/17] more --- src/Components/Components/src/ComponentsActivitySource.cs | 6 +++++- .../Server/src/Circuits/CircuitActivitySource.cs | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 52e403a59b8d..aeceb1e569f4 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -32,6 +32,7 @@ public ComponentsActivityWrapper StartRouteActivity(string componentType, string var activity = ActivitySource.CreateActivity(OnRouteName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) { + var previousActivity = Activity.Current; if (activity.IsAllDataRequested) { if (_circuitId != null) @@ -46,10 +47,13 @@ public ComponentsActivityWrapper StartRouteActivity(string componentType, string { activity.SetTag("aspnetcore.components.route", route); } + if (previousActivity != null) + { + activity.AddLink(new ActivityLink(previousActivity.Context)); + } } activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; - var previousActivity = Activity.Current; Activity.Current = null; // do not inherit the parent activity activity.Start(); _routeContext = activity.Context; diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index 42dd8cfbd407..19185cf208a8 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -23,6 +23,8 @@ public CircuitActivityWrapper StartCircuitActivity(string circuitId, ActivityCon var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId:null, null, null); if (activity is not null) { + var previousActivity = Activity.Current; + if (activity.IsAllDataRequested) { if (circuitId != null) @@ -33,9 +35,12 @@ public CircuitActivityWrapper StartCircuitActivity(string circuitId, ActivityCon { activity.AddLink(new ActivityLink(httpActivityContext)); } + if (previousActivity != null) + { + activity.AddLink(new ActivityLink(previousActivity.Context)); + } } activity.DisplayName = $"Circuit {circuitId ?? ""}"; - var previousActivity = Activity.Current; Activity.Current = null; // do not inherit the parent activity activity.Start(); From f030f484b8aa3b0d7de01ef59bf7a8f180c334d1 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 12:46:50 +0200 Subject: [PATCH 07/17] feedback --- src/Components/Components/src/RenderTree/Renderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 32ebe08a3b32..e75bc47b9c43 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -518,7 +518,7 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie { receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; methodName ??= callback.Delegate.Method?.Name; - _ = ComponentMetrics.CaptureEventDuration(task, eventStartTimestamp, null, null, attributeName); + _ = ComponentMetrics.CaptureEventDuration(task, eventStartTimestamp, receiverName, methodName, attributeName); } // stop activity/trace @@ -533,7 +533,7 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie { receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; methodName ??= callback.Delegate.Method?.Name; - ComponentMetrics.FailEventSync(e, eventStartTimestamp, null, null, attributeName); + ComponentMetrics.FailEventSync(e, eventStartTimestamp, receiverName, methodName, attributeName); } if (ComponentActivitySource != null && wrapper.Activity != null) From 768834eb024ba3e513666a759724b2df8e7cd155 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 12:55:01 +0200 Subject: [PATCH 08/17] feedback --- .../src/ComponentsActivitySource.cs | 27 ++++++++++--------- .../Components/src/RenderTree/Renderer.cs | 12 ++++----- .../Components/src/Routing/Router.cs | 18 ++++++------- .../test/ComponentsActivitySourceTest.cs | 6 ++--- .../src/Circuits/CircuitActivitySource.cs | 23 +++++++++------- .../Server/src/Circuits/CircuitHost.cs | 8 +++--- .../Circuits/CircuitActivitySourceTest.cs | 6 ++--- 7 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index aeceb1e569f4..c56f5e220903 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -5,7 +5,10 @@ namespace Microsoft.AspNetCore.Components; -internal struct ComponentsActivityWrapper +/// +/// Named tuple for restoring the previous activity after stopping the current one. +/// +internal struct ComponentsActivityHandle { public Activity? Previous; public Activity? Activity; @@ -27,7 +30,7 @@ internal class ComponentsActivitySource private ActivitySource ActivitySource { get; } = new ActivitySource(Name); - public ComponentsActivityWrapper StartRouteActivity(string componentType, string route) + public ComponentsActivityHandle StartRouteActivity(string componentType, string route) { var activity = ActivitySource.CreateActivity(OnRouteName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) @@ -57,12 +60,12 @@ public ComponentsActivityWrapper StartRouteActivity(string componentType, string Activity.Current = null; // do not inherit the parent activity activity.Start(); _routeContext = activity.Context; - return new ComponentsActivityWrapper { Activity = activity, Previous = previousActivity }; + return new ComponentsActivityHandle { Activity = activity, Previous = previousActivity }; } return default; } - public ComponentsActivityWrapper StartEventActivity(string? componentType, string? methodName, string? attributeName) + public ComponentsActivityHandle StartEventActivity(string? componentType, string? methodName, string? attributeName) { var activity = ActivitySource.CreateActivity(OnEventName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) @@ -91,14 +94,14 @@ public ComponentsActivityWrapper StartEventActivity(string? componentType, strin var previousActivity = Activity.Current; Activity.Current = null; // do not inherit the parent activity activity.Start(); - return new ComponentsActivityWrapper { Activity = activity, Previous = previousActivity }; + return new ComponentsActivityHandle { Activity = activity, Previous = previousActivity }; } return default; } - public void StopComponentActivity(ComponentsActivityWrapper wrapper, Exception? ex) + public void StopComponentActivity(ComponentsActivityHandle activityHandle, Exception? ex) { - var activity = wrapper.Activity; + var activity = activityHandle.Activity; if (activity != null && !activity.IsStopped) { if (activity.IsAllDataRequested) @@ -127,23 +130,23 @@ public void StopComponentActivity(ComponentsActivityWrapper wrapper, Exception? } activity.Stop(); - if (Activity.Current == null && wrapper.Previous != null && !wrapper.Previous.IsStopped) + if (Activity.Current == null && activityHandle.Previous != null && !activityHandle.Previous.IsStopped) { - Activity.Current = wrapper.Previous; + Activity.Current = activityHandle.Previous; } } } - public async Task CaptureEventStopAsync(Task task, ComponentsActivityWrapper wrapper) + public async Task CaptureEventStopAsync(Task task, ComponentsActivityHandle activityHandle) { try { await task; - StopComponentActivity(wrapper, null); + StopComponentActivity(activityHandle, null); } catch (Exception ex) { - StopComponentActivity(wrapper, ex); + StopComponentActivity(activityHandle, ex); } } } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index e75bc47b9c43..d8746424f073 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -460,14 +460,14 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie var (renderedByComponentId, callback, attributeName) = GetRequiredEventBindingEntry(eventHandlerId); // collect trace - ComponentsActivityWrapper wrapper = default; + ComponentsActivityHandle activityHandle = default; string receiverName = null; string methodName = null; if (ComponentActivitySource != null) { receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; methodName ??= callback.Delegate.Method?.Name; - wrapper = ComponentActivitySource.StartEventActivity(receiverName, methodName, attributeName); + activityHandle = ComponentActivitySource.StartEventActivity(receiverName, methodName, attributeName); } var eventStartTimestamp = ComponentMetrics != null && ComponentMetrics.IsEventEnabled ? Stopwatch.GetTimestamp() : 0; @@ -522,9 +522,9 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie } // stop activity/trace - if (ComponentActivitySource != null && wrapper.Activity != null) + if (ComponentActivitySource != null && activityHandle.Activity != null) { - _ = ComponentActivitySource.CaptureEventStopAsync(task, wrapper); + _ = ComponentActivitySource.CaptureEventStopAsync(task, activityHandle); } } catch (Exception e) @@ -536,9 +536,9 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie ComponentMetrics.FailEventSync(e, eventStartTimestamp, receiverName, methodName, attributeName); } - if (ComponentActivitySource != null && wrapper.Activity != null) + if (ComponentActivitySource != null && activityHandle.Activity != null) { - ComponentActivitySource.StopComponentActivity(wrapper, e); + ComponentActivitySource.StopComponentActivity(activityHandle, e); } HandleExceptionViaErrorBoundary(e, receiverComponentState); diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index da372dee4556..7ec4e028a0e5 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -222,12 +222,12 @@ internal virtual void Refresh(bool isNavigationIntercepted) var relativePath = NavigationManager.ToBaseRelativePath(_locationAbsolute.AsSpan()); var locationPathSpan = TrimQueryOrHash(relativePath); var locationPath = $"/{locationPathSpan}"; - ComponentsActivityWrapper activityWrapper; + ComponentsActivityHandle activityHandle; // In order to avoid routing twice we check for RouteData if (RoutingStateProvider?.RouteData is { } endpointRouteData) { - activityWrapper = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); + activityHandle = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); // Other routers shouldn't provide RouteData, this is specific to our router component // and must abide by our syntax and behaviors. @@ -240,7 +240,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) endpointRouteData = RouteTable.ProcessParameters(endpointRouteData); _renderHandle.Render(Found(endpointRouteData)); - _renderHandle.ComponentActivitySource?.StopComponentActivity(activityWrapper, null); + _renderHandle.ComponentActivitySource?.StopComponentActivity(activityHandle, null); return; } @@ -257,7 +257,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) $"does not implement {typeof(IComponent).FullName}."); } - activityWrapper = RecordDiagnostics(context.Handler.FullName, context.Entry.RoutePattern.RawText); + activityHandle = RecordDiagnostics(context.Handler.FullName, context.Entry.RoutePattern.RawText); Log.NavigatingToComponent(_logger, context.Handler, locationPath, _baseUri); @@ -278,7 +278,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) { if (!isNavigationIntercepted) { - activityWrapper = RecordDiagnostics("NotFound", "NotFound"); + activityHandle = RecordDiagnostics("NotFound", "NotFound"); Log.DisplayingNotFound(_logger, locationPath, _baseUri); @@ -289,18 +289,18 @@ internal virtual void Refresh(bool isNavigationIntercepted) } else { - activityWrapper = RecordDiagnostics("External", "External"); + activityHandle = RecordDiagnostics("External", "External"); Log.NavigatingToExternalUri(_logger, _locationAbsolute, locationPath, _baseUri); NavigationManager.NavigateTo(_locationAbsolute, forceLoad: true); } } - _renderHandle.ComponentActivitySource?.StopComponentActivity(activityWrapper, null); + _renderHandle.ComponentActivitySource?.StopComponentActivity(activityHandle, null); } - private ComponentsActivityWrapper RecordDiagnostics(string componentType, string template) + private ComponentsActivityHandle RecordDiagnostics(string componentType, string template) { - ComponentsActivityWrapper activityWrapper = default; + ComponentsActivityHandle activityWrapper = default; if (_renderHandle.ComponentActivitySource != null) { activityWrapper = _renderHandle.ComponentActivitySource.StartRouteActivity(componentType, template); diff --git a/src/Components/Components/test/ComponentsActivitySourceTest.cs b/src/Components/Components/test/ComponentsActivitySourceTest.cs index 7fc0298a7fe2..d3bae74722af 100644 --- a/src/Components/Components/test/ComponentsActivitySourceTest.cs +++ b/src/Components/Components/test/ComponentsActivitySourceTest.cs @@ -95,12 +95,12 @@ public void FailEventActivity_SetsErrorStatusAndStopsActivity() { // Arrange var componentsActivitySource = new ComponentsActivitySource(); - var wrapper = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); - var activity = wrapper.Activity; + var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); // Act - componentsActivitySource.StopComponentActivity(wrapper, exception); + componentsActivitySource.StopComponentActivity(activityHandle, exception); // Assert Assert.True(activity!.IsStopped); diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index 19185cf208a8..e2d3f03bcebe 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -5,7 +5,10 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Components.RenderTree; -internal struct CircuitActivityWrapper +/// +/// Named tuple for restoring the previous activity after stopping the current one. +/// +internal struct CircuitActivityHandle { public Activity? Previous; public Activity? Activity; @@ -18,7 +21,7 @@ internal class CircuitActivitySource private ActivitySource ActivitySource { get; } = new ActivitySource(Name); - public CircuitActivityWrapper StartCircuitActivity(string circuitId, ActivityContext httpActivityContext, Renderer? renderer) + public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityContext httpActivityContext, Renderer? renderer) { var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId:null, null, null); if (activity is not null) @@ -52,25 +55,25 @@ public CircuitActivityWrapper StartCircuitActivity(string circuitId, ActivityCon activity.AddLink(new ActivityLink(routeActivityContext)); } } - return new CircuitActivityWrapper { Previous = previousActivity, Activity = activity }; + return new CircuitActivityHandle { Previous = previousActivity, Activity = activity }; } return default; } - public static void StopCircuitActivity(CircuitActivityWrapper wrapper, Exception? ex) + public static void StopCircuitActivity(CircuitActivityHandle activityHandle, Exception? ex) { - if (wrapper.Activity != null && !wrapper.Activity.IsStopped) + if (activityHandle.Activity != null && !activityHandle.Activity.IsStopped) { if (ex != null) { - wrapper.Activity.SetTag("error.type", ex.GetType().FullName); - wrapper.Activity.SetStatus(ActivityStatusCode.Error); + activityHandle.Activity.SetTag("error.type", ex.GetType().FullName); + activityHandle.Activity.SetStatus(ActivityStatusCode.Error); } - wrapper.Activity.Stop(); + activityHandle.Activity.Stop(); - if (Activity.Current == null && wrapper.Previous != null && !wrapper.Previous.IsStopped) + if (Activity.Current == null && activityHandle.Previous != null && !activityHandle.Previous.IsStopped) { - Activity.Current = wrapper.Previous; + Activity.Current = activityHandle.Previous; } } } diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index 8d7f5a6adeb2..b17612f47251 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -119,13 +119,13 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, A throw new InvalidOperationException("The circuit host is already initialized."); } - CircuitActivityWrapper circuitActivity = default; + CircuitActivityHandle activityHandle = default; try { _initialized = true; // We're ready to accept incoming JSInterop calls from here on - circuitActivity = _circuitActivitySource.StartCircuitActivity(CircuitId.Id, httpActivityContext, Renderer); + activityHandle = _circuitActivitySource.StartCircuitActivity(CircuitId.Id, httpActivityContext, Renderer); _startTime = (_circuitMetrics != null && _circuitMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0; // We only run the handlers in case we are in a Blazor Server scenario, which renders @@ -171,11 +171,11 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, A Log.InitializationSucceeded(_logger); - CircuitActivitySource.StopCircuitActivity(circuitActivity, null); + CircuitActivitySource.StopCircuitActivity(activityHandle, null); } catch (Exception ex) { - CircuitActivitySource.StopCircuitActivity(circuitActivity, ex); + CircuitActivitySource.StopCircuitActivity(activityHandle, ex); // Report errors asynchronously. InitializeAsync is designed not to throw. Log.InitializationFailed(_logger, ex); diff --git a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs index 4edb168c54e3..256a2e4a052b 100644 --- a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs +++ b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs @@ -62,12 +62,12 @@ public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() var circuitActivitySource = new CircuitActivitySource(); var circuitId = "test-circuit-id"; var httpContext = default(ActivityContext); - var wrapper = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); - var activity = wrapper.Activity; + var activityHandle = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); + var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); // Act - CircuitActivitySource.StopCircuitActivity(wrapper, exception); + CircuitActivitySource.StopCircuitActivity(activityHandle, exception); // Assert Assert.True(activity!.IsStopped); From 52ac6817f23aa23877892b0756e89642264d3f75 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 16:02:14 +0200 Subject: [PATCH 09/17] new public interface Microsoft.AspNetCore.Components.IComponentsActivityLinkStore --- .../src/ComponentsActivityLinkStore.cs | 88 +++++++++++++++ .../src/ComponentsActivitySource.cs | 100 ++++++++---------- .../Components/src/PublicAPI.Unshipped.txt | 8 ++ ...eringMetricsServiceCollectionExtensions.cs | 1 + .../Components/src/RenderTree/Renderer.cs | 14 +-- .../Components/src/Routing/Router.cs | 10 +- .../test/ComponentsActivitySourceTest.cs | 65 +++++++----- .../src/RazorComponentEndpointInvoker.cs | 9 +- .../src/Circuits/CircuitActivitySource.cs | 69 +++++++----- .../Server/src/Circuits/CircuitHost.cs | 4 +- .../ComponentServiceCollectionExtensions.cs | 2 +- .../Circuits/CircuitActivitySourceTest.cs | 8 +- .../Server/test/Circuits/TestCircuitHost.cs | 3 +- 13 files changed, 241 insertions(+), 140 deletions(-) create mode 100644 src/Components/Components/src/ComponentsActivityLinkStore.cs diff --git a/src/Components/Components/src/ComponentsActivityLinkStore.cs b/src/Components/Components/src/ComponentsActivityLinkStore.cs new file mode 100644 index 000000000000..a090cb1ea21e --- /dev/null +++ b/src/Components/Components/src/ComponentsActivityLinkStore.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.AspNetCore.Components; + +/// +/// Helper for storing links between diagnostic activities in Blazor components. +/// +public interface IComponentsActivityLinkStore +{ + /// + /// Sets the activity context for a specific category. + /// + /// Category of the trace. + /// Link to the trace. + /// Optional tag metadata. + void SetActivityContext(int category, ActivityContext activityLink, KeyValuePair? tag); + + /// + /// Will add all activity contexts except the one specified by to the . + /// + /// Category of the target trace. + /// Activity to add links to. + void AddActivityContexts(int exceptCategory, Activity targetActivity); +} + +/// +/// Helper for storing links between diagnostic activities in Blazor components. +/// +public static class ComponentsActivityCategory +{ + /// + /// Http trace. + /// + public const int Http = 0; + /// + /// SignalR trace. + /// + public const int SignalR = 1; + /// + /// Route trace. + /// + public const int Route = 2; + /// + /// Circuit trace. + /// + public const int Circuit = 3; + + internal const int COUNT = 4;// keep this one bigger than the last linkable category index + + /// + /// Event trace. + /// + internal const int Event = 5; // not linkable +} + +internal class ComponentsActivityLinkStore : IComponentsActivityLinkStore +{ + private readonly ActivityContext[] _activityLinks = new ActivityContext[ComponentsActivityCategory.COUNT]; + private readonly KeyValuePair?[] _activityTags = new KeyValuePair?[ComponentsActivityCategory.COUNT]; + + public void SetActivityContext(int category, ActivityContext activityLink, KeyValuePair? tag) + { + _activityLinks[category] = activityLink; + _activityTags[category] = tag; + } + + public void AddActivityContexts(int exceptCategory, Activity targetActivity) + { + for (var i = 0; i < ComponentsActivityCategory.COUNT; i++) + { + if (i != exceptCategory) + { + if (_activityLinks[i] != default) + { + targetActivity.AddLink(new ActivityLink(_activityLinks[i])); + } + var tag = _activityTags[i]; + if (tag != null) + { + targetActivity.SetTag(tag.Value.Key, tag.Value.Value); + } + } + } + } +} diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index c56f5e220903..1d667741de9e 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -5,15 +5,6 @@ namespace Microsoft.AspNetCore.Components; -/// -/// Named tuple for restoring the previous activity after stopping the current one. -/// -internal struct ComponentsActivityHandle -{ - public Activity? Previous; - public Activity? Activity; -} - /// /// This is instance scoped per renderer /// @@ -23,13 +14,15 @@ internal class ComponentsActivitySource internal const string OnRouteName = $"{Name}.RouteChange"; internal const string OnEventName = $"{Name}.HandleEvent"; - internal ActivityContext _httpActivityContext; - internal ActivityContext _routeContext; - internal ActivityContext _circuitActivityContext; - internal string? _circuitId; + private readonly IComponentsActivityLinkStore _activityLinkStore; private ActivitySource ActivitySource { get; } = new ActivitySource(Name); + public ComponentsActivitySource(IComponentsActivityLinkStore activityLinkStore) + { + _activityLinkStore = activityLinkStore ?? throw new ArgumentNullException(nameof(activityLinkStore)); + } + public ComponentsActivityHandle StartRouteActivity(string componentType, string route) { var activity = ActivitySource.CreateActivity(OnRouteName, ActivityKind.Internal, parentId: null, null, null); @@ -38,10 +31,6 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string var previousActivity = Activity.Current; if (activity.IsAllDataRequested) { - if (_circuitId != null) - { - activity.SetTag("aspnetcore.components.circuit.id", _circuitId); - } if (componentType != null) { activity.SetTag("aspnetcore.components.type", componentType); @@ -54,17 +43,25 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string { activity.AddLink(new ActivityLink(previousActivity.Context)); } + + // store the link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Route, activity.Context, + new KeyValuePair("aspnetcore.components.route", route)); } activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; Activity.Current = null; // do not inherit the parent activity activity.Start(); - _routeContext = activity.Context; return new ComponentsActivityHandle { Activity = activity, Previous = previousActivity }; } return default; } + public void StopRouteActivity(ComponentsActivityHandle activityHandle, Exception? ex) + { + StopComponentActivity(ComponentsActivityCategory.Route, activityHandle, ex); + } + public ComponentsActivityHandle StartEventActivity(string? componentType, string? methodName, string? attributeName) { var activity = ActivitySource.CreateActivity(OnEventName, ActivityKind.Internal, parentId: null, null, null); @@ -72,10 +69,6 @@ public ComponentsActivityHandle StartEventActivity(string? componentType, string { if (activity.IsAllDataRequested) { - if (_circuitId != null) - { - activity.SetTag("aspnetcore.components.circuit.id", _circuitId); - } if (componentType != null) { activity.SetTag("aspnetcore.components.type", componentType); @@ -99,35 +92,38 @@ public ComponentsActivityHandle StartEventActivity(string? componentType, string return default; } - public void StopComponentActivity(ComponentsActivityHandle activityHandle, Exception? ex) + public void StopEventActivity(ComponentsActivityHandle activityHandle, Exception? ex) + { + StopComponentActivity(ComponentsActivityCategory.Event, activityHandle, ex); + } + + public async Task CaptureEventStopAsync(Task task, ComponentsActivityHandle activityHandle) + { + try + { + await task; + StopEventActivity(activityHandle, null); + } + catch (Exception ex) + { + StopEventActivity(activityHandle, ex); + } + } + + private void StopComponentActivity(int category, ComponentsActivityHandle activityHandle, Exception? ex) { var activity = activityHandle.Activity; if (activity != null && !activity.IsStopped) { - if (activity.IsAllDataRequested) - { - if (_circuitId != null) - { - activity.SetTag("aspnetcore.components.circuit.id", _circuitId); - } - if (_httpActivityContext != default) - { - activity.AddLink(new ActivityLink(_httpActivityContext)); - } - if (_circuitActivityContext != default) - { - activity.AddLink(new ActivityLink(_circuitActivityContext)); - } - if (_routeContext != default && activity.Context != _routeContext) - { - activity.AddLink(new ActivityLink(_routeContext)); - } - } if (ex != null) { activity.SetTag("error.type", ex.GetType().FullName); activity.SetStatus(ActivityStatusCode.Error); } + if (activity.IsAllDataRequested) + { + _activityLinkStore.AddActivityContexts(category, activity); + } activity.Stop(); if (Activity.Current == null && activityHandle.Previous != null && !activityHandle.Previous.IsStopped) @@ -136,17 +132,13 @@ public void StopComponentActivity(ComponentsActivityHandle activityHandle, Excep } } } +} - public async Task CaptureEventStopAsync(Task task, ComponentsActivityHandle activityHandle) - { - try - { - await task; - StopComponentActivity(activityHandle, null); - } - catch (Exception ex) - { - StopComponentActivity(activityHandle, ex); - } - } +/// +/// Named tuple for restoring the previous activity after stopping the current one. +/// +internal struct ComponentsActivityHandle +{ + public Activity? Previous; + public Activity? Activity; } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 5eb52d2c330e..871791615595 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,4 +1,12 @@ #nullable enable +Microsoft.AspNetCore.Components.IComponentsActivityLinkStore +Microsoft.AspNetCore.Components.IComponentsActivityLinkStore.AddActivityContexts(int exceptCategory, System.Diagnostics.Activity! targetActivity) -> void +Microsoft.AspNetCore.Components.IComponentsActivityLinkStore.SetActivityContext(int category, System.Diagnostics.ActivityContext activityLink, System.Collections.Generic.KeyValuePair? tag) -> void +Microsoft.AspNetCore.Components.ComponentsActivityCategory +const Microsoft.AspNetCore.Components.ComponentsActivityCategory.Http = 0 -> int +const Microsoft.AspNetCore.Components.ComponentsActivityCategory.SignalR = 1 -> int +const Microsoft.AspNetCore.Components.ComponentsActivityCategory.Route = 2 -> int +const Microsoft.AspNetCore.Components.ComponentsActivityCategory.Circuit = 3 -> int Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type! Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.set -> void Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions diff --git a/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs b/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs index e1e224e5f71b..92fe8706adb5 100644 --- a/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs +++ b/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs @@ -39,6 +39,7 @@ public static IServiceCollection AddComponentsTracing( IServiceCollection services) { services.TryAddScoped(); + services.TryAddScoped(); return services; } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index d8746424f073..b520916dfdeb 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -115,18 +115,6 @@ private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvid ?? new DefaultComponentActivator(serviceProvider); } - internal ActivityContext LinkActivityContexts(ActivityContext httpActivityContext, ActivityContext circuitActivityContext, string? circuitId) - { - if (ComponentActivitySource != null) - { - ComponentActivitySource._httpActivityContext = httpActivityContext; - ComponentActivitySource._circuitActivityContext = circuitActivityContext; - ComponentActivitySource._circuitId = circuitId; - return ComponentActivitySource._routeContext; - } - return default; - } - /// /// Gets the associated with this . /// @@ -538,7 +526,7 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie if (ComponentActivitySource != null && activityHandle.Activity != null) { - ComponentActivitySource.StopComponentActivity(activityHandle, e); + ComponentActivitySource.StopEventActivity(activityHandle, e); } HandleExceptionViaErrorBoundary(e, receiverComponentState); diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index 7ec4e028a0e5..7a73ead53ea9 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -240,7 +240,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) endpointRouteData = RouteTable.ProcessParameters(endpointRouteData); _renderHandle.Render(Found(endpointRouteData)); - _renderHandle.ComponentActivitySource?.StopComponentActivity(activityHandle, null); + _renderHandle.ComponentActivitySource?.StopRouteActivity(activityHandle, null); return; } @@ -295,15 +295,15 @@ internal virtual void Refresh(bool isNavigationIntercepted) NavigationManager.NavigateTo(_locationAbsolute, forceLoad: true); } } - _renderHandle.ComponentActivitySource?.StopComponentActivity(activityHandle, null); + _renderHandle.ComponentActivitySource?.StopRouteActivity(activityHandle, null); } private ComponentsActivityHandle RecordDiagnostics(string componentType, string template) { - ComponentsActivityHandle activityWrapper = default; + ComponentsActivityHandle activityHandle = default; if (_renderHandle.ComponentActivitySource != null) { - activityWrapper = _renderHandle.ComponentActivitySource.StartRouteActivity(componentType, template); + activityHandle = _renderHandle.ComponentActivitySource.StartRouteActivity(componentType, template); } if (_renderHandle.ComponentMetrics != null && _renderHandle.ComponentMetrics.IsNavigationEnabled) @@ -311,7 +311,7 @@ private ComponentsActivityHandle RecordDiagnostics(string componentType, string _renderHandle.ComponentMetrics.Navigation(componentType, template); } - return activityWrapper; + return activityHandle; } private static void DefaultNotFoundContent(RenderTreeBuilder builder) diff --git a/src/Components/Components/test/ComponentsActivitySourceTest.cs b/src/Components/Components/test/ComponentsActivitySourceTest.cs index d3bae74722af..277e67a45d9f 100644 --- a/src/Components/Components/test/ComponentsActivitySourceTest.cs +++ b/src/Components/Components/test/ComponentsActivitySourceTest.cs @@ -27,7 +27,7 @@ public ComponentsActivitySourceTest() public void Constructor_CreatesActivitySourceCorrectly() { // Arrange & Act - var componentsActivitySource = new ComponentsActivitySource(); + var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); // Assert Assert.NotNull(componentsActivitySource); @@ -37,16 +37,17 @@ public void Constructor_CreatesActivitySourceCorrectly() public void StartRouteActivity_CreatesAndStartsActivity() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(); + var componentsActivitySource = new ComponentsActivitySource(linkstore); var componentType = "TestComponent"; var route = "/test-route"; // First set up a circuit context - componentsActivitySource._circuitId = "test-circuit-id"; + linkstore.SetActivityContext(ComponentsActivityCategory.Circuit, new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), new KeyValuePair("aspnetcore.components.circuit.id", "test-circuit-id")); // Act - var wrapper = componentsActivitySource.StartRouteActivity(componentType, route); - var activity = wrapper.Activity; + var activityHandle = componentsActivitySource.StartRouteActivity(componentType, route); + var activity = activityHandle.Activity; // Assert Assert.NotNull(activity); @@ -56,26 +57,32 @@ public void StartRouteActivity_CreatesAndStartsActivity() Assert.True(activity.IsAllDataRequested); Assert.Equal(componentType, activity.GetTagItem("aspnetcore.components.type")); Assert.Equal(route, activity.GetTagItem("aspnetcore.components.route")); - Assert.Equal("test-circuit-id", activity.GetTagItem("aspnetcore.components.circuit.id")); Assert.False(activity.IsStopped); + + componentsActivitySource.StopRouteActivity(activityHandle, null); + Assert.True(activity.IsStopped); + Assert.Equal("test-circuit-id", activity.GetTagItem("aspnetcore.components.circuit.id")); + Assert.Single(activity.Links); + } [Fact] public void StartEventActivity_CreatesAndStartsActivity() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(); + var componentsActivitySource = new ComponentsActivitySource(linkstore); var componentType = "TestComponent"; var methodName = "OnClick"; var attributeName = "onclick"; // First set up a circuit and route context - componentsActivitySource._circuitId = "test-circuit-id"; + linkstore.SetActivityContext(ComponentsActivityCategory.Circuit, default, new KeyValuePair("aspnetcore.components.circuit.id", "test-circuit-id")); componentsActivitySource.StartRouteActivity("ParentComponent", "/parent"); // Act - var wrapper = componentsActivitySource.StartEventActivity(componentType, methodName, attributeName); - var activity = wrapper.Activity; + var activityHandle = componentsActivitySource.StartEventActivity(componentType, methodName, attributeName); + var activity = activityHandle.Activity; // Assert Assert.NotNull(activity); @@ -86,21 +93,25 @@ public void StartEventActivity_CreatesAndStartsActivity() Assert.Equal(componentType, activity.GetTagItem("aspnetcore.components.type")); Assert.Equal(methodName, activity.GetTagItem("aspnetcore.components.method")); Assert.Equal(attributeName, activity.GetTagItem("aspnetcore.components.attribute.name")); - Assert.Equal("test-circuit-id", activity.GetTagItem("aspnetcore.components.circuit.id")); Assert.False(activity.IsStopped); + + componentsActivitySource.StopRouteActivity(activityHandle, null); + Assert.True(activity.IsStopped); + Assert.Equal("test-circuit-id", activity.GetTagItem("aspnetcore.components.circuit.id")); + Assert.Empty(activity.Links); } [Fact] public void FailEventActivity_SetsErrorStatusAndStopsActivity() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); + var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); // Act - componentsActivitySource.StopComponentActivity(activityHandle, exception); + componentsActivitySource.StopEventActivity(activityHandle, exception); // Assert Assert.True(activity!.IsStopped); @@ -112,13 +123,13 @@ public void FailEventActivity_SetsErrorStatusAndStopsActivity() public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); - var wrapper = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); - var activity = wrapper.Activity; + var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var activity = activityHandle.Activity; var task = Task.CompletedTask; // Act - await componentsActivitySource.CaptureEventStopAsync(task, wrapper); + await componentsActivitySource.CaptureEventStopAsync(task, activityHandle); // Assert Assert.True(activity!.IsStopped); @@ -129,14 +140,14 @@ public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() public async Task CaptureEventStopAsync_FailsActivityOnException() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); - var wrapper = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); - var activity = wrapper.Activity; + var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); var task = Task.FromException(exception); // Act - await componentsActivitySource.CaptureEventStopAsync(task, wrapper); + await componentsActivitySource.CaptureEventStopAsync(task, activityHandle); // Assert Assert.True(activity!.IsStopped); @@ -148,11 +159,11 @@ public async Task CaptureEventStopAsync_FailsActivityOnException() public void StartRouteActivity_HandlesNullValues() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); + var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); // Act - var wrapper = componentsActivitySource.StartRouteActivity(null, null); - var activity = wrapper.Activity; + var activityHandle = componentsActivitySource.StartRouteActivity(null, null); + var activity = activityHandle.Activity; // Assert Assert.NotNull(activity); @@ -163,11 +174,11 @@ public void StartRouteActivity_HandlesNullValues() public void StartEventActivity_HandlesNullValues() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(); + var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); // Act - var wrapper = componentsActivitySource.StartEventActivity(null, null, null); - var activity = wrapper.Activity; + var activityHandle = componentsActivitySource.StartEventActivity(null, null, null); + var activity = activityHandle.Activity; // Assert Assert.NotNull(activity); diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index f0f8264328da..b04d1817db64 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -23,10 +23,12 @@ internal partial class RazorComponentEndpointInvoker : IRazorComponentEndpointIn { private readonly EndpointHtmlRenderer _renderer; private readonly ILogger _logger; + private readonly IComponentsActivityLinkStore _activityLinkStore; - public RazorComponentEndpointInvoker(EndpointHtmlRenderer renderer, ILogger logger) + public RazorComponentEndpointInvoker(EndpointHtmlRenderer renderer, IComponentsActivityLinkStore activityLinkStore, ILogger logger) { _renderer = renderer; + _activityLinkStore = activityLinkStore; _logger = logger; } @@ -85,7 +87,7 @@ private async Task RenderComponentCore(HttpContext context) if (httpActivityContext != default) { - LinkActivityContexts(_renderer, default, httpActivityContext, null); + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivityContext, null); } await _renderer.InitializeStandardComponentServicesAsync( @@ -281,9 +283,6 @@ private async Task ValidateRequestAsync(HttpContext cont return null; } - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "LinkActivityContexts")] - static extern ActivityContext LinkActivityContexts(Renderer type, ActivityContext httpContext, ActivityContext circuitContext, string? circuitId); - [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] private readonly struct RequestValidationState(bool isValid, bool isPost, string? handlerName) { diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index e2d3f03bcebe..f7808e244643 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -3,71 +3,78 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.RenderTree; - -/// -/// Named tuple for restoring the previous activity after stopping the current one. -/// -internal struct CircuitActivityHandle -{ - public Activity? Previous; - public Activity? Activity; -} +using Microsoft.AspNetCore.Routing; internal class CircuitActivitySource { internal const string Name = "Microsoft.AspNetCore.Components.Server.Circuits"; internal const string OnCircuitName = $"{Name}.CircuitStart"; + private readonly IComponentsActivityLinkStore _activityLinkStore; + private ActivitySource ActivitySource { get; } = new ActivitySource(Name); + public CircuitActivitySource(IComponentsActivityLinkStore activityLinkStore) + { + _activityLinkStore = activityLinkStore ?? throw new ArgumentNullException(nameof(activityLinkStore)); + } + public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityContext httpActivityContext, Renderer? renderer) { var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId:null, null, null); if (activity is not null) { - var previousActivity = Activity.Current; + var signalRActivity = Activity.Current; if (activity.IsAllDataRequested) { if (circuitId != null) { activity.SetTag("aspnetcore.components.circuit.id", circuitId); + + // store the circuit link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Route, activity.Context, + new KeyValuePair("aspnetcore.components.circuit.id", circuitId)); } if (httpActivityContext != default) { activity.AddLink(new ActivityLink(httpActivityContext)); + + // store the http link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivityContext, null); } - if (previousActivity != null) + if (signalRActivity != null) { - activity.AddLink(new ActivityLink(previousActivity.Context)); + activity.AddLink(new ActivityLink(signalRActivity.Context)); + + // store the SignalR link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.SignalR, signalRActivity.Context, null); } } activity.DisplayName = $"Circuit {circuitId ?? ""}"; Activity.Current = null; // do not inherit the parent activity activity.Start(); - - if (renderer != null) - { - var routeActivityContext = LinkActivityContexts(renderer, httpActivityContext, activity.Context, circuitId); - if (routeActivityContext != default) - { - activity.AddLink(new ActivityLink(routeActivityContext)); - } - } - return new CircuitActivityHandle { Previous = previousActivity, Activity = activity }; + return new CircuitActivityHandle { Previous = signalRActivity, Activity = activity }; } return default; } - public static void StopCircuitActivity(CircuitActivityHandle activityHandle, Exception? ex) + public void StopCircuitActivity(CircuitActivityHandle activityHandle, Exception? ex) { - if (activityHandle.Activity != null && !activityHandle.Activity.IsStopped) + var activity = activityHandle.Activity; + if (activity != null && !activity.IsStopped) { if (ex != null) { - activityHandle.Activity.SetTag("error.type", ex.GetType().FullName); - activityHandle.Activity.SetStatus(ActivityStatusCode.Error); + activity.SetTag("error.type", ex.GetType().FullName); + activity.SetStatus(ActivityStatusCode.Error); + } + if (activity.IsAllDataRequested) + { + // ComponentsActivityCategory.Circuit = 5; + _activityLinkStore.AddActivityContexts(5, activity); } activityHandle.Activity.Stop(); @@ -77,7 +84,13 @@ public static void StopCircuitActivity(CircuitActivityHandle activityHandle, Exc } } } +} - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "LinkActivityContexts")] - static extern ActivityContext LinkActivityContexts(Renderer type, ActivityContext httpContext, ActivityContext circuitContext, string? circuitId); +/// +/// Named tuple for restoring the previous activity after stopping the current one. +/// +internal struct CircuitActivityHandle +{ + public Activity? Previous; + public Activity? Activity; } diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index b17612f47251..fef298ffb4f7 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -171,11 +171,11 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, A Log.InitializationSucceeded(_logger); - CircuitActivitySource.StopCircuitActivity(activityHandle, null); + _circuitActivitySource.StopCircuitActivity(activityHandle, null); } catch (Exception ex) { - CircuitActivitySource.StopCircuitActivity(activityHandle, ex); + _circuitActivitySource.StopCircuitActivity(activityHandle, ex); // Report errors asynchronously. InitializeAsync is designed not to throw. Log.InitializationFailed(_logger, ex); diff --git a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs index 8e7662b1712b..c02751a32fef 100644 --- a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs +++ b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs @@ -89,7 +89,7 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti services.TryAddEnumerable(ServiceDescriptor.Singleton, CircuitOptionsJSInteropDetailedErrorsConfiguration>()); services.TryAddEnumerable(ServiceDescriptor.Singleton, CircuitOptionsJavaScriptInitializersConfiguration>()); - services.TryAddSingleton(); + services.TryAddScoped(); if (configure != null) { diff --git a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs index 256a2e4a052b..4c07f91313b4 100644 --- a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs +++ b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs @@ -36,7 +36,7 @@ public CircuitActivitySourceTest() public void StartCircuitActivity_CreatesAndStartsActivity() { // Arrange - var circuitActivitySource = new CircuitActivitySource(); + var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); var circuitId = "test-circuit-id"; var httpContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); @@ -59,7 +59,7 @@ public void StartCircuitActivity_CreatesAndStartsActivity() public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() { // Arrange - var circuitActivitySource = new CircuitActivitySource(); + var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); var circuitId = "test-circuit-id"; var httpContext = default(ActivityContext); var activityHandle = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); @@ -67,7 +67,7 @@ public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() var exception = new InvalidOperationException("Test exception"); // Act - CircuitActivitySource.StopCircuitActivity(activityHandle, exception); + circuitActivitySource.StopCircuitActivity(activityHandle, exception); // Assert Assert.True(activity!.IsStopped); @@ -79,7 +79,7 @@ public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() public void StartCircuitActivity_HandlesNullValues() { // Arrange - var circuitActivitySource = new CircuitActivitySource(); + var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); // Act var wrapper = circuitActivitySource.StartCircuitActivity(null, default, null); diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index 9852cab9a82f..5410b5794ae7 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -39,7 +39,8 @@ public static CircuitHost Create( .Returns(jsRuntime); var serverComponentDeserializer = Mock.Of(); var circuitMetrics = new CircuitMetrics(new TestMeterFactory()); - var circuitActivitySource = new CircuitActivitySource(); + var linkstore = new ComponentsActivityLinkStore(); + var circuitActivitySource = new CircuitActivitySource(linkstore); if (remoteRenderer == null) { From 32fd18991c4d09308e02fa861c0a2ff060fc3375 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 16:20:11 +0200 Subject: [PATCH 10/17] ci --- src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index b04d1817db64..966fab183e8d 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery; From ee482de961620a091b1aa4952b5028e1fb0af1d0 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 17:29:43 +0200 Subject: [PATCH 11/17] fixes --- .../src/ComponentsActivityLinkStore.cs | 8 +++-- .../src/ComponentsActivitySource.cs | 34 +++++++++++-------- ...eringMetricsServiceCollectionExtensions.cs | 2 +- .../src/RazorComponentEndpointInvoker.cs | 1 - .../test/RazorComponentEndpointInvokerTest.cs | 1 + .../src/Circuits/CircuitActivitySource.cs | 25 +++++--------- .../Server/src/Circuits/CircuitHost.cs | 2 +- .../Circuits/CircuitActivitySourceTest.cs | 16 +++++---- 8 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/Components/Components/src/ComponentsActivityLinkStore.cs b/src/Components/Components/src/ComponentsActivityLinkStore.cs index a090cb1ea21e..812707157b04 100644 --- a/src/Components/Components/src/ComponentsActivityLinkStore.cs +++ b/src/Components/Components/src/ComponentsActivityLinkStore.cs @@ -73,11 +73,13 @@ public void AddActivityContexts(int exceptCategory, Activity targetActivity) { if (i != exceptCategory) { - if (_activityLinks[i] != default) + var link = _activityLinks[i]; + var tag = _activityTags[i]; + + if (link != default) { - targetActivity.AddLink(new ActivityLink(_activityLinks[i])); + targetActivity.AddLink(new ActivityLink(link)); } - var tag = _activityTags[i]; if (tag != null) { targetActivity.SetTag(tag.Value.Key, tag.Value.Value); diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 1d667741de9e..113afd229233 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -28,7 +28,11 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string var activity = ActivitySource.CreateActivity(OnRouteName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) { - var previousActivity = Activity.Current; + var httpActivity = Activity.Current; + activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; + Activity.Current = null; // do not inherit the parent activity + activity.Start(); + if (activity.IsAllDataRequested) { if (componentType != null) @@ -38,21 +42,19 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string if (route != null) { activity.SetTag("aspnetcore.components.route", route); + + // store self link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Route, activity.Context, + new KeyValuePair("aspnetcore.components.route", route)); } - if (previousActivity != null) + if (httpActivity != null && httpActivity.Source.Name == "Microsoft.AspNetCore") { - activity.AddLink(new ActivityLink(previousActivity.Context)); + // store the http link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivity.Context, null); } - - // store the link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Route, activity.Context, - new KeyValuePair("aspnetcore.components.route", route)); } - activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; - Activity.Current = null; // do not inherit the parent activity - activity.Start(); - return new ComponentsActivityHandle { Activity = activity, Previous = previousActivity }; + return new ComponentsActivityHandle { Activity = activity, Previous = httpActivity }; } return default; } @@ -65,8 +67,14 @@ public void StopRouteActivity(ComponentsActivityHandle activityHandle, Exception public ComponentsActivityHandle StartEventActivity(string? componentType, string? methodName, string? attributeName) { var activity = ActivitySource.CreateActivity(OnEventName, ActivityKind.Internal, parentId: null, null, null); + if (activity is not null) { + var previousActivity = Activity.Current; + activity.DisplayName = $"Event {attributeName ?? "[unknown attribute]"} -> {componentType ?? "[unknown component]"}.{methodName ?? "[unknown method]"}"; + Activity.Current = null; // do not inherit the parent activity + activity.Start(); + if (activity.IsAllDataRequested) { if (componentType != null) @@ -83,10 +91,6 @@ public ComponentsActivityHandle StartEventActivity(string? componentType, string } } - activity.DisplayName = $"Event {attributeName ?? "[unknown attribute]"} -> {componentType ?? "[unknown component]"}.{methodName ?? "[unknown method]"}"; - var previousActivity = Activity.Current; - Activity.Current = null; // do not inherit the parent activity - activity.Start(); return new ComponentsActivityHandle { Activity = activity, Previous = previousActivity }; } return default; diff --git a/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs b/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs index 92fe8706adb5..fb299e2f63b6 100644 --- a/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs +++ b/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs @@ -38,8 +38,8 @@ public static IServiceCollection AddComponentsMetrics( public static IServiceCollection AddComponentsTracing( IServiceCollection services) { - services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); return services; } diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 966fab183e8d..788095297945 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -7,7 +7,6 @@ using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Components.Endpoints.Rendering; -using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; diff --git a/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs b/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs index 88dff7565185..0c2331df7182 100644 --- a/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs +++ b/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs @@ -28,6 +28,7 @@ public async Task Invoker_RejectsPostRequestsWithNonFormDataContentTypesAsync() new EndpointHtmlRenderer( services, NullLoggerFactory.Instance), + new ComponentsActivityLinkStore(), NullLogger.Instance); var context = new DefaultHttpContext(); diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index f7808e244643..224437d6f7bb 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Routing; internal class CircuitActivitySource { @@ -21,12 +19,15 @@ public CircuitActivitySource(IComponentsActivityLinkStore activityLinkStore) _activityLinkStore = activityLinkStore ?? throw new ArgumentNullException(nameof(activityLinkStore)); } - public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityContext httpActivityContext, Renderer? renderer) + public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityContext httpActivityContext) { - var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId:null, null, null); + var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId: null, null, null); if (activity is not null) { var signalRActivity = Activity.Current; + activity.DisplayName = $"Circuit {circuitId ?? ""}"; + Activity.Current = null; // do not inherit the parent activity + activity.Start(); if (activity.IsAllDataRequested) { @@ -34,28 +35,21 @@ public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityCont { activity.SetTag("aspnetcore.components.circuit.id", circuitId); - // store the circuit link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Route, activity.Context, + // store self link + _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Circuit, activity.Context, new KeyValuePair("aspnetcore.components.circuit.id", circuitId)); } if (httpActivityContext != default) { - activity.AddLink(new ActivityLink(httpActivityContext)); - // store the http link _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivityContext, null); } - if (signalRActivity != null) + if (signalRActivity != null && signalRActivity.Source.Name == "Microsoft.AspNetCore.SignalR.Server") { - activity.AddLink(new ActivityLink(signalRActivity.Context)); - // store the SignalR link _activityLinkStore.SetActivityContext(ComponentsActivityCategory.SignalR, signalRActivity.Context, null); } } - activity.DisplayName = $"Circuit {circuitId ?? ""}"; - Activity.Current = null; // do not inherit the parent activity - activity.Start(); return new CircuitActivityHandle { Previous = signalRActivity, Activity = activity }; } return default; @@ -73,8 +67,7 @@ public void StopCircuitActivity(CircuitActivityHandle activityHandle, Exception? } if (activity.IsAllDataRequested) { - // ComponentsActivityCategory.Circuit = 5; - _activityLinkStore.AddActivityContexts(5, activity); + _activityLinkStore.AddActivityContexts(ComponentsActivityCategory.Circuit, activity); } activityHandle.Activity.Stop(); diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index fef298ffb4f7..9e2fc4a74d31 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -125,7 +125,7 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, A { _initialized = true; // We're ready to accept incoming JSInterop calls from here on - activityHandle = _circuitActivitySource.StartCircuitActivity(CircuitId.Id, httpActivityContext, Renderer); + activityHandle = _circuitActivitySource.StartCircuitActivity(CircuitId.Id, httpActivityContext); _startTime = (_circuitMetrics != null && _circuitMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0; // We only run the handlers in case we are in a Blazor Server scenario, which renders diff --git a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs index 4c07f91313b4..b14bf52c8d2e 100644 --- a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs +++ b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs @@ -41,8 +41,8 @@ public void StartCircuitActivity_CreatesAndStartsActivity() var httpContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); // Act - var wrapper = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); - var activity = wrapper.Activity; + var activityHandle = circuitActivitySource.StartCircuitActivity(circuitId, httpContext); + var activity = activityHandle.Activity; // Assert Assert.NotNull(activity); @@ -51,8 +51,12 @@ public void StartCircuitActivity_CreatesAndStartsActivity() Assert.Equal(ActivityKind.Internal, activity.Kind); Assert.True(activity.IsAllDataRequested); Assert.Equal(circuitId, activity.GetTagItem("aspnetcore.components.circuit.id")); - Assert.Contains(activity.Links, link => link.Context == httpContext); + Assert.Empty(activity.Links); Assert.False(activity.IsStopped); + + circuitActivitySource.StopCircuitActivity(activityHandle, null); + Assert.True(activity.IsStopped); + Assert.Contains(activity.Links, link => link.Context == httpContext); } [Fact] @@ -62,7 +66,7 @@ public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); var circuitId = "test-circuit-id"; var httpContext = default(ActivityContext); - var activityHandle = circuitActivitySource.StartCircuitActivity(circuitId, httpContext, null); + var activityHandle = circuitActivitySource.StartCircuitActivity(circuitId, httpContext); var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); @@ -82,8 +86,8 @@ public void StartCircuitActivity_HandlesNullValues() var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); // Act - var wrapper = circuitActivitySource.StartCircuitActivity(null, default, null); - var activity = wrapper.Activity; + var activityHandle = circuitActivitySource.StartCircuitActivity(null, default); + var activity = activityHandle.Activity; // Assert Assert.NotNull(activity); From 6fd41a5bce49a5ca8749292b463dc48901f67181 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 17:41:49 +0200 Subject: [PATCH 12/17] more --- src/Components/Server/src/Circuits/CircuitActivitySource.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index 224437d6f7bb..c0f23511a818 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.RenderTree; internal class CircuitActivitySource { From eef0f28f189626b2c06dd733646932525050b16d Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 22:15:23 +0200 Subject: [PATCH 13/17] back to UnsafeAccessor, shared file --- .../src/ComponentsActivityLinkStore.cs | 90 ------------------- .../src/ComponentsActivitySource.cs | 20 ++--- .../Microsoft.AspNetCore.Components.csproj | 1 + .../Components/src/PublicAPI.Unshipped.txt | 8 -- ...eringMetricsServiceCollectionExtensions.cs | 1 - .../Components/src/RenderTree/Renderer.cs | 6 ++ .../test/ComponentsActivitySourceTest.cs | 39 +++++--- ...oft.AspNetCore.Components.Endpoints.csproj | 1 + .../src/RazorComponentEndpointInvoker.cs | 9 +- .../test/RazorComponentEndpointInvokerTest.cs | 1 - .../src/Circuits/CircuitActivitySource.cs | 16 ++-- .../Server/src/Circuits/CircuitFactory.cs | 2 + ...rosoft.AspNetCore.Components.Server.csproj | 1 + .../Circuits/CircuitActivitySourceTest.cs | 13 ++- .../Server/test/Circuits/TestCircuitHost.cs | 6 +- ....AspNetCore.Components.Server.Tests.csproj | 2 +- .../Components/ComponentsActivityLinkStore.cs | 66 ++++++++++++++ 17 files changed, 142 insertions(+), 140 deletions(-) delete mode 100644 src/Components/Components/src/ComponentsActivityLinkStore.cs create mode 100644 src/Shared/Components/ComponentsActivityLinkStore.cs diff --git a/src/Components/Components/src/ComponentsActivityLinkStore.cs b/src/Components/Components/src/ComponentsActivityLinkStore.cs deleted file mode 100644 index 812707157b04..000000000000 --- a/src/Components/Components/src/ComponentsActivityLinkStore.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace Microsoft.AspNetCore.Components; - -/// -/// Helper for storing links between diagnostic activities in Blazor components. -/// -public interface IComponentsActivityLinkStore -{ - /// - /// Sets the activity context for a specific category. - /// - /// Category of the trace. - /// Link to the trace. - /// Optional tag metadata. - void SetActivityContext(int category, ActivityContext activityLink, KeyValuePair? tag); - - /// - /// Will add all activity contexts except the one specified by to the . - /// - /// Category of the target trace. - /// Activity to add links to. - void AddActivityContexts(int exceptCategory, Activity targetActivity); -} - -/// -/// Helper for storing links between diagnostic activities in Blazor components. -/// -public static class ComponentsActivityCategory -{ - /// - /// Http trace. - /// - public const int Http = 0; - /// - /// SignalR trace. - /// - public const int SignalR = 1; - /// - /// Route trace. - /// - public const int Route = 2; - /// - /// Circuit trace. - /// - public const int Circuit = 3; - - internal const int COUNT = 4;// keep this one bigger than the last linkable category index - - /// - /// Event trace. - /// - internal const int Event = 5; // not linkable -} - -internal class ComponentsActivityLinkStore : IComponentsActivityLinkStore -{ - private readonly ActivityContext[] _activityLinks = new ActivityContext[ComponentsActivityCategory.COUNT]; - private readonly KeyValuePair?[] _activityTags = new KeyValuePair?[ComponentsActivityCategory.COUNT]; - - public void SetActivityContext(int category, ActivityContext activityLink, KeyValuePair? tag) - { - _activityLinks[category] = activityLink; - _activityTags[category] = tag; - } - - public void AddActivityContexts(int exceptCategory, Activity targetActivity) - { - for (var i = 0; i < ComponentsActivityCategory.COUNT; i++) - { - if (i != exceptCategory) - { - var link = _activityLinks[i]; - var tag = _activityTags[i]; - - if (link != default) - { - targetActivity.AddLink(new ActivityLink(link)); - } - if (tag != null) - { - targetActivity.SetTag(tag.Value.Key, tag.Value.Value); - } - } - } - } -} diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 113afd229233..13d1b97e7c3e 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.AspNetCore.Components.Infrastructure; namespace Microsoft.AspNetCore.Components; @@ -14,13 +15,12 @@ internal class ComponentsActivitySource internal const string OnRouteName = $"{Name}.RouteChange"; internal const string OnEventName = $"{Name}.HandleEvent"; - private readonly IComponentsActivityLinkStore _activityLinkStore; - private ActivitySource ActivitySource { get; } = new ActivitySource(Name); + private ComponentsActivityLinkStore? _componentsActivityLinkStore; - public ComponentsActivitySource(IComponentsActivityLinkStore activityLinkStore) + public void Init(ComponentsActivityLinkStore store) { - _activityLinkStore = activityLinkStore ?? throw new ArgumentNullException(nameof(activityLinkStore)); + _componentsActivityLinkStore = store; } public ComponentsActivityHandle StartRouteActivity(string componentType, string route) @@ -44,13 +44,13 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string activity.SetTag("aspnetcore.components.route", route); // store self link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Route, activity.Context, + _componentsActivityLinkStore!.SetActivityContext(ComponentsActivityLinkStore.Route, activity.Context, new KeyValuePair("aspnetcore.components.route", route)); } if (httpActivity != null && httpActivity.Source.Name == "Microsoft.AspNetCore") { // store the http link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivity.Context, null); + _componentsActivityLinkStore!.SetActivityContext(ComponentsActivityLinkStore.Http, httpActivity.Context, null); } } @@ -61,7 +61,7 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string public void StopRouteActivity(ComponentsActivityHandle activityHandle, Exception? ex) { - StopComponentActivity(ComponentsActivityCategory.Route, activityHandle, ex); + StopComponentActivity(ComponentsActivityLinkStore.Route, activityHandle, ex); } public ComponentsActivityHandle StartEventActivity(string? componentType, string? methodName, string? attributeName) @@ -98,7 +98,7 @@ public ComponentsActivityHandle StartEventActivity(string? componentType, string public void StopEventActivity(ComponentsActivityHandle activityHandle, Exception? ex) { - StopComponentActivity(ComponentsActivityCategory.Event, activityHandle, ex); + StopComponentActivity(ComponentsActivityLinkStore.Event, activityHandle, ex); } public async Task CaptureEventStopAsync(Task task, ComponentsActivityHandle activityHandle) @@ -114,7 +114,7 @@ public async Task CaptureEventStopAsync(Task task, ComponentsActivityHandle acti } } - private void StopComponentActivity(int category, ComponentsActivityHandle activityHandle, Exception? ex) + private void StopComponentActivity(string category, ComponentsActivityHandle activityHandle, Exception? ex) { var activity = activityHandle.Activity; if (activity != null && !activity.IsStopped) @@ -126,7 +126,7 @@ private void StopComponentActivity(int category, ComponentsActivityHandle activi } if (activity.IsAllDataRequested) { - _activityLinkStore.AddActivityContexts(category, activity); + _componentsActivityLinkStore!.AddActivityContexts(category, activity); } activity.Stop(); diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index ca3286f8b6c2..ea4689586d69 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 871791615595..5eb52d2c330e 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,12 +1,4 @@ #nullable enable -Microsoft.AspNetCore.Components.IComponentsActivityLinkStore -Microsoft.AspNetCore.Components.IComponentsActivityLinkStore.AddActivityContexts(int exceptCategory, System.Diagnostics.Activity! targetActivity) -> void -Microsoft.AspNetCore.Components.IComponentsActivityLinkStore.SetActivityContext(int category, System.Diagnostics.ActivityContext activityLink, System.Collections.Generic.KeyValuePair? tag) -> void -Microsoft.AspNetCore.Components.ComponentsActivityCategory -const Microsoft.AspNetCore.Components.ComponentsActivityCategory.Http = 0 -> int -const Microsoft.AspNetCore.Components.ComponentsActivityCategory.SignalR = 1 -> int -const Microsoft.AspNetCore.Components.ComponentsActivityCategory.Route = 2 -> int -const Microsoft.AspNetCore.Components.ComponentsActivityCategory.Circuit = 3 -> int Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type! Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.set -> void Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions diff --git a/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs b/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs index fb299e2f63b6..e1e224e5f71b 100644 --- a/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs +++ b/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs @@ -38,7 +38,6 @@ public static IServiceCollection AddComponentsMetrics( public static IServiceCollection AddComponentsTracing( IServiceCollection services) { - services.TryAddScoped(); services.TryAddScoped(); return services; diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index b520916dfdeb..7cd799b5ba32 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Components.HotReload; +using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.Components.Reflection; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.DependencyInjection; @@ -15,6 +16,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree; +using CategoryLink = Tuple?>; + /// /// Types in the Microsoft.AspNetCore.Components.RenderTree are not recommended for use outside /// of the Blazor framework. These types will change in a future release. @@ -37,6 +40,8 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable private readonly ComponentFactory _componentFactory; private readonly ComponentsMetrics? _componentsMetrics; private readonly ComponentsActivitySource? _componentsActivitySource; + private readonly object _activityLinksStore = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal object ActivityLinksStore => _activityLinksStore; private Dictionary? _rootComponentsLatestParameters; private Task? _ongoingQuiescenceTask; @@ -96,6 +101,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, _componentFactory = new ComponentFactory(componentActivator, this); _componentsMetrics = serviceProvider.GetService(); _componentsActivitySource = serviceProvider.GetService(); + _componentsActivitySource.Init(new ComponentsActivityLinkStore(this)); ServiceProviderCascadingValueSuppliers = serviceProvider.GetService() is null ? Array.Empty() diff --git a/src/Components/Components/test/ComponentsActivitySourceTest.cs b/src/Components/Components/test/ComponentsActivitySourceTest.cs index 277e67a45d9f..f51b5fac4f89 100644 --- a/src/Components/Components/test/ComponentsActivitySourceTest.cs +++ b/src/Components/Components/test/ComponentsActivitySourceTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.AspNetCore.Components.Infrastructure; namespace Microsoft.AspNetCore.Components; @@ -27,7 +28,9 @@ public ComponentsActivitySourceTest() public void Constructor_CreatesActivitySourceCorrectly() { // Arrange & Act - var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); // Assert Assert.NotNull(componentsActivitySource); @@ -37,13 +40,14 @@ public void Constructor_CreatesActivitySourceCorrectly() public void StartRouteActivity_CreatesAndStartsActivity() { // Arrange - var linkstore = new ComponentsActivityLinkStore(); - var componentsActivitySource = new ComponentsActivitySource(linkstore); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); var componentType = "TestComponent"; var route = "/test-route"; // First set up a circuit context - linkstore.SetActivityContext(ComponentsActivityCategory.Circuit, new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), new KeyValuePair("aspnetcore.components.circuit.id", "test-circuit-id")); + linkstore.SetActivityContext(ComponentsActivityLinkStore.Circuit, new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), new KeyValuePair("aspnetcore.components.circuit.id", "test-circuit-id")); // Act var activityHandle = componentsActivitySource.StartRouteActivity(componentType, route); @@ -70,14 +74,15 @@ public void StartRouteActivity_CreatesAndStartsActivity() public void StartEventActivity_CreatesAndStartsActivity() { // Arrange - var linkstore = new ComponentsActivityLinkStore(); - var componentsActivitySource = new ComponentsActivitySource(linkstore); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); var componentType = "TestComponent"; var methodName = "OnClick"; var attributeName = "onclick"; // First set up a circuit and route context - linkstore.SetActivityContext(ComponentsActivityCategory.Circuit, default, new KeyValuePair("aspnetcore.components.circuit.id", "test-circuit-id")); + linkstore.SetActivityContext(ComponentsActivityLinkStore.Circuit, default, new KeyValuePair("aspnetcore.components.circuit.id", "test-circuit-id")); componentsActivitySource.StartRouteActivity("ParentComponent", "/parent"); // Act @@ -105,7 +110,9 @@ public void StartEventActivity_CreatesAndStartsActivity() public void FailEventActivity_SetsErrorStatusAndStopsActivity() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); @@ -123,7 +130,9 @@ public void FailEventActivity_SetsErrorStatusAndStopsActivity() public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); var activity = activityHandle.Activity; var task = Task.CompletedTask; @@ -140,7 +149,9 @@ public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() public async Task CaptureEventStopAsync_FailsActivityOnException() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); var activityHandle = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); var activity = activityHandle.Activity; var exception = new InvalidOperationException("Test exception"); @@ -159,7 +170,9 @@ public async Task CaptureEventStopAsync_FailsActivityOnException() public void StartRouteActivity_HandlesNullValues() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); // Act var activityHandle = componentsActivitySource.StartRouteActivity(null, null); @@ -174,7 +187,9 @@ public void StartRouteActivity_HandlesNullValues() public void StartEventActivity_HandlesNullValues() { // Arrange - var componentsActivitySource = new ComponentsActivitySource(new ComponentsActivityLinkStore()); + var componentsActivitySource = new ComponentsActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + componentsActivitySource.Init(linkstore); // Act var activityHandle = componentsActivitySource.StartEventActivity(null, null, null); diff --git a/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj b/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj index 4fa9814ea77e..08a2162a4957 100644 --- a/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj +++ b/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 788095297945..dff81401577b 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -7,6 +7,7 @@ using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Components.Endpoints.Rendering; +using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -21,12 +22,12 @@ internal partial class RazorComponentEndpointInvoker : IRazorComponentEndpointIn { private readonly EndpointHtmlRenderer _renderer; private readonly ILogger _logger; - private readonly IComponentsActivityLinkStore _activityLinkStore; + private readonly ComponentsActivityLinkStore _activityLinkStore; - public RazorComponentEndpointInvoker(EndpointHtmlRenderer renderer, IComponentsActivityLinkStore activityLinkStore, ILogger logger) + public RazorComponentEndpointInvoker(EndpointHtmlRenderer renderer, ILogger logger) { _renderer = renderer; - _activityLinkStore = activityLinkStore; + _activityLinkStore = new ComponentsActivityLinkStore(renderer); _logger = logger; } @@ -85,7 +86,7 @@ private async Task RenderComponentCore(HttpContext context) if (httpActivityContext != default) { - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivityContext, null); + _activityLinkStore.SetActivityContext(ComponentsActivityLinkStore.Http, httpActivityContext, null); } await _renderer.InitializeStandardComponentServicesAsync( diff --git a/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs b/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs index 0c2331df7182..88dff7565185 100644 --- a/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs +++ b/src/Components/Endpoints/test/RazorComponentEndpointInvokerTest.cs @@ -28,7 +28,6 @@ public async Task Invoker_RejectsPostRequestsWithNonFormDataContentTypesAsync() new EndpointHtmlRenderer( services, NullLoggerFactory.Instance), - new ComponentsActivityLinkStore(), NullLogger.Instance); var context = new DefaultHttpContext(); diff --git a/src/Components/Server/src/Circuits/CircuitActivitySource.cs b/src/Components/Server/src/Circuits/CircuitActivitySource.cs index c0f23511a818..ad554bad2e7c 100644 --- a/src/Components/Server/src/Circuits/CircuitActivitySource.cs +++ b/src/Components/Server/src/Circuits/CircuitActivitySource.cs @@ -2,20 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Infrastructure.Server; internal class CircuitActivitySource { internal const string Name = "Microsoft.AspNetCore.Components.Server.Circuits"; internal const string OnCircuitName = $"{Name}.CircuitStart"; - private readonly IComponentsActivityLinkStore _activityLinkStore; + private ComponentsActivityLinkStore? _activityLinkStore; private ActivitySource ActivitySource { get; } = new ActivitySource(Name); - public CircuitActivitySource(IComponentsActivityLinkStore activityLinkStore) + public void Init(ComponentsActivityLinkStore store) { - _activityLinkStore = activityLinkStore ?? throw new ArgumentNullException(nameof(activityLinkStore)); + _activityLinkStore = store; } public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityContext httpActivityContext) @@ -35,18 +35,18 @@ public CircuitActivityHandle StartCircuitActivity(string circuitId, ActivityCont activity.SetTag("aspnetcore.components.circuit.id", circuitId); // store self link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Circuit, activity.Context, + _activityLinkStore.SetActivityContext(ComponentsActivityLinkStore.Circuit, activity.Context, new KeyValuePair("aspnetcore.components.circuit.id", circuitId)); } if (httpActivityContext != default) { // store the http link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.Http, httpActivityContext, null); + _activityLinkStore.SetActivityContext(ComponentsActivityLinkStore.Http, httpActivityContext, null); } if (signalRActivity != null && signalRActivity.Source.Name == "Microsoft.AspNetCore.SignalR.Server") { // store the SignalR link - _activityLinkStore.SetActivityContext(ComponentsActivityCategory.SignalR, signalRActivity.Context, null); + _activityLinkStore.SetActivityContext(ComponentsActivityLinkStore.SignalR, signalRActivity.Context, null); } } return new CircuitActivityHandle { Previous = signalRActivity, Activity = activity }; @@ -66,7 +66,7 @@ public void StopCircuitActivity(CircuitActivityHandle activityHandle, Exception? } if (activity.IsAllDataRequested) { - _activityLinkStore.AddActivityContexts(ComponentsActivityCategory.Circuit, activity); + _activityLinkStore.AddActivityContexts(ComponentsActivityLinkStore.Circuit, activity); } activityHandle.Activity.Stop(); diff --git a/src/Components/Server/src/Circuits/CircuitFactory.cs b/src/Components/Server/src/Circuits/CircuitFactory.cs index 010b8f9050e2..9801ae5715c1 100644 --- a/src/Components/Server/src/Circuits/CircuitFactory.cs +++ b/src/Components/Server/src/Circuits/CircuitFactory.cs @@ -92,6 +92,8 @@ public async ValueTask CreateCircuitHostAsync( jsComponentInterop, resourceCollection); + circuitActivitySource.Init(new Infrastructure.Server.ComponentsActivityLinkStore(renderer)); + // In Blazor Server we have already restored the app state, so we can get the handlers from DI. // In Blazor Web the state is provided in the first call to UpdateRootComponents, so we need to // delay creating the handlers until then. Otherwise, a handler would be able to access the state diff --git a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj index ed27d422aca6..2bba3603e2e1 100644 --- a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj +++ b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs index b14bf52c8d2e..292eab307959 100644 --- a/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs +++ b/src/Components/Server/test/Circuits/CircuitActivitySourceTest.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.InternalTesting; using Moq; +using Microsoft.AspNetCore.Components.Infrastructure.Server; namespace Microsoft.AspNetCore.Components.Server.Circuits; @@ -36,7 +37,9 @@ public CircuitActivitySourceTest() public void StartCircuitActivity_CreatesAndStartsActivity() { // Arrange - var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); + var circuitActivitySource = new CircuitActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + circuitActivitySource.Init(linkstore); var circuitId = "test-circuit-id"; var httpContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); @@ -63,7 +66,9 @@ public void StartCircuitActivity_CreatesAndStartsActivity() public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() { // Arrange - var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); + var circuitActivitySource = new CircuitActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + circuitActivitySource.Init(linkstore); var circuitId = "test-circuit-id"; var httpContext = default(ActivityContext); var activityHandle = circuitActivitySource.StartCircuitActivity(circuitId, httpContext); @@ -83,7 +88,9 @@ public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() public void StartCircuitActivity_HandlesNullValues() { // Arrange - var circuitActivitySource = new CircuitActivitySource(new ComponentsActivityLinkStore()); + var circuitActivitySource = new CircuitActivitySource(); + var linkstore = new ComponentsActivityLinkStore(null); + circuitActivitySource.Init(linkstore); // Act var activityHandle = circuitActivitySource.StartCircuitActivity(null, default); diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index 5410b5794ae7..7c10d66a80b0 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; @@ -39,8 +40,7 @@ public static CircuitHost Create( .Returns(jsRuntime); var serverComponentDeserializer = Mock.Of(); var circuitMetrics = new CircuitMetrics(new TestMeterFactory()); - var linkstore = new ComponentsActivityLinkStore(); - var circuitActivitySource = new CircuitActivitySource(linkstore); + var circuitActivitySource = new CircuitActivitySource(); if (remoteRenderer == null) { @@ -54,6 +54,8 @@ public static CircuitHost Create( jsRuntime, new CircuitJSComponentInterop(new CircuitOptions())); } + var linkstore = new Infrastructure.Server.ComponentsActivityLinkStore(remoteRenderer); + circuitActivitySource.Init(linkstore); handlers ??= Array.Empty(); return new TestCircuitHost( diff --git a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj index db46700aed1b..3eea3a0b0bef 100644 --- a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj +++ b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) diff --git a/src/Shared/Components/ComponentsActivityLinkStore.cs b/src/Shared/Components/ComponentsActivityLinkStore.cs new file mode 100644 index 000000000000..7ada7041a949 --- /dev/null +++ b/src/Shared/Components/ComponentsActivityLinkStore.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Components.RenderTree; + +// this internal helper class is used in both Components and Components.Server projects as a different type +// the namespace is different to avoid conflicts with internalVisibleTo in unit tests +#if COMPONENTS +namespace Microsoft.AspNetCore.Components.Infrastructure; +#else +namespace Microsoft.AspNetCore.Components.Infrastructure.Server; +#endif + +using CategoryLink = Tuple?>; + +/// +/// Helper for storing links between diagnostic activities in Blazor components. +/// +internal class ComponentsActivityLinkStore +{ + public const string Http = "Http"; + public const string SignalR = "SignalR"; + public const string Route = "Route"; + public const string Circuit = "Circuit"; + public const string Event = "Event"; + + private readonly Dictionary _store; + + public ComponentsActivityLinkStore(Renderer? instance) + { + _store = instance == null + ? new Dictionary(StringComparer.OrdinalIgnoreCase) + : (Dictionary)GetActivityLinksStore(instance); + Debug.Assert(_store != null, "Activity links store should not be null."); + } + + public void SetActivityContext(string category, ActivityContext activityLink, KeyValuePair? tag) + { + _store[category] = new CategoryLink(activityLink, tag); + } + + public void AddActivityContexts(string exceptCategory, Activity targetActivity) + { + foreach (var kvp in _store) + { + if (kvp.Key != exceptCategory) + { + var link = kvp.Value.Item1; + var tag = kvp.Value.Item2; + if (link != default) + { + targetActivity.AddLink(new ActivityLink(link)); + } + if (tag != null) + { + targetActivity.SetTag(tag.Value.Key, tag.Value.Value); + } + } + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_ActivityLinksStore")] + static extern object GetActivityLinksStore(Renderer instance); +} From 9a15e738cb355465acd8ee68ceb8ac8ced5c1fae Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 23:09:17 +0200 Subject: [PATCH 14/17] fix merge --- src/Components/Server/src/ComponentHub.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index a175494a39b5..a12d9fdfceca 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -354,11 +354,13 @@ await NotifyClientError( store: null, resourceCollection); + var httpActivityContext = Context.GetHttpContext().Features.Get()?.Activity.Context ?? default; + // Fire-and-forget the initialization process, because we can't block the // SignalR message loop (we'd get a deadlock if any of the initialization // logic relied on receiving a subsequent message from SignalR), and it will // take care of its own errors anyway. - _ = circuitHost.InitializeAsync(store: null, _httpContext, Context.ConnectionAborted); + _ = circuitHost.InitializeAsync(store: null, httpActivityContext, Context.ConnectionAborted); circuitHost.AttachPersistedState(persistedCircuitState); From 181e69c38572ed3e36febd6a0728a15112045b37 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 23:17:20 +0200 Subject: [PATCH 15/17] feedback --- src/Components/Components/src/ComponentsActivitySource.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Components/Components/src/ComponentsActivitySource.cs b/src/Components/Components/src/ComponentsActivitySource.cs index 13d1b97e7c3e..bf17ea8ae0ee 100644 --- a/src/Components/Components/src/ComponentsActivitySource.cs +++ b/src/Components/Components/src/ComponentsActivitySource.cs @@ -47,11 +47,6 @@ public ComponentsActivityHandle StartRouteActivity(string componentType, string _componentsActivityLinkStore!.SetActivityContext(ComponentsActivityLinkStore.Route, activity.Context, new KeyValuePair("aspnetcore.components.route", route)); } - if (httpActivity != null && httpActivity.Source.Name == "Microsoft.AspNetCore") - { - // store the http link - _componentsActivityLinkStore!.SetActivityContext(ComponentsActivityLinkStore.Http, httpActivity.Context, null); - } } return new ComponentsActivityHandle { Activity = activity, Previous = httpActivity }; From f383fe21c71b1d0a25871dacff408451aafa8cf3 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 11 Jun 2025 23:53:18 +0200 Subject: [PATCH 16/17] fix merge --- .../Server/test/Circuits/CircuitPersistenceManagerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs b/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs index 51decd3cfb10..8f9d92ddb4fe 100644 --- a/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs +++ b/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs @@ -300,7 +300,7 @@ private async Task CreateCircuitHostAsync( NullLoggerFactory.Instance.CreateLogger()); var circuitHandlers = Array.Empty(); var circuitMetrics = new CircuitMetrics(new TestMeterFactory()); - var componentsActivitySource = new ComponentsActivitySource(); + var componentsActivitySource = new CircuitActivitySource(); var logger = NullLoggerFactory.Instance.CreateLogger(); var circuitHost = new CircuitHost( From 2ac7286c853860c6aa1b236ecbeca7d4a6a8cf15 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 12 Jun 2025 07:14:53 +0200 Subject: [PATCH 17/17] fix tests --- src/Components/Components/src/RenderTree/Renderer.cs | 2 +- src/Components/Server/test/Circuits/TestCircuitHost.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 7cd799b5ba32..d0a62f240059 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -101,7 +101,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, _componentFactory = new ComponentFactory(componentActivator, this); _componentsMetrics = serviceProvider.GetService(); _componentsActivitySource = serviceProvider.GetService(); - _componentsActivitySource.Init(new ComponentsActivityLinkStore(this)); + _componentsActivitySource?.Init(new ComponentsActivityLinkStore(this)); ServiceProviderCascadingValueSuppliers = serviceProvider.GetService() is null ? Array.Empty() diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index 7c10d66a80b0..c5818c55ce28 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -34,13 +34,17 @@ public static CircuitHost Create( clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of(), Guid.NewGuid().ToString()); var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of>()); var navigationManager = new RemoteNavigationManager(Mock.Of>()); + var componentsActivitySource = new ComponentsActivitySource(); + var circuitActivitySource = new CircuitActivitySource(); var serviceProvider = new Mock(); serviceProvider .Setup(services => services.GetService(typeof(IJSRuntime))) .Returns(jsRuntime); + serviceProvider + .Setup(services => services.GetService(typeof(ComponentsActivitySource))) + .Returns(componentsActivitySource); var serverComponentDeserializer = Mock.Of(); var circuitMetrics = new CircuitMetrics(new TestMeterFactory()); - var circuitActivitySource = new CircuitActivitySource(); if (remoteRenderer == null) {