From 97f62ee0d1e32de0d23c48daecf1c7f3b886f4fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 18:02:06 +0000 Subject: [PATCH 1/5] Initial plan From 3ae866e51cd3aaf869d1c4213a2e73b2f2029889 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 18:11:27 +0000 Subject: [PATCH 2/5] Emit dashboard login line for dotnet watch Co-authored-by: karolz-ms <15271049+karolz-ms@users.noreply.github.com> --- src/Shared/LoggingHelpers.cs | 5 +++++ .../LoggingHelpersTests.cs | 18 +++++++++++++++--- .../Dashboard/DashboardEventHandlersTests.cs | 6 ++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Shared/LoggingHelpers.cs b/src/Shared/LoggingHelpers.cs index d5c2f0b5f3e..8e4dea25d26 100644 --- a/src/Shared/LoggingHelpers.cs +++ b/src/Shared/LoggingHelpers.cs @@ -37,6 +37,11 @@ public static void WriteDashboardSummary(ILogger logger, string? dashboardUrl, s ? $"{dashboardAuthority}/login?t={token}" : null; + if (loginUrl is not null) + { + logger.LogInformation("Login to the dashboard at {LoginUrl}", loginUrl); + } + var templateBuilder = new StringBuilder(); var parameters = new List(); diff --git a/tests/Aspire.Dashboard.Tests/LoggingHelpersTests.cs b/tests/Aspire.Dashboard.Tests/LoggingHelpersTests.cs index f26d9fcbd47..c0c622e6876 100644 --- a/tests/Aspire.Dashboard.Tests/LoggingHelpersTests.cs +++ b/tests/Aspire.Dashboard.Tests/LoggingHelpersTests.cs @@ -23,7 +23,15 @@ public void WriteDashboardSummary_WithTokenAndOtlpEndpoints_LogsSummaryAndStruct "http://localhost:18890", "abc123"); - var write = Assert.Single(sink.Writes); + Assert.Equal(2, sink.Writes.Count); + + var loginWrite = sink.Writes.ElementAt(0); + Assert.Equal(LogLevel.Information, loginWrite.LogLevel); + Assert.Equal("Login to the dashboard at http://localhost:18888/login?t=abc123", loginWrite.Message); + Assert.Equal("Login to the dashboard at {LoginUrl}", LogTestHelpers.GetValue(loginWrite, "{OriginalFormat}")); + Assert.Equal("http://localhost:18888/login?t=abc123", LogTestHelpers.GetValue(loginWrite, "LoginUrl")); + + var write = sink.Writes.ElementAt(1); Assert.Equal(LogLevel.Information, write.LogLevel); Assert.NotNull(write.Message); var lines = GetMessageLines(write.Message!); @@ -169,7 +177,9 @@ public void WriteDashboardSummary_WithoutOtlpEndpoints_DoesNotIncludeOtlpLines() otlpHttpUrl: null, token: "abc123"); - var write = Assert.Single(sink.Writes); + Assert.Equal(2, sink.Writes.Count); + + var write = sink.Writes.ElementAt(1); Assert.NotNull(write.Message); var lines = GetMessageLines(write.Message!); @@ -198,7 +208,9 @@ public void WriteDashboardSummary_IsContainer_IncludesContainerMessage() token: "abc123", isContainer: true); - var write = Assert.Single(sink.Writes); + Assert.Equal(2, sink.Writes.Count); + + var write = sink.Writes.ElementAt(1); Assert.NotNull(write.Message); var containerMessage = "URLs may need changes depending on how network access to the container is configured."; diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardEventHandlersTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardEventHandlersTests.cs index 6ae294327ea..0069422ad8b 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardEventHandlersTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardEventHandlersTests.cs @@ -271,6 +271,12 @@ public async Task ResourceReadyEvent_LogsDashboardUrlFromAllocatedEndpoint(strin Assert.Equal(allocatedPort, uri.Port); Assert.Equal(expectedScheme, uri.Scheme); + var loginLog = testSink.Writes.FirstOrDefault(l => + LogTestHelpers.GetValue(l, "{OriginalFormat}")?.ToString() == "Login to the dashboard at {LoginUrl}"); + + Assert.NotNull(loginLog); + Assert.Equal($"{expectedScheme}://{expectedHost}:{allocatedPort}/login?t=test-token", LogTestHelpers.GetValue(loginLog, "LoginUrl")); + var summaryLog = testSink.Writes.FirstOrDefault(l => LogTestHelpers.GetValue(l, "{OriginalFormat}")?.ToString()?.Contains("OTLP/gRPC:") == true); From a5e6d63f49f96ebef58c5bb16adf50416b4a4593 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 22:31:28 +0000 Subject: [PATCH 3/5] Fix dashboard browser token log test Co-authored-by: karolz-ms <15271049+karolz-ms@users.noreply.github.com> --- .../Integration/FrontendBrowserTokenAuthTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Aspire.Dashboard.Tests/Integration/FrontendBrowserTokenAuthTests.cs b/tests/Aspire.Dashboard.Tests/Integration/FrontendBrowserTokenAuthTests.cs index 89a9a7b2697..48f7e85aa6e 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/FrontendBrowserTokenAuthTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/FrontendBrowserTokenAuthTests.cs @@ -405,6 +405,15 @@ public async Task LogOutput_NoToken_GeneratedTokenLogged() Assert.Equal(LogLevel.Warning, w.LogLevel); }, w => + { + Assert.Equal("Login to the dashboard at {LoginUrl}", LogTestHelpers.GetValue(w, "{OriginalFormat}")); + + var loginUrl = (string)LogTestHelpers.GetValue(w, "LoginUrl")!; + var uri = new Uri(loginUrl, UriKind.Absolute); + var queryString = HttpUtility.ParseQueryString(uri.Query); + Assert.NotNull(queryString["t"]); + }, + w => { Assert.StartsWith("Aspire Dashboard", (string)LogTestHelpers.GetValue(w, "{OriginalFormat}")!); From b6aede917e395610b1021c80f85e81162499b933 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 03:14:28 +0000 Subject: [PATCH 4/5] Clarify dotnet watch login log contract Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> --- src/Shared/LoggingHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/LoggingHelpers.cs b/src/Shared/LoggingHelpers.cs index 8329ae1d412..13c02bd6faa 100644 --- a/src/Shared/LoggingHelpers.cs +++ b/src/Shared/LoggingHelpers.cs @@ -39,7 +39,7 @@ public static void WriteDashboardSummary(ILogger logger, string? dashboardUrl, s if (loginUrl is not null) { - // dotnet watch looks for this exact log message to launch the dashboard. Do not change it. + // The template format string "Login to the dashboard at {LoginUrl}" is the contract with dotnet watch. Do not change it. logger.LogInformation("Login to the dashboard at {LoginUrl}", loginUrl); } From cfb8db4769fe6747a521b7ecf1cd8765b9950bf3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 29 May 2026 11:22:59 +0800 Subject: [PATCH 5/5] Apply suggestion from @JamesNK --- src/Shared/LoggingHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/LoggingHelpers.cs b/src/Shared/LoggingHelpers.cs index 13c02bd6faa..8329ae1d412 100644 --- a/src/Shared/LoggingHelpers.cs +++ b/src/Shared/LoggingHelpers.cs @@ -39,7 +39,7 @@ public static void WriteDashboardSummary(ILogger logger, string? dashboardUrl, s if (loginUrl is not null) { - // The template format string "Login to the dashboard at {LoginUrl}" is the contract with dotnet watch. Do not change it. + // dotnet watch looks for this exact log message to launch the dashboard. Do not change it. logger.LogInformation("Login to the dashboard at {LoginUrl}", loginUrl); }