Skip to content

[blazor] Diagnostic tracing - feedback #62286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 12, 2025
Merged
Prev Previous commit
Next Next commit
feedback
  • Loading branch information
pavelsavara committed Jun 11, 2025
commit 768834eb024ba3e513666a759724b2df8e7cd155
27 changes: 15 additions & 12 deletions src/Components/Components/src/ComponentsActivitySource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

namespace Microsoft.AspNetCore.Components;

internal struct ComponentsActivityWrapper
/// <summary>
/// Named tuple for restoring the previous activity after stopping the current one.
/// </summary>
internal struct ComponentsActivityHandle
{
public Activity? Previous;
public Activity? Activity;
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
}
12 changes: 6 additions & 6 deletions src/Components/Components/src/RenderTree/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down
18 changes: 9 additions & 9 deletions src/Components/Components/src/Routing/Router.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
23 changes: 13 additions & 10 deletions src/Components/Server/src/Circuits/CircuitActivitySource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Components.RenderTree;

internal struct CircuitActivityWrapper
/// <summary>
/// Named tuple for restoring the previous activity after stopping the current one.
/// </summary>
internal struct CircuitActivityHandle
{
public Activity? Previous;
public Activity? Activity;
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/Components/Server/src/Circuits/CircuitHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading