From 87f101718fe6705b7f84e99bb47d00a70862906d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 05:34:43 +0000 Subject: [PATCH 01/12] Initial plan From 4fc23075ec1cc9868b24dc90a8ed4fe03dcd5005 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 05:52:41 +0000 Subject: [PATCH 02/12] Clean up deployment logs by conditionally showing stack traces and reducing verbosity Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../PublishModeProvisioningContextProvider.cs | 36 ++++++++++++++++--- .../RunModeProvisioningContextProvider.cs | 9 ++++- .../Provisioners/AzureProvisioner.cs | 9 ++++- .../Orchestrator/ParameterProcessor.cs | 27 ++++++++++++-- .../Publishing/ContainerRuntimeBase.cs | 4 +-- .../Publishing/DockerContainerRuntime.cs | 2 +- .../Internal/FileDeploymentStateManager.cs | 9 ++++- .../UserSecretsDeploymentStateManager.cs | 18 ++++++++-- .../ResourceContainerImageBuilder.cs | 18 ++++++++-- 9 files changed, 115 insertions(+), 17 deletions(-) diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs index f4c72d460f5..92a3bd4ca17 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs @@ -73,7 +73,14 @@ public override async Task CreateProvisioningContextAsync(C } catch (Exception ex) { - _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); + } + else + { + _logger.LogError("Failed to retrieve Azure provisioning options."); + } } return await base.CreateProvisioningContextAsync(cancellationToken).ConfigureAwait(false); @@ -144,7 +151,14 @@ private async Task PromptForTenantAsync(CancellationToken cancellationToken) } catch (Exception ex) { - _logger.LogError(ex, "Failed to retrieve Azure tenant information."); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogError(ex, "Failed to retrieve Azure tenant information."); + } + else + { + _logger.LogError("Failed to retrieve Azure tenant information: {Message}", ex.Message); + } await step.FailAsync($"Failed to retrieve tenant information: {ex.Message}", cancellationToken).ConfigureAwait(false); throw; } @@ -243,7 +257,14 @@ private async Task PromptForSubscriptionAsync(CancellationToken cancellationToke } catch (Exception ex) { - _logger.LogError(ex, "Failed to retrieve Azure subscription information."); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogError(ex, "Failed to retrieve Azure subscription information."); + } + else + { + _logger.LogError("Failed to retrieve Azure subscription information: {Message}", ex.Message); + } await step.FailAsync($"Failed to retrieve subscription information: {ex.Message}", cancellationToken).ConfigureAwait(false); throw; } @@ -342,7 +363,14 @@ private async Task PromptForLocationAndResourceGroupAsync(CancellationToken canc } catch (Exception ex) { - _logger.LogError(ex, "Failed to retrieve Azure region information."); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogError(ex, "Failed to retrieve Azure region information."); + } + else + { + _logger.LogError("Failed to retrieve Azure region information: {Message}", ex.Message); + } await step.FailAsync($"Failed to retrieve region information: {ex.Message}", cancellationToken).ConfigureAwait(false); throw; } diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs index 5ffb3b6d721..45f09dcbdc3 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs @@ -84,7 +84,14 @@ private void EnsureProvisioningOptions() } catch (Exception ex) { - _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); + } + else + { + _logger.LogError("Failed to retrieve Azure provisioning options."); + } _provisioningOptionsAvailable.SetException(ex); } }); diff --git a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs index 149f04e87ec..e6c5fb246a3 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs @@ -245,7 +245,14 @@ await bicepProvisioner.GetOrCreateResourceAsync( } catch (Exception ex) { - resourceLogger.LogError(ex, "Error provisioning {ResourceName}.", resource.AzureResource.Name); + if (resourceLogger.IsEnabled(LogLevel.Debug)) + { + resourceLogger.LogError(ex, "Error provisioning {ResourceName}.", resource.AzureResource.Name); + } + else + { + resourceLogger.LogError("Error provisioning {ResourceName}: {Message}", resource.AzureResource.Name, ex.Message); + } resource.AzureResource.ProvisioningTaskCompletionSource?.TrySetException(new InvalidOperationException($"Unable to resolve references from {resource.AzureResource.Name}")); } } diff --git a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs index 317891ef505..deb74d4adf1 100644 --- a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs +++ b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs @@ -239,7 +239,14 @@ internal async Task HandleUnresolvedParametersAsync(IList unr } catch (Exception ex) { - logger.LogWarning(ex, "Failed to load deployment state. Continuing without saved parameter values."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogWarning(ex, "Failed to load deployment state. Continuing without saved parameter values."); + } + else + { + logger.LogWarning("Failed to load deployment state. Continuing without saved parameter values."); + } } } @@ -369,7 +376,14 @@ await notificationService.PublishUpdateAsync(parameter, s => } catch (Exception ex) { - logger.LogWarning(ex, "Failed to save parameter values to deployment state."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogWarning(ex, "Failed to save parameter values to deployment state."); + } + else + { + logger.LogWarning("Failed to save parameter values to deployment state."); + } } } } @@ -398,7 +412,14 @@ private async Task SaveParametersToDeploymentStateAsync(IEnumerable ExecuteContainerCommandWithExitCodeAsync( } } - _logger.LogInformation("Running {RuntimeName} with arguments: {ArgumentList}", Name, spec.Arguments); + _logger.LogDebug("Running {RuntimeName} with arguments: {ArgumentList}", Name, spec.Arguments); var (pendingProcessResult, processDisposable) = ProcessUtil.Run(spec); await using (processDisposable) diff --git a/src/Aspire.Hosting/Publishing/DockerContainerRuntime.cs b/src/Aspire.Hosting/Publishing/DockerContainerRuntime.cs index e141accc03b..7ae54143991 100644 --- a/src/Aspire.Hosting/Publishing/DockerContainerRuntime.cs +++ b/src/Aspire.Hosting/Publishing/DockerContainerRuntime.cs @@ -110,7 +110,7 @@ private async Task RunDockerBuildAsync(string contextPath, string dockerfil } } - Logger.LogInformation("Running Docker CLI with arguments: {ArgumentList}", spec.Arguments); + Logger.LogDebug("Running Docker CLI with arguments: {ArgumentList}", spec.Arguments); var (pendingProcessResult, processDisposable) = ProcessUtil.Run(spec); await using (processDisposable) diff --git a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs index c3ff3831a2e..ece621c3fca 100644 --- a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs +++ b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs @@ -73,7 +73,14 @@ await File.WriteAllTextAsync( } catch (Exception ex) { - logger.LogWarning(ex, "Failed to save deployment state."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogWarning(ex, "Failed to save deployment state."); + } + else + { + logger.LogWarning("Failed to save deployment state: {Message}", ex.Message); + } throw; } } diff --git a/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs index 40f4e340d50..690e3c029d6 100644 --- a/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs +++ b/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs @@ -43,12 +43,26 @@ protected override async Task SaveStateToStorageAsync(JsonObject state, Cancella } catch (JsonException ex) { - logger.LogError(ex, "Failed to provision Azure resources because user secrets file is not well-formed JSON."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogError(ex, "Failed to provision Azure resources because user secrets file is not well-formed JSON."); + } + else + { + logger.LogError("Failed to provision Azure resources because user secrets file is not well-formed JSON: {Message}", ex.Message); + } throw; } catch (Exception ex) { - logger.LogWarning(ex, "Failed to save user secrets."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogWarning(ex, "Failed to save user secrets."); + } + else + { + logger.LogWarning("Failed to save user secrets: {Message}", ex.Message); + } throw; } } diff --git a/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs b/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs index 6ef489bf4e5..223ccafab98 100644 --- a/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs +++ b/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs @@ -407,7 +407,14 @@ await ContainerRuntime.BuildImageAsync( } catch (Exception ex) { - logger.LogError(ex, "Failed to build container image from Dockerfile."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogError(ex, "Failed to build container image from Dockerfile."); + } + else + { + logger.LogError("Failed to build container image from Dockerfile: {Message}", ex.Message); + } await publishingTask.FailAsync($"Building image for {resource.Name} failed", cancellationToken).ConfigureAwait(false); throw; } @@ -430,7 +437,14 @@ await ContainerRuntime.BuildImageAsync( } catch (Exception ex) { - logger.LogError(ex, "Failed to build container image from Dockerfile."); + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogError(ex, "Failed to build container image from Dockerfile."); + } + else + { + logger.LogError("Failed to build container image from Dockerfile: {Message}", ex.Message); + } throw; } } From f98a94ed7e29bbbc474128e24f562686220b31a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 06:15:34 +0000 Subject: [PATCH 03/12] Centralize stack trace filtering logic in PipelineLoggerProvider Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../PublishModeProvisioningContextProvider.cs | 36 +++---------------- .../RunModeProvisioningContextProvider.cs | 9 +---- .../Provisioners/AzureProvisioner.cs | 9 +---- .../Orchestrator/ParameterProcessor.cs | 27 ++------------ .../Pipelines/PipelineLoggerProvider.cs | 18 ++++++++-- .../Internal/FileDeploymentStateManager.cs | 9 +---- .../UserSecretsDeploymentStateManager.cs | 18 ++-------- .../ResourceContainerImageBuilder.cs | 18 ++-------- 8 files changed, 30 insertions(+), 114 deletions(-) diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs index 92a3bd4ca17..f4c72d460f5 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs @@ -73,14 +73,7 @@ public override async Task CreateProvisioningContextAsync(C } catch (Exception ex) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); - } - else - { - _logger.LogError("Failed to retrieve Azure provisioning options."); - } + _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); } return await base.CreateProvisioningContextAsync(cancellationToken).ConfigureAwait(false); @@ -151,14 +144,7 @@ private async Task PromptForTenantAsync(CancellationToken cancellationToken) } catch (Exception ex) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogError(ex, "Failed to retrieve Azure tenant information."); - } - else - { - _logger.LogError("Failed to retrieve Azure tenant information: {Message}", ex.Message); - } + _logger.LogError(ex, "Failed to retrieve Azure tenant information."); await step.FailAsync($"Failed to retrieve tenant information: {ex.Message}", cancellationToken).ConfigureAwait(false); throw; } @@ -257,14 +243,7 @@ private async Task PromptForSubscriptionAsync(CancellationToken cancellationToke } catch (Exception ex) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogError(ex, "Failed to retrieve Azure subscription information."); - } - else - { - _logger.LogError("Failed to retrieve Azure subscription information: {Message}", ex.Message); - } + _logger.LogError(ex, "Failed to retrieve Azure subscription information."); await step.FailAsync($"Failed to retrieve subscription information: {ex.Message}", cancellationToken).ConfigureAwait(false); throw; } @@ -363,14 +342,7 @@ private async Task PromptForLocationAndResourceGroupAsync(CancellationToken canc } catch (Exception ex) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogError(ex, "Failed to retrieve Azure region information."); - } - else - { - _logger.LogError("Failed to retrieve Azure region information: {Message}", ex.Message); - } + _logger.LogError(ex, "Failed to retrieve Azure region information."); await step.FailAsync($"Failed to retrieve region information: {ex.Message}", cancellationToken).ConfigureAwait(false); throw; } diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs index 45f09dcbdc3..5ffb3b6d721 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs @@ -84,14 +84,7 @@ private void EnsureProvisioningOptions() } catch (Exception ex) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); - } - else - { - _logger.LogError("Failed to retrieve Azure provisioning options."); - } + _logger.LogError(ex, "Failed to retrieve Azure provisioning options."); _provisioningOptionsAvailable.SetException(ex); } }); diff --git a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs index e6c5fb246a3..149f04e87ec 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs @@ -245,14 +245,7 @@ await bicepProvisioner.GetOrCreateResourceAsync( } catch (Exception ex) { - if (resourceLogger.IsEnabled(LogLevel.Debug)) - { - resourceLogger.LogError(ex, "Error provisioning {ResourceName}.", resource.AzureResource.Name); - } - else - { - resourceLogger.LogError("Error provisioning {ResourceName}: {Message}", resource.AzureResource.Name, ex.Message); - } + resourceLogger.LogError(ex, "Error provisioning {ResourceName}.", resource.AzureResource.Name); resource.AzureResource.ProvisioningTaskCompletionSource?.TrySetException(new InvalidOperationException($"Unable to resolve references from {resource.AzureResource.Name}")); } } diff --git a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs index deb74d4adf1..317891ef505 100644 --- a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs +++ b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs @@ -239,14 +239,7 @@ internal async Task HandleUnresolvedParametersAsync(IList unr } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogWarning(ex, "Failed to load deployment state. Continuing without saved parameter values."); - } - else - { - logger.LogWarning("Failed to load deployment state. Continuing without saved parameter values."); - } + logger.LogWarning(ex, "Failed to load deployment state. Continuing without saved parameter values."); } } @@ -376,14 +369,7 @@ await notificationService.PublishUpdateAsync(parameter, s => } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogWarning(ex, "Failed to save parameter values to deployment state."); - } - else - { - logger.LogWarning("Failed to save parameter values to deployment state."); - } + logger.LogWarning(ex, "Failed to save parameter values to deployment state."); } } } @@ -412,14 +398,7 @@ private async Task SaveParametersToDeploymentStateAsync(IEnumerable /// This logger acts as a proxy and dynamically resolves the current logger on each operation, /// allowing the target logger to change between calls. + /// When logging exceptions, stack traces are only included when the log level is Debug or Trace. /// private sealed class PipelineLogger(Func currentLoggerAccessor) : ILogger { @@ -73,7 +74,20 @@ public bool IsEnabled(LogLevel logLevel) => currentLoggerAccessor().IsEnabled(logLevel); /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => - currentLoggerAccessor().Log(logLevel, eventId, state, exception, formatter); + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + var logger = currentLoggerAccessor(); + + // If there's an exception and the log level is not Debug or Trace, exclude the exception from the log + // to avoid cluttering production logs with stack traces + if (exception is not null && logLevel > LogLevel.Debug) + { + logger.Log(logLevel, eventId, state, null, formatter); + } + else + { + logger.Log(logLevel, eventId, state, exception, formatter); + } + } } } \ No newline at end of file diff --git a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs index ece621c3fca..c3ff3831a2e 100644 --- a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs +++ b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs @@ -73,14 +73,7 @@ await File.WriteAllTextAsync( } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogWarning(ex, "Failed to save deployment state."); - } - else - { - logger.LogWarning("Failed to save deployment state: {Message}", ex.Message); - } + logger.LogWarning(ex, "Failed to save deployment state."); throw; } } diff --git a/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs index 690e3c029d6..40f4e340d50 100644 --- a/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs +++ b/src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs @@ -43,26 +43,12 @@ protected override async Task SaveStateToStorageAsync(JsonObject state, Cancella } catch (JsonException ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogError(ex, "Failed to provision Azure resources because user secrets file is not well-formed JSON."); - } - else - { - logger.LogError("Failed to provision Azure resources because user secrets file is not well-formed JSON: {Message}", ex.Message); - } + logger.LogError(ex, "Failed to provision Azure resources because user secrets file is not well-formed JSON."); throw; } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogWarning(ex, "Failed to save user secrets."); - } - else - { - logger.LogWarning("Failed to save user secrets: {Message}", ex.Message); - } + logger.LogWarning(ex, "Failed to save user secrets."); throw; } } diff --git a/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs b/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs index 223ccafab98..6ef489bf4e5 100644 --- a/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs +++ b/src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs @@ -407,14 +407,7 @@ await ContainerRuntime.BuildImageAsync( } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogError(ex, "Failed to build container image from Dockerfile."); - } - else - { - logger.LogError("Failed to build container image from Dockerfile: {Message}", ex.Message); - } + logger.LogError(ex, "Failed to build container image from Dockerfile."); await publishingTask.FailAsync($"Building image for {resource.Name} failed", cancellationToken).ConfigureAwait(false); throw; } @@ -437,14 +430,7 @@ await ContainerRuntime.BuildImageAsync( } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogError(ex, "Failed to build container image from Dockerfile."); - } - else - { - logger.LogError("Failed to build container image from Dockerfile: {Message}", ex.Message); - } + logger.LogError(ex, "Failed to build container image from Dockerfile."); throw; } } From a6db74f80cc304a0b4e6b5d00164ac89e807e488 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 Oct 2025 23:50:29 -0700 Subject: [PATCH 04/12] Update src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs --- src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs index 1dd31a080ec..80f0a821060 100644 --- a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs +++ b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs @@ -80,7 +80,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except // If there's an exception and the log level is not Debug or Trace, exclude the exception from the log // to avoid cluttering production logs with stack traces - if (exception is not null && logLevel > LogLevel.Debug) + if (exception is not null && logLevel >= LogLevel.Debug) { logger.Log(logLevel, eventId, state, null, formatter); } From 36d0aabd516c9fe5c1c6aac9120f083598ad2181 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:11:43 +0000 Subject: [PATCH 05/12] Clean up Azure credential logging and remove verbose messages Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../Internal/BaseProvisioningContextProvider.cs | 2 -- .../Internal/DefaultTokenCredentialProvider.cs | 11 +---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs index 40a0c0daff0..9ea5c2a64ad 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs @@ -88,8 +88,6 @@ public virtual async Task CreateProvisioningContextAsync(Ca var armClient = _armClientProvider.GetArmClient(credential, subscriptionId); - _logger.LogInformation("Getting default subscription and tenant..."); - var (subscriptionResource, tenantResource) = await armClient.GetSubscriptionAndTenantAsync(cancellationToken).ConfigureAwait(false); _logger.LogInformation("Default subscription: {name} ({subscriptionId})", subscriptionResource.DisplayName, subscriptionResource.Id); diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultTokenCredentialProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultTokenCredentialProvider.cs index 88fe8607253..31b2efa072f 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultTokenCredentialProvider.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultTokenCredentialProvider.cs @@ -57,15 +57,6 @@ public DefaultTokenCredentialProvider( internal void LogCredentialType() { - if (_credential.GetType() == typeof(DefaultAzureCredential)) - { - _logger.LogInformation( - "Using DefaultAzureCredential for provisioning. This may not work in all environments. " + - "See https://aka.ms/azsdk/net/identity/credential-chains#defaultazurecredential-overview for more information."); - } - else - { - _logger.LogInformation("Using {credentialType} for provisioning.", _credential.GetType().Name); - } + _logger.LogInformation("Using {credentialType} for provisioning.", _credential.GetType().Name); } } From a69aebb7407c27f21012dc683017b14d7f478c84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:10:08 +0000 Subject: [PATCH 06/12] Use PipelineLoggingOptions to centralize log level and exception detail configuration Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../DistributedApplicationBuilder.cs | 16 +++++++----- .../Pipelines/PipelineLoggerProvider.cs | 21 ++++++++++++---- .../Pipelines/PipelineLoggingOptions.cs | 25 +++++++++++++++++++ .../Pipelines/PipelineLoggerProviderTests.cs | 10 ++++++-- 4 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 src/Aspire.Hosting/Pipelines/PipelineLoggingOptions.cs diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index aec66987b26..30b1d2a5cbe 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -446,13 +446,11 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) _innerBuilder.Services.AddSingleton(); _innerBuilder.Services.AddSingleton(sp => sp.GetRequiredService()); _innerBuilder.Services.AddSingleton(Pipeline); - _innerBuilder.Services.AddSingleton(); - // Read this once from configuration and use it to filter logs from the pipeline - LogLevel? minLevel = null; - _innerBuilder.Logging.AddFilter((level) => + // Configure pipeline logging options + _innerBuilder.Services.Configure(o => { - minLevel ??= _innerBuilder.Configuration["Publishing:LogLevel"]?.ToLowerInvariant() switch + o.MinimumLogLevel = _innerBuilder.Configuration["Publishing:LogLevel"]?.ToLowerInvariant() switch { "trace" => LogLevel.Trace, "debug" => LogLevel.Debug, @@ -462,8 +460,14 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) "crit" or "critical" => LogLevel.Critical, _ => LogLevel.Information }; + }); - return level >= minLevel; + _innerBuilder.Services.AddSingleton(); + + // Configure logging filter using the PipelineLoggingOptions + _innerBuilder.Services.AddOptions().Configure((filterLoggingOptions, pipelineLoggingOptions) => + { + filterLoggingOptions.AddFilter((level) => level >= pipelineLoggingOptions.MinimumLogLevel); }); // Register IDeploymentStateManager based on execution context diff --git a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs index 80f0a821060..0cd75d582da 100644 --- a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs +++ b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; namespace Aspire.Hosting.Pipelines; @@ -16,6 +17,16 @@ namespace Aspire.Hosting.Pipelines; internal sealed class PipelineLoggerProvider : ILoggerProvider { private static readonly AsyncLocal s_currentLogger = new(); + private readonly PipelineLoggingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// The pipeline logging options. + public PipelineLoggerProvider(IOptions options) + { + _options = options.Value; + } /// /// Gets or sets the current logger for the executing pipeline step. @@ -39,7 +50,7 @@ public static ILogger CurrentLogger /// public ILogger CreateLogger(string categoryName) => - new PipelineLogger(() => CurrentLogger); + new PipelineLogger(() => CurrentLogger, _options); /// public void Dispose() @@ -61,9 +72,9 @@ private sealed class StepLoggerHolder /// /// This logger acts as a proxy and dynamically resolves the current logger on each operation, /// allowing the target logger to change between calls. - /// When logging exceptions, stack traces are only included when the log level is Debug or Trace. + /// When logging exceptions, stack traces are only included when the configured minimum log level is Debug or Trace. /// - private sealed class PipelineLogger(Func currentLoggerAccessor) : ILogger + private sealed class PipelineLogger(Func currentLoggerAccessor, PipelineLoggingOptions options) : ILogger { /// public IDisposable? BeginScope(TState state) where TState : notnull => @@ -78,9 +89,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { var logger = currentLoggerAccessor(); - // If there's an exception and the log level is not Debug or Trace, exclude the exception from the log + // If there's an exception and we should not include exception details, exclude the exception from the log // to avoid cluttering production logs with stack traces - if (exception is not null && logLevel >= LogLevel.Debug) + if (exception is not null && !options.IncludeExceptionDetails) { logger.Log(logLevel, eventId, state, null, formatter); } diff --git a/src/Aspire.Hosting/Pipelines/PipelineLoggingOptions.cs b/src/Aspire.Hosting/Pipelines/PipelineLoggingOptions.cs new file mode 100644 index 00000000000..22fe7609506 --- /dev/null +++ b/src/Aspire.Hosting/Pipelines/PipelineLoggingOptions.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging; + +namespace Aspire.Hosting.Pipelines; + +/// +/// Options for controlling logging behavior in the pipeline. +/// +internal sealed class PipelineLoggingOptions +{ + /// + /// Gets or sets the minimum log level for pipeline logging. + /// + public LogLevel MinimumLogLevel { get; set; } = LogLevel.Information; + + /// + /// Gets a value indicating whether exception details (stack traces) should be included in logs. + /// + /// + /// Exception details are included when the minimum log level is Debug or Trace. + /// + public bool IncludeExceptionDetails => MinimumLogLevel <= LogLevel.Debug; +} diff --git a/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs b/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs index 4321b78975d..9cfd63df4b4 100644 --- a/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs +++ b/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs @@ -8,11 +8,17 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Options; namespace Aspire.Hosting.Tests.Pipelines; public class PipelineLoggerProviderTests { + private static PipelineLoggerProvider CreateProvider(LogLevel minimumLogLevel = LogLevel.Information) + { + var options = Options.Create(new PipelineLoggingOptions { MinimumLogLevel = minimumLogLevel }); + return new PipelineLoggerProvider(options); + } [Fact] public void CurrentLogger_WhenNotSet_ReturnsNullLogger() { @@ -72,7 +78,7 @@ public void CurrentLogger_WhenSetToNull_ReturnsNullLogger() public void CreateLogger_ReturnsValidLogger() { // Arrange - var provider = new PipelineLoggerProvider(); + var provider = CreateProvider(); // Act var logger = provider.CreateLogger("TestCategory"); @@ -87,7 +93,7 @@ public async Task CurrentLogger_IsAsyncLocal_IsolatedBetweenTasks() // Arrange var fakeLogger1 = new FakeLogger(); var fakeLogger2 = new FakeLogger(); - var provider = new PipelineLoggerProvider(); + var provider = CreateProvider(); // Act var task1 = Task.Run(async () => From 422208dd84c0d45f572f05e203f17e0944281f94 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 Oct 2025 18:24:27 -0700 Subject: [PATCH 07/12] Fix logging configuration to use IOptions for PipelineLoggingOptions --- src/Aspire.Hosting/DistributedApplicationBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index 30b1d2a5cbe..e8f1731a30e 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -465,9 +465,9 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) _innerBuilder.Services.AddSingleton(); // Configure logging filter using the PipelineLoggingOptions - _innerBuilder.Services.AddOptions().Configure((filterLoggingOptions, pipelineLoggingOptions) => + _innerBuilder.Services.AddOptions().Configure>((filterLoggingOptions, pipelineLoggingOptions) => { - filterLoggingOptions.AddFilter((level) => level >= pipelineLoggingOptions.MinimumLogLevel); + filterLoggingOptions.AddFilter((level) => level >= pipelineLoggingOptions.Value.MinimumLogLevel); }); // Register IDeploymentStateManager based on execution context From 582c9ca4339795484a2449fdad884d3b599f2c2f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 Oct 2025 19:41:23 -0700 Subject: [PATCH 08/12] Update src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs --- src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs index 0cd75d582da..747ef951320 100644 --- a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs +++ b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs @@ -91,14 +91,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except // If there's an exception and we should not include exception details, exclude the exception from the log // to avoid cluttering production logs with stack traces - if (exception is not null && !options.IncludeExceptionDetails) - { - logger.Log(logLevel, eventId, state, null, formatter); - } - else - { - logger.Log(logLevel, eventId, state, exception, formatter); - } + logger.Log(logLevel, eventId, state, options.IncludeExceptionDetails ? exception : null, formatter); } } } \ No newline at end of file From 658d998c9144c7c1f5d1a76652168133459a1ef2 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 Oct 2025 19:55:28 -0700 Subject: [PATCH 09/12] Refactor PipelineLoggerProvider and PipelineStepContext to remove IOptions dependency and simplify logger initialization --- .../Pipelines/PipelineLoggerProvider.cs | 25 +++---------------- .../Pipelines/PipelineStepContext.cs | 12 ++++++--- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs index 747ef951320..5e09440c5fb 100644 --- a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs +++ b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; namespace Aspire.Hosting.Pipelines; @@ -17,16 +16,6 @@ namespace Aspire.Hosting.Pipelines; internal sealed class PipelineLoggerProvider : ILoggerProvider { private static readonly AsyncLocal s_currentLogger = new(); - private readonly PipelineLoggingOptions _options; - - /// - /// Initializes a new instance of the class. - /// - /// The pipeline logging options. - public PipelineLoggerProvider(IOptions options) - { - _options = options.Value; - } /// /// Gets or sets the current logger for the executing pipeline step. @@ -50,7 +39,7 @@ public static ILogger CurrentLogger /// public ILogger CreateLogger(string categoryName) => - new PipelineLogger(() => CurrentLogger, _options); + new PipelineLogger(() => CurrentLogger); /// public void Dispose() @@ -74,7 +63,7 @@ private sealed class StepLoggerHolder /// allowing the target logger to change between calls. /// When logging exceptions, stack traces are only included when the configured minimum log level is Debug or Trace. /// - private sealed class PipelineLogger(Func currentLoggerAccessor, PipelineLoggingOptions options) : ILogger + private sealed class PipelineLogger(Func currentLoggerAccessor) : ILogger { /// public IDisposable? BeginScope(TState state) where TState : notnull => @@ -85,13 +74,7 @@ public bool IsEnabled(LogLevel logLevel) => currentLoggerAccessor().IsEnabled(logLevel); /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - var logger = currentLoggerAccessor(); - - // If there's an exception and we should not include exception details, exclude the exception from the log - // to avoid cluttering production logs with stack traces - logger.Log(logLevel, eventId, state, options.IncludeExceptionDetails ? exception : null, formatter); - } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => + currentLoggerAccessor().Log(logLevel, eventId, state, exception, formatter); } } \ No newline at end of file diff --git a/src/Aspire.Hosting/Pipelines/PipelineStepContext.cs b/src/Aspire.Hosting/Pipelines/PipelineStepContext.cs index 5056b8bdf0c..d5653f31575 100644 --- a/src/Aspire.Hosting/Pipelines/PipelineStepContext.cs +++ b/src/Aspire.Hosting/Pipelines/PipelineStepContext.cs @@ -3,7 +3,9 @@ using System.Diagnostics.CodeAnalysis; using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Aspire.Hosting.Pipelines; @@ -45,10 +47,12 @@ public sealed class PipelineStepContext /// public IServiceProvider Services => PipelineContext.Services; + internal PipelineLoggingOptions PipelineLoggingOptions => Services.GetRequiredService>().Value; + /// /// Gets the logger for pipeline operations that writes to both the pipeline logger and the step logger. /// - public ILogger Logger => field ??= new StepLogger(ReportingStep); + public ILogger Logger => field ??= new StepLogger(ReportingStep, PipelineLoggingOptions); /// /// Gets the cancellation token for the pipeline operation. @@ -65,9 +69,10 @@ public sealed class PipelineStepContext /// A logger that writes to the step logger. /// [Experimental("ASPIREPUBLISHERS001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] -internal sealed class StepLogger(IReportingStep step) : ILogger +internal sealed class StepLogger(IReportingStep step, PipelineLoggingOptions options) : ILogger { private readonly IReportingStep _step = step; + private readonly PipelineLoggingOptions _options = options; public IDisposable? BeginScope(TState state) where TState : notnull { @@ -83,7 +88,8 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { // Also log to the step logger (for publishing output display) var message = formatter(state, exception); - if (exception != null) + + if (_options.IncludeExceptionDetails && exception != null) { message = $"{message} {exception}"; } From 0981d18fb2bab3d753e2ae062ccedc33bdedd86b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 Oct 2025 20:01:12 -0700 Subject: [PATCH 10/12] Change log level from Information to Debug for deployment state save confirmation --- .../Publishing/Internal/FileDeploymentStateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs index c3ff3831a2e..5a2f9c59097 100644 --- a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs +++ b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs @@ -69,7 +69,7 @@ await File.WriteAllTextAsync( flattenedSecrets.ToJsonString(s_jsonSerializerOptions), cancellationToken).ConfigureAwait(false); - logger.LogInformation("Deployment state saved to {Path}", deploymentStatePath); + logger.LogDebug("Deployment state saved to {Path}", deploymentStatePath); } catch (Exception ex) { From c6e1435449e4e9fc276c41fcaab77dde09818f93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:24:18 +0000 Subject: [PATCH 11/12] Fix PipelineLoggerProviderTests to use parameterless constructor Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../Pipelines/PipelineLoggerProviderTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs b/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs index 9cfd63df4b4..0f935ba4686 100644 --- a/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs +++ b/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs @@ -8,16 +8,14 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Extensions.Options; namespace Aspire.Hosting.Tests.Pipelines; public class PipelineLoggerProviderTests { - private static PipelineLoggerProvider CreateProvider(LogLevel minimumLogLevel = LogLevel.Information) + private static PipelineLoggerProvider CreateProvider() { - var options = Options.Create(new PipelineLoggingOptions { MinimumLogLevel = minimumLogLevel }); - return new PipelineLoggerProvider(options); + return new PipelineLoggerProvider(); } [Fact] public void CurrentLogger_WhenNotSet_ReturnsNullLogger() From 677e52ec0acd4365a30ea50b49ad09a551ed9fa2 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 Oct 2025 20:35:31 -0700 Subject: [PATCH 12/12] Apply suggestions from code review --- .../Pipelines/PipelineLoggerProviderTests.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs b/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs index 0f935ba4686..4321b78975d 100644 --- a/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs +++ b/tests/Aspire.Hosting.Tests/Pipelines/PipelineLoggerProviderTests.cs @@ -13,10 +13,6 @@ namespace Aspire.Hosting.Tests.Pipelines; public class PipelineLoggerProviderTests { - private static PipelineLoggerProvider CreateProvider() - { - return new PipelineLoggerProvider(); - } [Fact] public void CurrentLogger_WhenNotSet_ReturnsNullLogger() { @@ -76,7 +72,7 @@ public void CurrentLogger_WhenSetToNull_ReturnsNullLogger() public void CreateLogger_ReturnsValidLogger() { // Arrange - var provider = CreateProvider(); + var provider = new PipelineLoggerProvider(); // Act var logger = provider.CreateLogger("TestCategory"); @@ -91,7 +87,7 @@ public async Task CurrentLogger_IsAsyncLocal_IsolatedBetweenTasks() // Arrange var fakeLogger1 = new FakeLogger(); var fakeLogger2 = new FakeLogger(); - var provider = CreateProvider(); + var provider = new PipelineLoggerProvider(); // Act var task1 = Task.Run(async () =>