File: BackEnd\Components\Logging\EventSourceSink.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 System;
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
 
using InternalLoggerException = Microsoft.Build.Exceptions.InternalLoggerException;
 
namespace Microsoft.Build.BackEnd.Logging
{
    /// <summary>
    /// This class raises events on behalf of the build engine to all registered loggers.
    /// </summary>
    internal sealed class EventSourceSink :
#if FEATURE_APPDOMAIN
        MarshalByRefObject,
#endif
        IEventSource4, IBuildEventSink
    {
        #region Events
 
        /// <summary>
        /// This event is raised to log a message.
        /// </summary>
        public event BuildMessageEventHandler? MessageRaised;
 
        /// <summary>
        /// This event is raised to log an error.
        /// </summary>
        public event BuildErrorEventHandler? ErrorRaised;
 
        /// <summary>
        /// This event is raised to log a warning.
        /// </summary>
        public event BuildWarningEventHandler? WarningRaised;
 
        /// <summary>
        /// this event is raised to log the start of a build
        /// </summary>
        public event BuildStartedEventHandler? BuildStarted;
 
        /// <summary>
        /// this event is raised to log the end of a build
        /// </summary>
        public event BuildFinishedEventHandler? BuildFinished;
 
        /// <summary>
        /// this event is raised to log the start of a project build
        /// </summary>
        public event ProjectStartedEventHandler? ProjectStarted;
 
        /// <summary>
        /// this event is raised to log the end of a project build
        /// </summary>
        public event ProjectFinishedEventHandler? ProjectFinished;
 
        /// <summary>
        /// this event is raised to log the start of a target build
        /// </summary>
        public event TargetStartedEventHandler? TargetStarted;
 
        /// <summary>
        /// this event is raised to log the end of a target build
        /// </summary>
        public event TargetFinishedEventHandler? TargetFinished;
 
        /// <summary>
        /// this event is raised to log the start of task execution
        /// </summary>
        public event TaskStartedEventHandler? TaskStarted;
 
        /// <summary>
        /// this event is raised to log the end of task execution
        /// </summary>
        public event TaskFinishedEventHandler? TaskFinished;
 
        /// <summary>
        /// this event is raised to log a custom event
        /// </summary>
        public event CustomBuildEventHandler? CustomEventRaised;
 
        /// <summary>
        /// this event is raised to log build status events, such as
        /// build/project/target/task started/stopped
        /// </summary>
        public event BuildStatusEventHandler? StatusEventRaised;
 
        /// <summary>
        /// This event is raised to log that some event has
        /// occurred.  It is raised on every event.
        /// </summary>
        public event AnyEventHandler? AnyEventRaised;
 
        /// <summary>
        /// This event is raised to log telemetry.
        /// </summary>
        public event TelemetryEventHandler? TelemetryLogged;
 
        /// <summary>
        /// This event is raised to log BuildCheck events.
        /// </summary>
        internal event BuildCheckEventHandler? BuildCheckEventRaised;
        #endregion
 
        #region Properties
        /// <summary>
        /// Provide a friendly name for the sink to make it easier to differentiate during
        /// debugging and display
        /// </summary>
        public string? Name
        {
            get;
            set;
        }
 
        /// <summary>
        /// Has the sink logged the BuildStartedEvent. This is important to know because we only want to log the build started event once
        /// </summary>
        public bool HaveLoggedBuildStartedEvent
        {
            get;
            set;
        }
 
        /// <summary>
        /// Has the sink logged the BuildFinishedEvent. This is important to know because we only want to log the build finished event once
        /// </summary>
        public bool HaveLoggedBuildFinishedEvent
        {
            get;
            set;
        }
 
        /// <summary>
        /// Should evaluation events include generated metaprojects?
        /// </summary>
        public bool IncludeEvaluationMetaprojects
        {
            get;
            private set;
        }
 
        /// <summary>
        /// Should evaluation events include profiling information?
        /// </summary>
        public bool IncludeEvaluationProfiles
        {
            get;
            private set;
        }
 
        /// <summary>
        /// Should task events include task inputs?
        /// </summary>
        public bool IncludeTaskInputs
        {
            get;
            private set;
        }
 
        /// <summary>
        /// Determines whether properties and items should be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>
        /// instead of <see cref="ProjectStartedEventArgs"/>
        /// </summary>
        public bool IncludeEvaluationPropertiesAndItems
        {
            get;
            private set;
        }
 
        #endregion
 
        #region Methods
 
        #region IEventSource3 Methods
 
        void IEventSource3.IncludeEvaluationMetaprojects()
        {
            IncludeEvaluationMetaprojects = true;
        }
 
        void IEventSource3.IncludeEvaluationProfiles()
        {
            IncludeEvaluationProfiles = true;
        }
 
        void IEventSource3.IncludeTaskInputs()
        {
            IncludeTaskInputs = true;
        }
 
        #endregion
 
        #region IEventSource4 Methods
 
        void IEventSource4.IncludeEvaluationPropertiesAndItems()
        {
            IncludeEvaluationPropertiesAndItems = true;
        }
 
        #endregion
 
        #region IEventSink Methods
 
        /// <summary>
        /// Raises the given event to all registered loggers.
        /// This method casts the events extracted from the queue to a more specific type.
        /// </summary>
        /// <param name="buildEvent">BuildEventArgs</param>
        /// <param name="sinkId">Note this is not used in the eventsource sink</param>
        public void Consume(BuildEventArgs buildEvent, int sinkId)
        {
            Consume(buildEvent);
        }
 
        /// <summary>
        /// Raises the given event to all registered loggers.
        /// This method casts the events extracted from the queue to a more specific type.
        /// </summary>
        public void Consume(BuildEventArgs buildEvent)
        {
 
            switch (buildEvent)
            {
                case BuildMessageEventArgs buildMessageEvent:
                    RaiseEvent(buildMessageEvent, args => MessageRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
                case TaskStartedEventArgs taskStartedEvent:
                    ArgsHandler<TaskStartedEventArgs> taskStartedFollowUp = args => RaiseEvent(args, args=> StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(taskStartedEvent, args => TaskStarted?.Invoke(null, args), taskStartedFollowUp);
                    break;
                case TaskFinishedEventArgs taskFinishedEvent:
                    ArgsHandler<TaskFinishedEventArgs> taskFinishedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(taskFinishedEvent, args => TaskFinished?.Invoke(null, args), taskFinishedFollowUp);
                    break;
                case TargetStartedEventArgs targetStartedEvent:
                    ArgsHandler<TargetStartedEventArgs> targetStartedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(targetStartedEvent, args => TargetStarted?.Invoke(null, args), targetStartedFollowUp);
                    break;
                case TargetFinishedEventArgs targetFinishedEvent:
                    ArgsHandler<TargetFinishedEventArgs> targetFinishedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(targetFinishedEvent, args => TargetFinished?.Invoke(null, args), targetFinishedFollowUp);
                    break;
                case ProjectStartedEventArgs projectStartedEvent:
                    ArgsHandler<ProjectStartedEventArgs> projectStartedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(projectStartedEvent, args => ProjectStarted?.Invoke(null, args), projectStartedFollowUp);
                    break;
                case ProjectFinishedEventArgs projectFinishedEvent:
                    ArgsHandler<ProjectFinishedEventArgs> projectFinishedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(projectFinishedEvent, args => ProjectFinished?.Invoke(null, args), projectFinishedFollowUp);
                    break;
                case BuildStartedEventArgs buildStartedEvent:
                    HaveLoggedBuildStartedEvent = true;
                    ArgsHandler<BuildStartedEventArgs> BuildStartedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(buildStartedEvent, args => BuildStarted?.Invoke(null, args), BuildStartedFollowUp);
                    break;
                case BuildFinishedEventArgs buildFinishedEvent:
                    HaveLoggedBuildFinishedEvent = true;
                    ArgsHandler<BuildFinishedEventArgs> BuildFinishedFollowUp = args => RaiseEvent(args, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    RaiseEvent(buildFinishedEvent, args => BuildFinished?.Invoke(null, args), BuildFinishedFollowUp);
                    break;
                case BuildCanceledEventArgs buildCanceledEvent:
 
                    RaiseEvent(buildCanceledEvent, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
                case CustomBuildEventArgs customBuildEvent:
                    RaiseEvent(customBuildEvent, args => CustomEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
                case BuildStatusEventArgs buildStatusEvent:
                    RaiseEvent(buildStatusEvent, args => StatusEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
                case BuildWarningEventArgs buildWarningEvent:
                    RaiseEvent(buildWarningEvent, args => WarningRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
                case BuildErrorEventArgs buildErrorEvent:
                    RaiseEvent(buildErrorEvent, args => ErrorRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
                case TelemetryEventArgs telemetryEvent:
                    RaiseEvent(telemetryEvent, args => TelemetryLogged?.Invoke(null, args), null);
                    break;
                case BuildCheckEventArgs buildCheckEvent:
                    RaiseEvent(buildCheckEvent, args => BuildCheckEventRaised?.Invoke(null, args), RaiseAnyEvent);
                    break;
 
                default:
                    ErrorUtilities.ThrowInternalError("Unknown event args type.");
                    break;
            }
        }
 
        /// <summary>
        /// Shutdown and displose of any resource this object is holding onto.
        /// </summary>
        public void ShutDown()
        {
            UnregisterAllEventHandlers();
        }
        #endregion
 
        #region Internal Methods
 
        /// <summary>
        /// Clears out all events.
        /// </summary>
        internal void UnregisterAllEventHandlers()
        {
            MessageRaised = null;
            ErrorRaised = null;
            WarningRaised = null;
            BuildStarted = null;
            BuildFinished = null;
            ProjectStarted = null;
            ProjectFinished = null;
            TargetStarted = null;
            TargetFinished = null;
            TaskStarted = null;
            TaskFinished = null;
            CustomEventRaised = null;
            StatusEventRaised = null;
            AnyEventRaised = null;
            TelemetryLogged = null;
            BuildCheckEventRaised = null;
        }
 
        #endregion
 
        #region Private Methods
 
        public delegate void ArgsHandler<in TArgs>(TArgs e) where TArgs : BuildEventArgs;
 
        /// <summary>
        /// Raises a message event to all registered loggers.
        /// </summary>
        /// <param name="buildEvent">event args</param>
        /// <param name="handler">argument handler that invokes the respective event</param>
        /// <param name="followUpHandler"> either anyEvent or statusEvent, it is invoked after the Event has been processed</param>
        /// <exception cref="LoggerException">When EventHandler raises an logger exception the LoggerException is rethrown</exception>
        /// <exception cref="InternalLoggerException">Any exceptions which are not LoggerExceptions are wrapped in an InternalLoggerException</exception>
        /// <exception cref="Exception">ExceptionHandling.IsCriticalException exceptions will not be wrapped</exception>
        private void RaiseEvent<TArgs>(TArgs buildEvent, ArgsHandler<TArgs> handler, ArgsHandler<TArgs>? followUpHandler)
            where TArgs : BuildEventArgs
        {
            try
            {
                handler(buildEvent);
            }
            catch (LoggerException)
            {
                // if a logger has failed politely, abort immediately
                // first unregister all loggers, since other loggers may receive remaining events in unexpected orderings
                // if a fellow logger is throwing in an event handler.
                UnregisterAllEventHandlers();
                throw;
            }
            catch (Exception exception)
            {
                // first unregister all loggers, since other loggers may receive remaining events in unexpected orderings
                // if a fellow logger is throwing in an event handler.
                UnregisterAllEventHandlers();
 
                if (ExceptionHandling.IsCriticalException(exception))
                {
                    throw;
                }
 
                InternalLoggerException.Throw(exception, buildEvent, "FatalErrorWhileLogging", false);
            }
            followUpHandler?.Invoke(buildEvent);
        }
 
        /// <summary>
        /// Raises a catch-all build event to all registered loggers.
        /// Keeping it separate since it also dumps the Exception to file as opposed to all other events.
        /// </summary>
        /// <param name="buildEvent">Build EventArgs</param>
        /// <exception cref="LoggerException">When EventHandler raises an logger exception the LoggerException is rethrown</exception>
        /// <exception cref="InternalLoggerException">Any exceptions which are not LoggerExceptions are wrapped in an InternalLoggerException</exception>
        /// <exception cref="Exception">ExceptionHandling.IsCriticalException exceptions will not be wrapped</exception>
        private void RaiseAnyEvent(BuildEventArgs buildEvent)
        {
            if (AnyEventRaised != null)
            {
                try
                {
                    AnyEventRaised(null, buildEvent);
                }
                catch (LoggerException exception)
                {
                    if (ExceptionHandling.IsCriticalException(exception))
                    {
                        // if a logger has failed politely, abort immediately
                        // first unregister all loggers, since other loggers may receive remaining events in unexpected orderings
                        // if a fellow logger is throwing in an event handler.
                        UnregisterAllEventHandlers();
 
                        // We ought to dump this further up the stack, but if for example a task is logging an event within a
                        // catch(Exception) block and not rethrowing it, there's the possibility that this exception could
                        // just get silently eaten.  So better to have duplicates than to not log the problem at all. :)
                        ExceptionHandling.DumpExceptionToFile(exception);
 
                        throw;
                    }
                }
                catch (Exception exception)
                {
                    // We ought to dump this further up the stack, but if for example a task is logging an event within a
                    // catch(Exception) block and not rethrowing it, there's the possibility that this exception could
                    // just get silently eaten.  So better to have duplicates than to not log the problem at all. :)
                    ExceptionHandling.DumpExceptionToFile(exception);
 
                    if (ExceptionHandling.IsCriticalException(exception))
                    {
                        throw;
                    }
 
                    InternalLoggerException.Throw(exception, buildEvent, "FatalErrorWhileLogging", false);
                }
            }
        }
 
        #endregion
        #endregion
    }
}