diff --git a/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs b/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs index 05bfd73b038..d1f938bdbaf 100644 --- a/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs +++ b/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs @@ -37,6 +37,9 @@ public partial class ResourceDetails : IComponentWithTelemetry, IDisposable [Inject] public required BrowserTimeProvider TimeProvider { get; init; } + [Inject] + public required ILogger Logger { get; init; } + private bool IsSpecOnlyToggleDisabled => !Resource.Environment.All(i => !i.FromSpec) && !GetResourceProperties(ordered: false).Any(static vm => vm.KnownProperty is null); // NOTE Excludes URLs as they don't expose sensitive items (and enumerating URLs is non-trivial) @@ -159,8 +162,8 @@ protected override async Task OnAfterRenderAsync(bool firstRender) protected override void OnInitialized() { - (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlStringsLoc); TelemetryContextProvider.Initialize(TelemetryContext); + (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlStringsLoc); } private IEnumerable GetRelationships() @@ -279,7 +282,7 @@ public void UpdateTelemetryProperties() { TelemetryContext.UpdateTelemetryProperties([ new ComponentTelemetryProperty(TelemetryPropertyKeys.ResourceType, new AspireTelemetryProperty(TelemetryPropertyValues.GetResourceTypeTelemetryValue(Resource.ResourceType, Resource.SupportsDetailedTelemetry))), - ]); + ], Logger); } public void Dispose() diff --git a/src/Aspire.Dashboard/Components/Pages/ComponentTelemetryContext.cs b/src/Aspire.Dashboard/Components/Pages/ComponentTelemetryContext.cs index 4726aafca14..b12168c64ef 100644 --- a/src/Aspire.Dashboard/Components/Pages/ComponentTelemetryContext.cs +++ b/src/Aspire.Dashboard/Components/Pages/ComponentTelemetryContext.cs @@ -61,7 +61,7 @@ public void Initialize(DashboardTelemetryService telemetryService, string? brows }); } - public bool UpdateTelemetryProperties(ReadOnlySpan modifiedProperties) + public bool UpdateTelemetryProperties(ReadOnlySpan modifiedProperties, ILogger logger) { // Only send updated properties if they are different from the existing ones. var anyChange = false; @@ -82,17 +82,18 @@ public bool UpdateTelemetryProperties(ReadOnlySpan m if (anyChange) { - PostProperties(); + PostProperties(logger); } return anyChange; } - private void PostProperties() + private void PostProperties(ILogger logger) { if (_telemetryService == null) { - throw new InvalidOperationException("InitializeAsync has not been called."); + logger.LogWarning($"Telemetry service for '{_componentType}' is not initialized. Cannot post properties."); + return; } _telemetryService.PostOperation( diff --git a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs index 4487cbf3fbc..69b1b174b73 100644 --- a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs @@ -126,6 +126,7 @@ private sealed class ConsoleLogsSubscription protected override async Task OnInitializedAsync() { + TelemetryContextProvider.Initialize(TelemetryContext); _resourceSubscriptionToken = _resourceSubscriptionCts.Token; _logEntries = new(Options.Value.Frontend.MaxConsoleLogCount); _noSelection = new() { Id = null, Name = ControlsStringsLoc[nameof(ControlsStrings.LabelNone)] }; @@ -172,8 +173,6 @@ protected override async Task OnInitializedAsync() PageViewModel.Status = Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsLogsNotYetAvailable)]; } - TelemetryContextProvider.Initialize(TelemetryContext); - async Task TrackResourceSnapshotsAsync() { if (!DashboardClient.IsEnabled) @@ -750,6 +749,6 @@ public void UpdateTelemetryProperties() { TelemetryContext.UpdateTelemetryProperties([ new ComponentTelemetryProperty(TelemetryPropertyKeys.ConsoleLogsShowTimestamp, new AspireTelemetryProperty(_showTimestamp, AspireTelemetryPropertyType.UserSetting)) - ]); + ], Logger); } } diff --git a/src/Aspire.Dashboard/Components/Pages/Error.razor.cs b/src/Aspire.Dashboard/Components/Pages/Error.razor.cs index 2723db43eb4..83654142ba9 100644 --- a/src/Aspire.Dashboard/Components/Pages/Error.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Error.razor.cs @@ -9,6 +9,9 @@ namespace Aspire.Dashboard.Components.Pages; public partial class Error : IComponentWithTelemetry, IDisposable { + [Inject] + public required ILogger Logger { get; init; } + [CascadingParameter] private HttpContext? HttpContext { get; set; } @@ -17,8 +20,8 @@ public partial class Error : IComponentWithTelemetry, IDisposable protected override void OnInitialized() { - RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; TelemetryContextProvider.Initialize(TelemetryContext); + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; } [Inject] @@ -36,7 +39,7 @@ public void UpdateTelemetryProperties() { TelemetryContext.UpdateTelemetryProperties([ new ComponentTelemetryProperty(TelemetryPropertyKeys.ErrorRequestId, new AspireTelemetryProperty(RequestId ?? string.Empty)), - ]); + ], Logger); } public void Dispose() diff --git a/src/Aspire.Dashboard/Components/Pages/Login.razor.cs b/src/Aspire.Dashboard/Components/Pages/Login.razor.cs index ae289b60d67..630934e7849 100644 --- a/src/Aspire.Dashboard/Components/Pages/Login.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Login.razor.cs @@ -41,6 +41,8 @@ public partial class Login : IAsyncDisposable, IComponentWithTelemetry protected override async Task OnInitializedAsync() { + TelemetryContextProvider.Initialize(TelemetryContext); + // Create EditContext before awaiting. This is required to prevent an await in OnInitializedAsync // triggering parameters being set on EditForm before EditContext is created. // If that happens then EditForm errors that it requires an EditContext. @@ -60,8 +62,6 @@ protected override async Task OnInitializedAsync() return; } } - - TelemetryContextProvider.Initialize(TelemetryContext); } protected override async Task OnAfterRenderAsync(bool firstRender) diff --git a/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs b/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs index d5e0eb2d006..e9a59a87dcb 100644 --- a/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs @@ -78,6 +78,8 @@ public partial class Metrics : IDisposable, IComponentWithTelemetry, IPageWithSe protected override void OnInitialized() { + TelemetryContextProvider.Initialize(TelemetryContext); + _durations = new List> { new() { Name = Loc[nameof(Dashboard.Resources.Metrics.MetricsLastOneMinute)], Id = TimeSpan.FromMinutes(1) }, @@ -109,8 +111,6 @@ protected override void OnInitialized() UpdateApplications(); StateHasChanged(); })); - - TelemetryContextProvider.Initialize(TelemetryContext); } protected override async Task OnParametersSetAsync() @@ -335,6 +335,6 @@ public void UpdateTelemetryProperties() new ComponentTelemetryProperty(TelemetryPropertyKeys.MetricsInstrumentsCount, new AspireTelemetryProperty((PageViewModel.Instruments?.Count ?? -1).ToString(CultureInfo.InvariantCulture), AspireTelemetryPropertyType.Metric)), new ComponentTelemetryProperty(TelemetryPropertyKeys.MetricsSelectedDuration, new AspireTelemetryProperty(PageViewModel.SelectedDuration.Id.ToString(), AspireTelemetryPropertyType.UserSetting)), new ComponentTelemetryProperty(TelemetryPropertyKeys.MetricsSelectedView, new AspireTelemetryProperty(PageViewModel.SelectedViewKind?.ToString() ?? string.Empty, AspireTelemetryPropertyType.UserSetting)) - ]); + ], Logger); } } diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs index 1181c1c25d1..896c17c8f90 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs @@ -56,6 +56,8 @@ public partial class Resources : ComponentBase, IComponentWithTelemetry, IAsyncD public required IOptionsMonitor DashboardOptions { get; init; } [Inject] public required ComponentTelemetryContextProvider TelemetryContextProvider { get; init; } + [Inject] + public required ILogger Logger { get; init; } public string BasePath => DashboardUrls.ResourcesBasePath; public string SessionStorageKey => BrowserStorageKeys.ResourcesPageState; @@ -160,6 +162,7 @@ private async Task HandleSearchFilterChangedAsync() protected override async Task OnInitializedAsync() { + TelemetryContextProvider.Initialize(TelemetryContext); (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ @@ -214,7 +217,6 @@ protected override async Task OnInitializedAsync() } }); - TelemetryContextProvider.Initialize(TelemetryContext); _isLoading = false; async Task SubscribeResourcesAsync() @@ -867,6 +869,6 @@ public void UpdateTelemetryProperties() new(TelemetryPropertyKeys.ResourceTypes, new AspireTelemetryProperty(_resourceByName.Values.Select(r => TelemetryPropertyValues.GetResourceTypeTelemetryValue(r.ResourceType, r.SupportsDetailedTelemetry)).OrderBy(t => t).ToList())) }; - TelemetryContext.UpdateTelemetryProperties(properties.ToArray()); + TelemetryContext.UpdateTelemetryProperties(properties.ToArray(), Logger); } } diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs index 640966b9581..ec00e5c2eed 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs @@ -148,6 +148,7 @@ private async ValueTask> GetData(GridItems protected override void OnInitialized() { + TelemetryContextProvider.Initialize(TelemetryContext); (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ @@ -203,8 +204,6 @@ protected override void OnInitialized() UpdateApplications(); StateHasChanged(); })); - - TelemetryContextProvider.Initialize(TelemetryContext); } protected override async Task OnParametersSetAsync() @@ -497,6 +496,6 @@ public void UpdateTelemetryProperties() TelemetryContext.UpdateTelemetryProperties([ new ComponentTelemetryProperty(TelemetryPropertyKeys.StructuredLogsSelectedLogLevel, new AspireTelemetryProperty(PageViewModel.SelectedLogLevel.Id?.ToString() ?? string.Empty, AspireTelemetryPropertyType.UserSetting)), new ComponentTelemetryProperty(TelemetryPropertyKeys.StructuredLogsFilterCount, new AspireTelemetryProperty(ViewModel.Filters.Count.ToString(CultureInfo.InvariantCulture), AspireTelemetryPropertyType.Metric)) - ]); + ], Logger); } } diff --git a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs index 811054b9683..0cd695f0a48 100644 --- a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs @@ -66,6 +66,8 @@ public partial class TraceDetail : ComponentBase, IComponentWithTelemetry, IDisp protected override void OnInitialized() { + TelemetryContextProvider.Initialize(TelemetryContext); + _gridColumns = [ new GridColumn(Name: NameColumn, DesktopWidth: "4fr", MobileWidth: "4fr"), new GridColumn(Name: TicksColumn, DesktopWidth: "12fr", MobileWidth: "12fr"), @@ -81,8 +83,6 @@ protected override void OnInitialized() await InvokeAsync(_dataGrid.SafeRefreshDataAsync); })); } - - TelemetryContextProvider.Initialize(TelemetryContext); } // Internal to be used in unit tests diff --git a/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs b/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs index c57c732de6c..b195b2d4a69 100644 --- a/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs @@ -149,6 +149,7 @@ private async ValueTask> GetData(GridItemsPro protected override void OnInitialized() { + TelemetryContextProvider.Initialize(TelemetryContext); (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ @@ -168,8 +169,6 @@ protected override void OnInitialized() UpdateApplications(); StateHasChanged(); })); - - TelemetryContextProvider.Initialize(TelemetryContext); } protected override async Task OnParametersSetAsync() diff --git a/tests/Aspire.Dashboard.Tests/Telemetry/ComponentTelemetryContextTests.cs b/tests/Aspire.Dashboard.Tests/Telemetry/ComponentTelemetryContextTests.cs index cfb1702ee14..eabe56b0567 100644 --- a/tests/Aspire.Dashboard.Tests/Telemetry/ComponentTelemetryContextTests.cs +++ b/tests/Aspire.Dashboard.Tests/Telemetry/ComponentTelemetryContextTests.cs @@ -20,6 +20,7 @@ public async Task ComponentTelemetryContext_TelemetryEnabled_EndToEnd() var telemetryContextProvider = new ComponentTelemetryContextProvider(telemetryService); telemetryContextProvider.SetBrowserUserAgent("mozilla"); await telemetryService.InitializeAsync(); + var logger = NullLogger.Instance; // Act & assert initialize telemetryContextProvider.Initialize(telemetryContext); @@ -38,18 +39,18 @@ public async Task ComponentTelemetryContext_TelemetryEnabled_EndToEnd() OperationContext? parametersUpdateOperation; // Act & assert update properties - telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("Value"))]); + telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("Value"))], logger); Assert.Equal(3, telemetryContext.Properties.Count); Assert.True(telemetrySender.ContextChannel.Reader.TryRead(out parametersUpdateOperation)); Assert.Equal("/telemetry/operation - $aspire/dashboard/component/paramsSet", parametersUpdateOperation.Name); Assert.Single(parametersUpdateOperation.Properties); // If value didn't change, we shouldn't post again - telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("Value"))]); + telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("Value"))], logger); Assert.Equal(3, telemetryContext.Properties.Count); Assert.False(telemetrySender.ContextChannel.Reader.TryRead(out parametersUpdateOperation)); - telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("NewValue"))]); + telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("NewValue"))], logger); Assert.Equal(3, telemetryContext.Properties.Count); Assert.True(telemetrySender.ContextChannel.Reader.TryRead(out parametersUpdateOperation)); @@ -69,13 +70,14 @@ public async Task ComponentTelemetryContext_TelemetryDisabled_EndToEnd() var telemetryContextProvider = new ComponentTelemetryContextProvider(telemetryService); telemetryContextProvider.SetBrowserUserAgent("mozilla"); await telemetryService.InitializeAsync(); + var logger = NullLogger.Instance; // Act & assert initialize telemetryContextProvider.Initialize(telemetryContext); Assert.False(telemetrySender.ContextChannel.Reader.TryRead(out _)); // Act & assert update properties - telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("Value"))]); + telemetryContext.UpdateTelemetryProperties([new ComponentTelemetryProperty("Test", new AspireTelemetryProperty("Value"))], logger); Assert.Equal(3, telemetryContext.Properties.Count); Assert.False(telemetrySender.ContextChannel.Reader.TryRead(out _));