| File: RestoreCommand\Logging\RestoreCollectorLogger.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Commands\NuGet.Commands.csproj (NuGet.Commands) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable disable using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using NuGet.Common; using NuGet.Packaging.Signing; using NuGet.ProjectModel; using NuGet.Shared; namespace NuGet.Commands { public class RestoreCollectorLogger : LoggerBase, ICollectorLogger { private readonly ILogger _innerLogger; private readonly ConcurrentQueue<IRestoreLogMessage> _errors; private readonly ConcurrentQueue<IRestoreLogMessage> _suppressedWarnings; private readonly bool _hideWarningsAndErrors; private IEnumerable<RestoreTargetGraph> _restoreTargetGraphs; private PackageSpec _projectSpec; private WarningPropertiesCollection _transitiveWarningPropertiesCollection; public string ProjectPath => _projectSpec?.RestoreMetadata?.ProjectPath; public IEnumerable<IRestoreLogMessage> Errors => _errors.ToArray(); internal IEnumerable<IRestoreLogMessage> SuppressedWarnings => _suppressedWarnings.ToArray(); internal WarningPropertiesCollection ProjectWarningPropertiesCollection { get; set; } internal WarningPropertiesCollection TransitiveWarningPropertiesCollection { get { if (_transitiveWarningPropertiesCollection == null) { // Populate TransitiveWarningPropertiesCollection only if it is null and we have RestoreTargetGraphs. // This will happen at most once and only if we have the project spec with restore metadata. if (_restoreTargetGraphs != null && _restoreTargetGraphs.Any() && _projectSpec != null && _projectSpec.RestoreMetadata != null) { TransitiveWarningPropertiesCollection = TransitiveNoWarnUtils.CreateTransitiveWarningPropertiesCollection( _restoreTargetGraphs, _projectSpec); } } return _transitiveWarningPropertiesCollection; } set => _transitiveWarningPropertiesCollection = value; } /// <summary> /// Stores a reference to PackageSpec for the project from the restore request. /// This are used to generate the warning properties for the project. /// </summary> /// <param name="projectSpec">PackageSpec to be stored for reference.</param> public void ApplyRestoreInputs(PackageSpec projectSpec) { _projectSpec = projectSpec; ProjectWarningPropertiesCollection = new WarningPropertiesCollection( projectSpec.RestoreMetadata?.ProjectWideWarningProperties, PackageSpecificWarningProperties.CreatePackageSpecificWarningProperties(projectSpec), projectSpec.TargetFrameworks.Select(f => f.TargetAlias).AsList().AsReadOnly() ); } /// <summary> /// Stores a reference to RestoreTargetGraphs from the restore output. /// These graphs are used to generate the transitive warning properties. /// </summary> /// <param name="restoreTargetGraphs">RestoreTargetGraphs to be stored for reference.</param> public void ApplyRestoreOutput(IEnumerable<RestoreTargetGraph> restoreTargetGraphs) { _restoreTargetGraphs = restoreTargetGraphs; } /// <summary> /// Initializes an instance of the <see cref="RestoreCollectorLogger"/>, while still /// delegating all log messages to the inner logger. /// </summary> /// <param name="innerLogger">The inner logger used to delegate the logging.</param> /// <param name="verbosity">Minimum verbosity below which no logs will be passed to the inner logger.</param> /// <param name="hideWarningsAndErrors">If this is true, then errors and warnings will not be passed to inner logger.</param> public RestoreCollectorLogger(ILogger innerLogger, LogLevel verbosity, bool hideWarningsAndErrors) : base(verbosity) { _innerLogger = innerLogger; _errors = new ConcurrentQueue<IRestoreLogMessage>(); _suppressedWarnings = new ConcurrentQueue<IRestoreLogMessage>(); _hideWarningsAndErrors = hideWarningsAndErrors; } /// <summary> /// Initializes an instance of the <see cref="RestoreCollectorLogger"/>, while still /// delegating all log messages to the inner logger. /// </summary> /// <param name="innerLogger">The inner logger used to delegate the logging.</param> /// <param name="hideWarningsAndErrors">If this is false, then errors and warnings will not be passed to inner logger.</param> public RestoreCollectorLogger(ILogger innerLogger, bool hideWarningsAndErrors) : this(innerLogger, LogLevel.Debug, hideWarningsAndErrors) { } /// <summary> /// Initializes an instance of the <see cref="RestoreCollectorLogger"/>, while still /// delegating all log messages to the inner logger. /// </summary> /// <param name="innerLogger">The inner logger used to delegate the logging.</param> /// <param name="verbosity">Minimum verbosity below which no logs will be passed to the inner logger.</param> public RestoreCollectorLogger(ILogger innerLogger, LogLevel verbosity) : this(innerLogger, verbosity, hideWarningsAndErrors: false) { } /// <summary> /// Initializes an instance of the <see cref="RestoreCollectorLogger"/>, while still /// delegating all log messages to the inner logger. /// </summary> /// <param name="innerLogger">The inner logger used to delegate the logging.</param> public RestoreCollectorLogger(ILogger innerLogger) : this(innerLogger, LogLevel.Debug, hideWarningsAndErrors: false) { } public void Log(IRestoreLogMessage message) { // check if the message is a warning and it is suppressed if (!IsWarningSuppressed(message)) { // if the message is not suppressed then check if it needs to be upgraded to an error UpgradeWarningToErrorIfNeeded(message); if (string.IsNullOrEmpty(message.ProjectPath)) { message.ProjectPath = ProjectPath; } if (string.IsNullOrEmpty(message.FilePath)) { message.FilePath = message.ProjectPath; } if (CollectMessage(message.Level)) { _errors.Enqueue(message); } if (DisplayMessage(message)) { _innerLogger.Log(message); } } } public Task LogAsync(IRestoreLogMessage message) { // check if the message is a warning and it is suppressed if (!IsWarningSuppressed(message)) { // if the message is not suppressed then check if it needs to be upgraded to an error UpgradeWarningToErrorIfNeeded(message); if (string.IsNullOrEmpty(message.FilePath)) { message.FilePath = message.ProjectPath ?? ProjectPath; } if (string.IsNullOrEmpty(message.ProjectPath)) { message.ProjectPath = ProjectPath; } if (CollectMessage(message.Level)) { _errors.Enqueue(message); } if (DisplayMessage(message)) { return _innerLogger.LogAsync(message); } } return Task.CompletedTask; } public override void Log(ILogMessage message) { Log(ToRestoreLogMessage(message)); } public override Task LogAsync(ILogMessage message) { return LogAsync(ToRestoreLogMessage(message)); } /// <summary> /// Decides if the log should be passed to the inner logger. /// </summary> /// <param name="message">IRestoreLogMessage to be logged.</param> /// <returns>bool indicating if this message should be logged.</returns> protected bool DisplayMessage(IRestoreLogMessage message) { if (message.Level == LogLevel.Error || message.Level == LogLevel.Warning) { return ((!_hideWarningsAndErrors || message.ShouldDisplay) && message.Level >= VerbosityLevel); } else { return (message.Level >= VerbosityLevel); } } /// <summary> /// This method checks if at least one of the warning properties collections is not null and it suppresses the warning. /// </summary> /// <param name="message">IRestoreLogMessage to be logged.</param> /// <returns>bool indicating if the message should be suppressed.</returns> private bool IsWarningSuppressed(IRestoreLogMessage message) { var isWarningSuppressed = false; if (message.Level == LogLevel.Warning) { // If the ProjectWarningPropertiesCollection is present then test if the warning is suppressed in // project wide no warn or package specific no warn if (ProjectWarningPropertiesCollection?.ApplyNoWarnProperties(message) == true) { isWarningSuppressed = true; } else { // Use transitive warning properties only if the project does not suppress the warning // In transitive warning properties look at only the package specific ones as all properties are per package reference. isWarningSuppressed = TransitiveWarningPropertiesCollection?.ApplyNoWarnProperties(message) == true; } } if (isWarningSuppressed) { _suppressedWarnings.Enqueue(message); } return isWarningSuppressed; } /// <summary> /// This method upgrades the warning to an error if the project wide warning properties have set the code in WarningsAsErrors or /// set TreatWarningsAsErrors to true /// </summary> /// <param name="message">IRestoreLogMessage to be logged as an error or warning.</param> /// <returns>bool indicating if the message should be suppressed.</returns> private void UpgradeWarningToErrorIfNeeded(IRestoreLogMessage message) { ProjectWarningPropertiesCollection?.ApplyWarningAsErrorProperties(message); } private static IRestoreLogMessage ToRestoreLogMessage(ILogMessage message) { if (message is IRestoreLogMessage restoreLogMessage) { return restoreLogMessage; } if (message is SignatureLog signatureLog) { return signatureLog.AsRestoreLogMessage(); } return new RestoreLogMessage(message.Level, message.Code, message.Message) { Time = message.Time, WarningLevel = message.WarningLevel, ProjectPath = message.ProjectPath }; } } }