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); } } diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index aec66987b26..e8f1731a30e 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.Value.MinimumLogLevel); }); // Register IDeploymentStateManager based on execution context diff --git a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs index de58380abdc..5e09440c5fb 100644 --- a/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs +++ b/src/Aspire.Hosting/Pipelines/PipelineLoggerProvider.cs @@ -61,6 +61,7 @@ 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 configured minimum log level is Debug or Trace. /// private sealed class PipelineLogger(Func currentLoggerAccessor) : ILogger { 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/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}"; } diff --git a/src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs b/src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs index 25adda016bd..540a7ff4789 100644 --- a/src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs +++ b/src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs @@ -82,7 +82,7 @@ private async Task ExecuteContainerCommandAsync( { var spec = CreateProcessSpec(arguments); - _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) @@ -131,7 +131,7 @@ protected async Task 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..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) {