File: Logging\DistributedLoggers\ConfigurableForwardingLogger.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
 
namespace Microsoft.Build.Logging
{
    /// <summary>
    /// Logger that forwards events to a central logger (e.g ConsoleLogger)
    /// residing on the parent node.
    /// </summary>
    public class ConfigurableForwardingLogger : IForwardingLogger
    {
        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public ConfigurableForwardingLogger()
        { }
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets or sets the level of detail to show in the event log.
        /// </summary>
        /// <value>Verbosity level.</value>
        public LoggerVerbosity Verbosity
        {
            get { return _verbosity; }
            set { _verbosity = value; }
        }
 
        /// <summary>
        /// The console logger takes a single parameter to suppress the output of the errors
        /// and warnings summary at the end of a build.
        /// </summary>
        /// <value>null</value>
        public string? Parameters
        {
            get { return _loggerParameters; }
            set { _loggerParameters = value; }
        }
 
        /// <summary>
        /// This property is set by the build engine to allow a node loggers to forward messages to the
        /// central logger
        /// </summary>
        public IEventRedirector? BuildEventRedirector
        {
            get { return _buildEventRedirector; }
            set { _buildEventRedirector = value; }
        }
 
        /// <summary>
        /// The identifier of the node.
        /// </summary>
        public int NodeId
        {
            get { return _nodeId; }
            set { _nodeId = value; }
        }
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Parses out the logger parameters from the Parameters string.
        /// </summary>
        private void ParseParameters(IEventSource eventSource)
        {
            if (_loggerParameters != null)
            {
                string[] parameterComponents = _loggerParameters.Split(s_parameterDelimiters);
                for (int param = 0; param < parameterComponents.Length; param++)
                {
                    if (parameterComponents[param].Length > 0)
                    {
                        ApplyParameter(eventSource, parameterComponents[param]);
                    }
                }
                // Setting events to forward on the commandline will override the verbosity and other switches such as
                // showPerfSummary and ShowSummary
                if (_forwardingSetFromParameters)
                {
                    _showPerfSummary = false;
                    _showSummary = true;
                }
 
                if (_forwardProjectContext)
                {
                    // We can't know whether the project items needed to find ForwardProjectContextDescription
                    // will be set on ProjectStarted or ProjectEvaluationFinished because we don't know
                    // all of the other loggers that will be attached. So turn both on.
                    eventSource.HandleStatusEventRaised(BuildStatusHandler);
                    eventSource.HandleProjectStarted(ForwardEvent);
                }
            }
        }
 
        /// <summary>
        /// Logger parameters can be used to enable and disable specific event types.
        /// Otherwise, the verbosity is used to choose which events to forward.
        /// </summary>
        private void ApplyParameter(IEventSource eventSource, string parameterName)
        {
            ErrorUtilities.VerifyThrowArgumentNull(parameterName);
 
            bool isEventForwardingParameter = true;
 
            // Careful - we need to brace before double specified parameters - hence the unsubscriptions before subscriptions
            switch (parameterName.ToUpperInvariant())
            {
                case BuildStartedEventDescription:
                    eventSource.HandleBuildStarted(ForwardEvent);
                    break;
                case BuildFinishedEventDescription:
                    eventSource.HandleBuildFinished(ForwardEvent);
                    break;
                case ProjectStartedEventDescription:
                    eventSource.HandleProjectStarted(ForwardEvent);
                    break;
                case ProjectFinishedEventDescription:
                    eventSource.HandleProjectFinished(ForwardEvent);
                    break;
                case TargetStartedEventDescription:
                    eventSource.HandleTargetStarted(ForwardEvent);
                    break;
                case TargetFinishedEventDescription:
                    eventSource.HandleTargetFinished(ForwardEvent);
                    break;
                case TaskStartedEventDescription:
                    eventSource.HandleTaskStarted(ForwardEvent);
                    break;
                case TaskFinishedEventDescription:
                    eventSource.HandleTaskFinished(ForwardEvent);
                    break;
                case ErrorEventDescription:
                    eventSource.HandleErrorRaised(ForwardEvent);
                    break;
                case WarningEventDescription:
                    eventSource.HandleWarningRaised(ForwardEvent);
                    break;
                case CustomEventDescription:
                    eventSource.HandleCustomEventRaised(ForwardEvent);
                    break;
                case HighMessageEventDescription:
                    eventSource.HandleMessageRaised(MessageHandler);
                    _forwardHighImportanceMessages = true;
                    break;
                case NormalMessageEventDescription:
                    eventSource.HandleMessageRaised(MessageHandler);
                    _forwardNormalImportanceMessages = true;
                    break;
                case LowMessageEventDescription:
                    eventSource.HandleMessageRaised(MessageHandler);
                    _forwardLowImportanceMessages = true;
                    break;
                case CommandLineDescription:
                    eventSource.HandleMessageRaised(MessageHandler);
                    _forwardTaskCommandLine = true;
                    break;
                case ProjectEvaluationStartedEventDescription:
                case ProjectEvaluationFinishedEventDescription:
                case ProjectEvaluationEventDescription:
                    eventSource.HandleStatusEventRaised(BuildStatusHandler);
                    break;
                case PerformanceSummaryDescription:
                    _showPerfSummary = true;
                    isEventForwardingParameter = false;
                    break;
                case NoSummaryDescription:
                    _showSummary = false;
                    isEventForwardingParameter = false;
                    break;
                case ShowCommandLineDescription:
                    _showCommandLine = true;
                    isEventForwardingParameter = false;
                    break;
                case ForwardProjectContextDescription:
                    _forwardProjectContext = true;
                    isEventForwardingParameter = false;
                    break;
                case RespectVerbosityDescription:
                    _respectVerbosity = true;
                    isEventForwardingParameter = false;
                    break;
                default:
                    isEventForwardingParameter = false;
                    break;
            }
 
            if (isEventForwardingParameter)
            {
                _forwardingSetFromParameters = true;
            }
        }
 
        /// <summary>
        /// Signs up the console logger for all build events.
        /// </summary>
        public virtual void Initialize(IEventSource eventSource)
        {
            ErrorUtilities.VerifyThrowArgumentNull(eventSource);
 
            ParseParameters(eventSource);
 
            if (_respectVerbosity || !_forwardingSetFromParameters)
            {
                SetForwardingBasedOnVerbosity(eventSource);
            }
        }
 
        /// <summary>
        /// Signs up the console logger for all build events.
        /// </summary>
        public void Initialize(IEventSource eventSource, int nodeCount)
        {
            Initialize(eventSource);
        }
 
        private void SetForwardingBasedOnVerbosity(IEventSource eventSource)
        {
            eventSource.HandleBuildStarted(ForwardEvent);
            eventSource.HandleBuildFinished(ForwardEvent);
 
            if (IsVerbosityAtLeast(LoggerVerbosity.Quiet))
            {
                eventSource.HandleErrorRaised(ForwardEvent);
                eventSource.HandleWarningRaised(ForwardEvent);
            }
 
            if (IsVerbosityAtLeast(LoggerVerbosity.Minimal))
            {
                eventSource.HandleMessageRaised(MessageHandler);
                _forwardHighImportanceMessages = true;
            }
 
            if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
            {
                // MessageHandler already subscribed
                _forwardNormalImportanceMessages = true;
                _forwardTaskCommandLine = true;
 
                eventSource.HandleProjectStarted(ForwardEvent);
                eventSource.HandleProjectFinished(ForwardEvent);
                eventSource.HandleTargetStarted(ForwardEvent);
                eventSource.HandleTargetFinished(ForwardEvent);
            }
 
            if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
            {
                eventSource.HandleTaskStarted(ForwardEvent);
                eventSource.HandleTaskFinished(ForwardEvent);
 
                // MessageHandler already subscribed
                _forwardLowImportanceMessages = true;
            }
 
            if (IsVerbosityAtLeast(LoggerVerbosity.Diagnostic))
            {
                eventSource.HandleCustomEventRaised(ForwardEvent);
                eventSource.HandleStatusEventRaised(BuildStatusHandler);
            }
 
            if (_showSummary)
            {
                eventSource.HandleErrorRaised(ForwardEvent);
                eventSource.HandleWarningRaised(ForwardEvent);
            }
 
            if (_showPerfSummary)
            {
                eventSource.HandleTaskStarted(ForwardEvent);
                eventSource.HandleTaskFinished(ForwardEvent);
                eventSource.HandleTargetStarted(ForwardEvent);
                eventSource.HandleTargetFinished(ForwardEvent);
                eventSource.HandleProjectStarted(ForwardEvent);
                eventSource.HandleProjectFinished(ForwardEvent);
                eventSource.HandleStatusEventRaised(BuildStatusHandler);
            }
 
            if (_showCommandLine)
            {
                // Prevent double subscribe
                eventSource.MessageRaised -= MessageHandler;
                eventSource.MessageRaised += MessageHandler;
 
                _forwardTaskCommandLine = true;
            }
        }
 
        /// <summary>
        /// Returns the minimum importance of messages logged by this logger.
        /// Forwarding logger might be configured to forward messages of particular importance regardless of the verbosity level of said logger.
        /// This method properly reflects that.
        /// </summary>
        /// <returns>
        /// The minimum message importance corresponding to this logger's verbosity or configuration of forwarding of messages of particular importance level.
        /// If this logger is not configured to forward messages of any importance and verbosity is not explicitly set, then (MessageImportance.High - 1) is returned.
        /// </returns>
        internal MessageImportance GetMinimumMessageImportance()
        {
            if (_forwardLowImportanceMessages)
            {
                return MessageImportance.Low;
            }
            if (_forwardNormalImportanceMessages)
            {
                return MessageImportance.Normal;
            }
            if (_forwardHighImportanceMessages)
            {
                return MessageImportance.High;
            }
            // The logger does not log messages of any importance.
            return MessageImportance.High - 1;
        }
 
        /// <summary>
        /// Called when Engine is done with this logger
        /// </summary>
        public virtual void Shutdown()
        {
            // Nothing to do
        }
 
        /// <summary>
        /// Handler for build events
        /// </summary>
        /// <param name="sender">sender (should be null)</param>
        /// <param name="e">event arguments</param>
        private void ForwardEvent(object sender, BuildEventArgs e)
        {
            ForwardToCentralLogger(e);
        }
 
        private void BuildStatusHandler(object sender, BuildStatusEventArgs e)
        {
            if (e is ProjectEvaluationStartedEventArgs || e is ProjectEvaluationFinishedEventArgs)
            {
                ForwardToCentralLogger(e);
            }
        }
 
        /// <summary>
        /// Tailored handler for BuildMessageEventArgs - fine tunes forwarding of messages.
        /// </summary>
        /// <param name="sender">sender (should be null)</param>
        /// <param name="e">event arguments</param>
        private void MessageHandler(object sender, BuildMessageEventArgs e)
        {
            bool forwardEvent =
                (_forwardLowImportanceMessages && e.Importance == MessageImportance.Low) ||
                (_forwardNormalImportanceMessages && e.Importance == MessageImportance.Normal) ||
                (_forwardHighImportanceMessages && e.Importance == MessageImportance.High) ||
                (_forwardTaskCommandLine && e is TaskCommandLineEventArgs);
 
            if (forwardEvent)
            {
                ForwardToCentralLogger(e);
            }
        }
 
        /// <summary>
        /// Forwards the specified event.
        /// </summary>
        /// <param name="e">The <see cref="BuildEventArgs"/> to forward.</param>
        protected virtual void ForwardToCentralLogger(BuildEventArgs e)
        {
            _buildEventRedirector?.ForwardEvent(e);
        }
 
        /// <summary>
        /// Determines whether the current verbosity setting is at least the value
        /// passed in.
        /// </summary>
        private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
        {
            return _verbosity >= checkVerbosity;
        }
        #endregion
 
        #region Private member data
 
        /// <summary>
        /// Controls the amount of text displayed by the logger
        /// </summary>
        private LoggerVerbosity _verbosity = LoggerVerbosity.Normal;
 
        /// <summary>
        /// Console logger parameters.
        /// </summary>
        private string? _loggerParameters = null;
 
        /// <summary>
        /// Console logger parameters delimiters.
        /// </summary>
        private static readonly char[] s_parameterDelimiters = MSBuildConstants.SemicolonChar;
 
        /// <summary>
        /// Strings that users of this logger can pass in to enable specific events or logger output.
        /// Also used as keys into our dictionary.
        /// </summary>
        private const string BuildStartedEventDescription = "BUILDSTARTEDEVENT";
        private const string BuildFinishedEventDescription = "BUILDFINISHEDEVENT";
        private const string ProjectStartedEventDescription = "PROJECTSTARTEDEVENT";
        private const string ProjectFinishedEventDescription = "PROJECTFINISHEDEVENT";
        private const string ProjectEvaluationEventDescription = "PROJECTEVALUATIONEVENT";
        private const string ProjectEvaluationStartedEventDescription = "PROJECTEVALUATIONSTARTEDEVENT";
        private const string ProjectEvaluationFinishedEventDescription = "PROJECTEVALUATIONFINISHEDEVENT";
        private const string TargetStartedEventDescription = "TARGETSTARTEDEVENT";
        private const string TargetFinishedEventDescription = "TARGETFINISHEDEVENT";
        private const string TaskStartedEventDescription = "TASKSTARTEDEVENT";
        private const string TaskFinishedEventDescription = "TASKFINISHEDEVENT";
        private const string ErrorEventDescription = "ERROREVENT";
        private const string WarningEventDescription = "WARNINGEVENT";
        private const string HighMessageEventDescription = "HIGHMESSAGEEVENT";
        private const string NormalMessageEventDescription = "NORMALMESSAGEEVENT";
        private const string LowMessageEventDescription = "LOWMESSAGEEVENT";
        private const string CustomEventDescription = "CUSTOMEVENT";
        private const string CommandLineDescription = "COMMANDLINE";
        private const string PerformanceSummaryDescription = "PERFORMANCESUMMARY";
        private const string NoSummaryDescription = "NOSUMMARY";
        private const string ShowCommandLineDescription = "SHOWCOMMANDLINE";
        private const string ForwardProjectContextDescription = "FORWARDPROJECTCONTEXTEVENTS";
        private const string RespectVerbosityDescription = "RESPECTVERBOSITY";
 
        #region Per-build Members
 
        /// <summary>
        /// A pointer to the central logger
        /// </summary>
        private IEventRedirector? _buildEventRedirector;
 
        /// <summary>
        /// Indicates if the events to forward are being set by the parameters sent to the logger
        /// if this is false the events to forward are based on verbosity else verbosity settings will be ignored
        /// </summary>
        private bool _forwardingSetFromParameters;
 
        /// <summary>
        /// Indicates if the parameters explicitly specified respecting of the verbosity (forwarding will
        ///  be set based on verbosity, in addition to explicitly configured forwarding via parameters).
        /// </summary>
        private bool _respectVerbosity;
 
        /// <summary>
        /// Indicates if the events to forward should include project context events, if not
        /// overridden by individual-event forwarding in <see cref="_forwardingSetFromParameters"/>.
        /// </summary>
        private bool _forwardProjectContext = false;
 
        /// <summary>
        /// Console logger should show error and warning summary at the end of build?
        /// </summary>
        private bool _showSummary = true;
 
        /// <summary>
        /// When true, accumulate performance numbers.
        /// </summary>
        private bool _showPerfSummary = false;
 
        /// <summary>
        /// When true the commandline message is sent
        /// </summary>
        private bool _showCommandLine = false;
 
        /// <summary>
        /// Fine-tuning of BuildMessageEventArgs forwarding
        /// </summary>
        private bool _forwardLowImportanceMessages;
 
        /// <summary>
        /// Fine-tuning of BuildMessageEventArgs forwarding
        /// </summary>
        private bool _forwardNormalImportanceMessages;
 
        /// <summary>
        /// Fine-tuning of BuildMessageEventArgs forwarding
        /// </summary>
        private bool _forwardHighImportanceMessages;
 
        /// <summary>
        /// Fine-tuning of BuildMessageEventArgs forwarding
        /// </summary>
        private bool _forwardTaskCommandLine;
 
        /// <summary>
        /// Id of the node the logger is attached to
        /// </summary>
        private int _nodeId;
 
        #endregion
        #endregion
    }
}