diff --git a/DevProxy.Tests/LogsCommandTests.cs b/DevProxy.Tests/LogsCommandTests.cs new file mode 100644 index 00000000..6f316b4e --- /dev/null +++ b/DevProxy.Tests/LogsCommandTests.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using DevProxy.Commands; +using DevProxy.State; + +using Xunit; + +namespace DevProxy.Tests; + +public class LogsCommandTests +{ + [Fact] + public void GetMultipleInstancesNotice_SingleInstance_ReturnsNull() + { + var state = new ProxyInstanceState { Pid = 100 }; + + Assert.Null(LogsCommand.GetMultipleInstancesNotice(state, [state])); + } + + [Fact] + public void GetMultipleInstancesNotice_MultipleInstances_ListsSelectedAndOtherPids() + { + var now = DateTimeOffset.UtcNow; + var systemProxy = new ProxyInstanceState { Pid = 100, AsSystemProxy = true, StartedAt = now.AddMinutes(-10) }; + var other1 = new ProxyInstanceState { Pid = 200, AsSystemProxy = false, StartedAt = now }; + var other2 = new ProxyInstanceState { Pid = 300, AsSystemProxy = false, StartedAt = now.AddMinutes(-5) }; + List states = [other1, systemProxy, other2]; + + var selected = StateManager.SelectPrimaryState(states); + Assert.Same(systemProxy, selected); + + var notice = LogsCommand.GetMultipleInstancesNotice(selected!, states); + + Assert.NotNull(notice); + Assert.Contains("PID 100 (system proxy instance)", notice, StringComparison.Ordinal); + Assert.Contains("Other instances: 200, 300", notice, StringComparison.Ordinal); + Assert.Contains("--pid", notice, StringComparison.Ordinal); + } + + [Fact] + public void GetMultipleInstancesNotice_NoSystemProxy_SelectsMostRecentlyStarted() + { + var now = DateTimeOffset.UtcNow; + var older = new ProxyInstanceState { Pid = 100, AsSystemProxy = false, StartedAt = now.AddMinutes(-10) }; + var newer = new ProxyInstanceState { Pid = 200, AsSystemProxy = false, StartedAt = now }; + List states = [older, newer]; + + var selected = StateManager.SelectPrimaryState(states); + Assert.Same(newer, selected); + + var notice = LogsCommand.GetMultipleInstancesNotice(selected!, states); + + Assert.NotNull(notice); + Assert.Contains("PID 200 (most recently started instance)", notice, StringComparison.Ordinal); + Assert.Contains("Other instances: 100.", notice, StringComparison.Ordinal); + } +} diff --git a/DevProxy/Commands/LogsCommand.cs b/DevProxy/Commands/LogsCommand.cs index 652ce783..f61228ce 100644 --- a/DevProxy/Commands/LogsCommand.cs +++ b/DevProxy/Commands/LogsCommand.cs @@ -62,12 +62,19 @@ private async Task RunAsync(ParseResult parseResult, CancellationToken canc } else { - state = await StateManager.LoadStateAsync(cancellationToken); + var states = await StateManager.LoadAllStatesAsync(cancellationToken); + state = StateManager.SelectPrimaryState(states); if (state is null) { Console.WriteLine("Dev Proxy is not running."); return 1; } + + var notice = GetMultipleInstancesNotice(state, states); + if (notice is not null) + { + await Console.Error.WriteLineAsync(notice); + } } var logFile = state.LogFile; @@ -92,6 +99,22 @@ private async Task RunAsync(ParseResult parseResult, CancellationToken canc return 0; } + internal static string? GetMultipleInstancesNotice(ProxyInstanceState selected, IReadOnlyCollection states) + { + var otherPids = states + .Where(s => s.Pid != selected.Pid) + .Select(s => s.Pid.ToString(CultureInfo.InvariantCulture)) + .ToList(); + if (otherPids.Count == 0) + { + return null; + } + + var selectedDescription = selected.AsSystemProxy ? "system proxy instance" : "most recently started instance"; + return $"Multiple Dev Proxy instances are running. Showing logs from PID {selected.Pid} ({selectedDescription}). " + + $"Other instances: {string.Join(", ", otherPids)}. Use --pid to show logs from a specific instance."; + } + private static DateTime? ParseSinceOption(string? since) { if (string.IsNullOrEmpty(since)) diff --git a/DevProxy/State/StateManager.cs b/DevProxy/State/StateManager.cs index 957542b9..b8e15831 100644 --- a/DevProxy/State/StateManager.cs +++ b/DevProxy/State/StateManager.cs @@ -104,6 +104,15 @@ public static Task SaveStateAsync(ProxyInstanceState state, CancellationToken ca public static async Task LoadStateAsync(CancellationToken cancellationToken = default) { var states = await LoadAllStatesAsync(cancellationToken); + return SelectPrimaryState(states); + } + + /// + /// Selects the primary instance from the given states. + /// Prefers the system-proxy instance, then the most recently started instance. + /// + public static ProxyInstanceState? SelectPrimaryState(IEnumerable states) + { return states .OrderByDescending(s => s.AsSystemProxy ? 1 : 0) .ThenByDescending(s => s.StartedAt)