Skip to content

Commit 93d7bfa

Browse files
authored
Reduce CLI diagnostic log noise and improve process logging (#15956)
1 parent bc0c339 commit 93d7bfa

18 files changed

+101
-216
lines changed

src/Aspire.Cli/Caching/DiskCache.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ private static TimeSpan ReadWindow(IConfiguration configuration, string key, Tim
6060
var cacheFilePath = ResolveValidCacheFile(keyHash);
6161
if (cacheFilePath is null)
6262
{
63-
_logger.LogDebug("Disk cache miss for key {RawKey}", key);
63+
_logger.LogTrace("Disk cache miss for key {RawKey}", key);
6464
return null;
6565
}
6666

6767
// Assuming here is a hit we attempt to read the file and return the string.
68-
_logger.LogDebug("Disk cache hit for key {RawKey} (file: {CacheFilePath})", key, cacheFilePath);
68+
_logger.LogTrace("Disk cache hit for key {RawKey} (file: {CacheFilePath})", key, cacheFilePath);
6969
return await File.ReadAllTextAsync(cacheFilePath, cancellationToken).ConfigureAwait(false);
7070
}
7171
catch (Exception ex)
@@ -120,7 +120,7 @@ public async Task SetAsync(string key, string content, CancellationToken cancell
120120
}
121121

122122
File.Move(tempFile, fullPath);
123-
_logger.LogDebug("Stored disk cache entry for key {RawKey} (file: {CacheFilePath})", key, fullPath);
123+
_logger.LogTrace("Stored disk cache entry for key {RawKey} (file: {CacheFilePath})", key, fullPath);
124124
}
125125
catch (Exception ex)
126126
{
@@ -270,19 +270,19 @@ private void TryDelete(FileInfo file, bool expired = false, bool old = false, bo
270270
file.Delete();
271271
if (expired)
272272
{
273-
_logger.LogDebug("Deleted expired cache file: {CacheFile}", file.FullName);
273+
_logger.LogTrace("Deleted expired cache file: {CacheFile}", file.FullName);
274274
}
275275
else if (old)
276276
{
277-
_logger.LogDebug("Deleted old cache file during global cleanup: {CacheFile}", file.FullName);
277+
_logger.LogTrace("Deleted old cache file during global cleanup: {CacheFile}", file.FullName);
278278
}
279279
else if (invalid)
280280
{
281-
_logger.LogDebug("Deleted invalid cache file: {CacheFile}", file.FullName);
281+
_logger.LogTrace("Deleted invalid cache file: {CacheFile}", file.FullName);
282282
}
283283
else
284284
{
285-
_logger.LogDebug("Deleted cache file: {CacheFile}", file.FullName);
285+
_logger.LogTrace("Deleted cache file: {CacheFile}", file.FullName);
286286
}
287287
}
288288
catch (Exception ex)

src/Aspire.Cli/Configuration/Features.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,29 @@ internal sealed class Features(IConfiguration configuration, ILogger<Features> l
1111
public bool IsFeatureEnabled(string feature, bool defaultValue)
1212
{
1313
var configKey = $"features:{feature}";
14-
14+
1515
var value = configuration[configKey];
16-
16+
1717
logger.LogTrace("Feature check: {Feature}, ConfigKey: {ConfigKey}, Value: '{Value}', DefaultValue: {DefaultValue}",
1818
feature, configKey, value ?? "(null)", defaultValue);
19-
19+
2020
if (string.IsNullOrEmpty(value))
2121
{
22-
logger.LogDebug("Feature {Feature} using default value: {DefaultValue}", feature, defaultValue);
22+
logger.LogTrace("Feature {Feature} using default value: {DefaultValue}", feature, defaultValue);
2323
return defaultValue;
2424
}
25-
25+
2626
var enabled = bool.TryParse(value, out var parsed) && parsed;
2727
logger.LogDebug("Feature {Feature} parsed value: {Enabled}", feature, enabled);
2828
return enabled;
2929
}
30-
}
30+
31+
public void LogFeatureState()
32+
{
33+
foreach (var metadata in KnownFeatures.GetAllFeatureMetadata())
34+
{
35+
var value = IsFeatureEnabled(metadata.Name, metadata.DefaultValue);
36+
logger.LogDebug("Feature {Feature} = {Value} (default: {DefaultValue})", metadata.Name, value, metadata.DefaultValue);
37+
}
38+
}
39+
}

src/Aspire.Cli/Configuration/IFeatures.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ namespace Aspire.Cli.Configuration;
66
internal interface IFeatures
77
{
88
bool IsFeatureEnabled(string featureFlag, bool defaultValue);
9-
}
9+
void LogFeatureState();
10+
}

src/Aspire.Cli/DotNet/ProcessExecution.cs

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -43,41 +43,31 @@ internal ProcessExecution(Process process, ILogger logger, ProcessInvocationOpti
4343
/// <inheritdoc />
4444
public bool Start()
4545
{
46-
var suppressLogging = _options.SuppressLogging;
47-
4846
var started = _process.Start();
4947

5048
if (!started)
5149
{
52-
if (!suppressLogging)
53-
{
54-
_logger.LogDebug("Failed to start process {FileName} with args: {Args}", FileName, string.Join(" ", Arguments));
55-
}
50+
_logger.LogDebug("{FileName} failed to start with args: {Args}", FileName, string.Join(" ", Arguments));
5651
return false;
5752
}
5853

59-
if (!suppressLogging)
60-
{
61-
_logger.LogDebug("Started {FileName} with PID: {ProcessId}", FileName, _process.Id);
62-
}
54+
_logger.LogDebug("{FileName}({ProcessId}) started in {WorkingDirectory}", FileName, _process.Id, _process.StartInfo.WorkingDirectory);
6355

6456
// Start stream forwarders
6557
_stdoutForwarder = Task.Run(async () =>
6658
{
6759
await ForwardStreamToLoggerAsync(
6860
_process.StandardOutput,
6961
"stdout",
70-
_options.StandardOutputCallback,
71-
suppressLogging);
62+
_options.StandardOutputCallback);
7263
});
7364

7465
_stderrForwarder = Task.Run(async () =>
7566
{
7667
await ForwardStreamToLoggerAsync(
7768
_process.StandardError,
7869
"stderr",
79-
_options.StandardErrorCallback,
80-
suppressLogging);
70+
_options.StandardErrorCallback);
8171
});
8272

8373
return true;
@@ -86,36 +76,25 @@ await ForwardStreamToLoggerAsync(
8676
/// <inheritdoc />
8777
public async Task<int> WaitForExitAsync(CancellationToken cancellationToken)
8878
{
89-
var suppressLogging = _options.SuppressLogging;
90-
91-
if (!suppressLogging)
92-
{
93-
_logger.LogDebug("Waiting for process to exit with PID: {ProcessId}", _process.Id);
94-
}
79+
_logger.LogDebug("{FileName}({ProcessId}) waiting for exit", FileName, _process.Id);
9580

9681
await _process.WaitForExitAsync(cancellationToken);
9782

9883
if (!_process.HasExited)
9984
{
100-
if (!suppressLogging)
101-
{
102-
_logger.LogDebug("Process with PID: {ProcessId} has not exited, killing it.", _process.Id);
103-
}
85+
_logger.LogDebug("{FileName}({ProcessId}) has not exited, killing it", FileName, _process.Id);
10486
_process.Kill(false);
10587
}
10688
else
10789
{
108-
if (!suppressLogging)
109-
{
110-
_logger.LogDebug("Process with PID: {ProcessId} has exited with code: {ExitCode}", _process.Id, _process.ExitCode);
111-
}
90+
_logger.LogDebug("{FileName}({ProcessId}) exited with code: {ExitCode}", FileName, _process.Id, _process.ExitCode);
11291
}
11392

11493
// Explicitly close the streams to unblock any pending ReadLineAsync calls.
11594
// In some environments (particularly CI containers), the stream handles may not
11695
// be automatically closed when the process exits, causing ReadLineAsync to block
11796
// indefinitely. Disposing the streams forces them to close.
118-
_logger.LogDebug("Closing stdout/stderr streams for PID: {ProcessId}", _process.Id);
97+
_logger.LogDebug("{FileName}({ProcessId}) closing stdout/stderr streams", FileName, _process.Id);
11998
_process.StandardOutput.Close();
12099
_process.StandardError.Close();
121100

@@ -130,11 +109,11 @@ public async Task<int> WaitForExitAsync(CancellationToken cancellationToken)
130109
var completedTask = await Task.WhenAny(forwardersCompleted, forwarderTimeout);
131110
if (completedTask == forwarderTimeout)
132111
{
133-
_logger.LogWarning("Stream forwarders for PID {ProcessId} did not complete within timeout after stream close. Continuing anyway.", _process.Id);
112+
_logger.LogWarning("{FileName}({ProcessId}) stream forwarders did not complete within timeout after stream close", FileName, _process.Id);
134113
}
135114
else
136115
{
137-
_logger.LogDebug("Pending forwarders for PID completed: {ProcessId}", _process.Id);
116+
_logger.LogDebug("{FileName}({ProcessId}) forwarders completed", FileName, _process.Id);
138117
}
139118
}
140119

@@ -153,23 +132,21 @@ public void Dispose()
153132
_process.Dispose();
154133
}
155134

156-
private async Task ForwardStreamToLoggerAsync(StreamReader reader, string identifier, Action<string>? lineCallback, bool suppressLogging)
135+
private async Task ForwardStreamToLoggerAsync(StreamReader reader, string identifier, Action<string>? lineCallback)
157136
{
158-
if (!suppressLogging)
159-
{
160-
_logger.LogDebug(
161-
"Starting to forward stream with identifier '{Identifier}' on process '{ProcessId}' to logger",
162-
identifier,
163-
_process.Id
164-
);
165-
}
137+
_logger.LogDebug(
138+
"{FileName}({ProcessId}) starting to forward {Identifier} stream",
139+
FileName,
140+
_process.Id,
141+
identifier
142+
);
166143

167144
try
168145
{
169146
string? line;
170147
while ((line = await reader.ReadLineAsync()) is not null)
171148
{
172-
if (!suppressLogging)
149+
if (_logger.IsEnabled(LogLevel.Trace))
173150
{
174151
_logger.LogTrace(
175152
"{FileName}({ProcessId}) {Identifier}: {Line}",
@@ -185,7 +162,7 @@ private async Task ForwardStreamToLoggerAsync(StreamReader reader, string identi
185162
catch (ObjectDisposedException)
186163
{
187164
// Stream was closed externally (e.g., after process exit). This is expected.
188-
_logger.LogDebug("Stream forwarder completed for {Identifier} - stream was closed", identifier);
165+
_logger.LogDebug("{FileName}({ProcessId}) {Identifier} stream forwarder completed - stream was closed", FileName, _process.Id, identifier);
189166
}
190167
}
191168
}

src/Aspire.Cli/DotNet/ProcessExecutionFactory.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Logging.Abstractions;
67

78
namespace Aspire.Cli.DotNet;
89

@@ -14,18 +15,15 @@ internal sealed class ProcessExecutionFactory(
1415
{
1516
public IProcessExecution CreateExecution(string fileName, string[] args, IDictionary<string, string>? env, DirectoryInfo workingDirectory, ProcessInvocationOptions options)
1617
{
17-
var suppressLogging = options.SuppressLogging;
18+
var effectiveLogger = options.SuppressLogging ? (ILogger)NullLogger.Instance : logger;
1819

19-
if (!suppressLogging)
20-
{
21-
logger.LogDebug("Running {FullName} with args: {Args}", workingDirectory.FullName, string.Join(" ", args));
20+
effectiveLogger.LogDebug("Running {FileName} in {WorkingDirectory} with args: {Args}", fileName, workingDirectory.FullName, string.Join(" ", args));
2221

23-
if (env is not null)
22+
if (env is not null)
23+
{
24+
foreach (var envKvp in env)
2425
{
25-
foreach (var envKvp in env)
26-
{
27-
logger.LogDebug("Running {FullName} with env: {EnvKey}={EnvValue}", workingDirectory.FullName, envKvp.Key, envKvp.Value);
28-
}
26+
effectiveLogger.LogDebug("{FileName} env: {EnvKey}={EnvValue}", fileName, envKvp.Key, envKvp.Value);
2927
}
3028
}
3129

@@ -53,6 +51,6 @@ public IProcessExecution CreateExecution(string fileName, string[] args, IDictio
5351
}
5452

5553
var process = new Process { StartInfo = startInfo };
56-
return new ProcessExecution(process, logger, options);
54+
return new ProcessExecution(process, effectiveLogger, options);
5755
}
5856
}

src/Aspire.Cli/NuGet/BundleNuGetPackageCache.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ private async Task<IEnumerable<NuGetPackage>> SearchPackagesInternalAsync(
211211

212212
private IEnumerable<NuGetPackage> FilterPackages(IEnumerable<NuGetPackage> packages, Func<string, bool>? filter)
213213
{
214+
var showDeprecatedPackages = _features.IsFeatureEnabled(KnownFeatures.ShowDeprecatedPackages, defaultValue: false);
214215
var effectiveFilter = (NuGetPackage p) =>
215216
{
216217
if (filter is not null)
@@ -221,7 +222,7 @@ private IEnumerable<NuGetPackage> FilterPackages(IEnumerable<NuGetPackage> packa
221222
var isOfficialPackage = IsOfficialOrCommunityToolkitPackage(p.Id);
222223

223224
// Apply deprecated package filter unless the user wants to show deprecated packages
224-
if (isOfficialPackage && !_features.IsFeatureEnabled(KnownFeatures.ShowDeprecatedPackages, defaultValue: false))
225+
if (isOfficialPackage && !showDeprecatedPackages)
225226
{
226227
return !DeprecatedPackages.IsDeprecated(p.Id);
227228
}

src/Aspire.Cli/NuGet/NuGetPackageCache.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo work
130130

131131
// If no specific filter is specified we use the fallback filter which is useful in most circumstances
132132
// other that aspire update which really needs to see all the packages to work effectively.
133+
var showDeprecatedPackages = features.IsFeatureEnabled(KnownFeatures.ShowDeprecatedPackages, defaultValue: false);
133134
var effectiveFilter = (NuGetPackage p) =>
134135
{
135136
if (filter is not null)
@@ -140,7 +141,7 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo work
140141
var isOfficialPackage = IsOfficialOrCommunityToolkitPackage(p.Id);
141142

142143
// Apply deprecated package filter unless the user wants to show deprecated packages
143-
if (isOfficialPackage && !features.IsFeatureEnabled(KnownFeatures.ShowDeprecatedPackages, defaultValue: false))
144+
if (isOfficialPackage && !showDeprecatedPackages)
144145
{
145146
return !DeprecatedPackages.IsDeprecated(p.Id);
146147
}

src/Aspire.Cli/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ public static async Task<int> Main(string[] args)
718718
var telemetry = app.Services.GetRequiredService<AspireCliTelemetry>();
719719
var telemetryManager = app.Services.GetRequiredService<TelemetryManager>();
720720

721+
// Log feature state at startup for diagnostics
722+
app.Services.GetRequiredService<IFeatures>().LogFeatureState();
723+
721724
// Display first run experience if this is the first time the CLI is run on this machine
722725
await DisplayFirstTimeUseNoticeIfNeededAsync(app.Services, args, cts.Token);
723726

tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ public void NewCommandTemplateSubcommandsListTechnicalNamesForNonInteractiveFlow
944944
using var workspace = TemporaryWorkspace.Create(outputHelper);
945945
var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
946946
{
947-
options.FeatureFlagsFactory = _ => new NewCommandTestFeatures(showAllTemplates: true);
947+
options.FeatureFlagsFactory = _ => new TestFeatures().SetFeature(KnownFeatures.ShowAllTemplates, true);
948948
});
949949

950950
var provider = services.BuildServiceProvider();
@@ -2131,18 +2131,6 @@ public Task<bool> ScaffoldAsync(ScaffoldContext context, CancellationToken cance
21312131
}
21322132
}
21332133

2134-
internal sealed class NewCommandTestFeatures(bool showAllTemplates = false) : IFeatures
2135-
{
2136-
public bool IsFeatureEnabled(string featureFlag, bool defaultValue)
2137-
{
2138-
return featureFlag switch
2139-
{
2140-
"showAllTemplates" => showAllTemplates,
2141-
_ => defaultValue
2142-
};
2143-
}
2144-
}
2145-
21462134
internal sealed class TestTypeScriptStarterProjectFactory(Func<DirectoryInfo, CancellationToken, Task<bool>> buildAndGenerateSdkAsync) : IAppHostProjectFactory
21472135
{
21482136
private readonly TestTypeScriptStarterProject _project = new(buildAndGenerateSdkAsync);

tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Text.Json;
66
using Aspire.Cli.Backchannel;
77
using Aspire.Cli.Commands;
8-
using Aspire.Cli.Configuration;
98
using Aspire.Cli.Diagnostics;
109
using Aspire.Cli.DotNet;
1110
using Aspire.Cli.Projects;
@@ -1481,22 +1480,6 @@ public void RunCommand_ForwardsUnmatchedTokensToAppHost()
14811480
Assert.Contains("value", result.UnmatchedTokens);
14821481
}
14831482

1484-
private sealed class TestFeatures : IFeatures
1485-
{
1486-
private readonly Dictionary<string, bool> _features = new();
1487-
1488-
public TestFeatures SetFeature(string featureName, bool value)
1489-
{
1490-
_features[featureName] = value;
1491-
return this;
1492-
}
1493-
1494-
public bool IsFeatureEnabled(string featureName, bool defaultValue = false)
1495-
{
1496-
return _features.TryGetValue(featureName, out var value) ? value : defaultValue;
1497-
}
1498-
}
1499-
15001483
[Fact]
15011484
public async Task CaptureAppHostLogsAsync_WritesCategoryWithAppHostPrefix()
15021485
{

0 commit comments

Comments
 (0)