File: BuildCheck\Infrastructure\BuildCheckConnectorLogger.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 System.Collections.Generic;
using System.Linq;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Experimental.BuildCheck.Acquisition;
using Microsoft.Build.Experimental.BuildCheck.Utilities;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
 
namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
 
internal sealed class BuildCheckConnectorLogger : ILogger
{
    private readonly Dictionary<Type, Action<BuildEventArgs>> _eventHandlers;
    private readonly IBuildCheckManager _buildCheckManager;
    private readonly IBuildAnalysisLoggingContextFactory _loggingContextFactory;
 
    internal BuildCheckConnectorLogger(
        IBuildAnalysisLoggingContextFactory loggingContextFactory,
        IBuildCheckManager buildCheckManager)
    {
        _buildCheckManager = buildCheckManager;
        _loggingContextFactory = loggingContextFactory;
        _eventHandlers = GetBuildEventHandlers();
    }
 
    public LoggerVerbosity Verbosity { get; set; }
 
    public string? Parameters { get; set; }
 
    public void Initialize(IEventSource eventSource)
    {
        eventSource.AnyEventRaised += EventSource_AnyEventRaised;
        eventSource.BuildFinished += EventSource_BuildFinished;
 
        if (eventSource is IEventSource3 eventSource3)
        {
            eventSource3.IncludeTaskInputs();
        }
        if (eventSource is IEventSource4 eventSource4)
        {
            eventSource4.IncludeEvaluationPropertiesAndItems();
        }
    }
 
    public void Shutdown()
    {
    }
 
    private void HandleProjectEvaluationFinishedEvent(ProjectEvaluationFinishedEventArgs eventArgs)
    {
        if (!IsMetaProjFile(eventArgs.ProjectFile))
        {
            _buildCheckManager.ProcessEvaluationFinishedEventArgs(
                _loggingContextFactory.CreateLoggingContext(eventArgs.BuildEventContext!),
                eventArgs);
 
            _buildCheckManager.EndProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!);
        }
    }
 
    private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventArgs eventArgs)
    {
        if (!IsMetaProjFile(eventArgs.ProjectFile))
        {
            _buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!, eventArgs.ProjectFile!);
        }
    }
 
    private void HandleBuildCheckTracingEvent(BuildCheckTracingEventArgs eventArgs)
    {
        if (!eventArgs.IsAggregatedGlobalReport)
        {
            _stats.Merge(eventArgs.TracingData, (span1, span2) => span1 + span2);
        }
    }
 
    private void HandleTaskStartedEvent(TaskStartedEventArgs eventArgs)
    {
        _buildCheckManager.ProcessTaskStartedEventArgs(
            _loggingContextFactory.CreateLoggingContext(eventArgs.BuildEventContext!),
            eventArgs);
    }
 
    private void HandleTaskFinishedEvent(TaskFinishedEventArgs eventArgs)
    {
        _buildCheckManager.ProcessTaskFinishedEventArgs(
            _loggingContextFactory.CreateLoggingContext(eventArgs.BuildEventContext!),
            eventArgs);
    }
 
    private void HandleTaskParameterEvent(TaskParameterEventArgs eventArgs)
    {
        _buildCheckManager.ProcessTaskParameterEventArgs(
            _loggingContextFactory.CreateLoggingContext(eventArgs.BuildEventContext!),
            eventArgs);
    }
 
    private bool IsMetaProjFile(string? projectFile) => !string.IsNullOrEmpty(projectFile) && projectFile!.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase);
 
    private void EventSource_AnyEventRaised(object sender, BuildEventArgs e)
    {
        if (_eventHandlers.TryGetValue(e.GetType(), out Action<BuildEventArgs>? handler))
        {
            handler(e);
        }
    }
 
    private readonly Dictionary<string, TimeSpan> _stats = new Dictionary<string, TimeSpan>();
 
    private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e)
    {
        LoggingContext loggingContext = _loggingContextFactory.CreateLoggingContext(GetBuildEventContext(e));
 
        _stats.Merge(_buildCheckManager.CreateAnalyzerTracingStats(), (span1, span2) => span1 + span2);
        LogAnalyzerStats(loggingContext);
    }
 
    private void LogAnalyzerStats(LoggingContext loggingContext)
    {
        Dictionary<string, TimeSpan> infraStats = new Dictionary<string, TimeSpan>();
        Dictionary<string, TimeSpan> analyzerStats = new Dictionary<string, TimeSpan>();
 
        foreach (var stat in _stats)
        {
            if (stat.Key.StartsWith(BuildCheckConstants.infraStatPrefix))
            {
                string newKey = stat.Key.Substring(BuildCheckConstants.infraStatPrefix.Length);
                infraStats[newKey] = stat.Value;
            }
            else
            {
                analyzerStats[stat.Key] = stat.Value;
            }
        }
 
        BuildCheckTracingEventArgs statEvent = new BuildCheckTracingEventArgs(_stats, true)
        { BuildEventContext = loggingContext.BuildEventContext };
 
        loggingContext.LogBuildEvent(statEvent);
 
        loggingContext.LogCommentFromText(MessageImportance.Low, $"BuildCheck run times{Environment.NewLine}");
        string infraData = BuildCsvString("Infrastructure run times", infraStats);
        loggingContext.LogCommentFromText(MessageImportance.Low, infraData);
        string analyzerData = BuildCsvString("Analyzer run times", analyzerStats);
        loggingContext.LogCommentFromText(MessageImportance.Low, analyzerData);
    }
 
    private string BuildCsvString(string title, Dictionary<string, TimeSpan> rowData)
    {
        return title + Environment.NewLine + String.Join(Environment.NewLine, rowData.Select(a => $"{a.Key},{a.Value}")) + Environment.NewLine;
    }
 
    private Dictionary<Type, Action<BuildEventArgs>> GetBuildEventHandlers() => new()
    {
        { typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs)e) },
        { typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs)e) },
        { typeof(ProjectStartedEventArgs), (BuildEventArgs e) => _buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) },
        { typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => _buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) },
        { typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => HandleBuildCheckTracingEvent((BuildCheckTracingEventArgs)e) },
        { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData(), GetBuildEventContext(e)) },
        { typeof(TaskStartedEventArgs), (BuildEventArgs e) => HandleTaskStartedEvent((TaskStartedEventArgs)e) },
        { typeof(TaskFinishedEventArgs), (BuildEventArgs e) => HandleTaskFinishedEvent((TaskFinishedEventArgs)e) },
        { typeof(TaskParameterEventArgs), (BuildEventArgs e) => HandleTaskParameterEvent((TaskParameterEventArgs)e) },
    };
 
    private BuildEventContext GetBuildEventContext(BuildEventArgs e) => e.BuildEventContext
        ?? new BuildEventContext(
                BuildEventContext.InvalidNodeId,
                BuildEventContext.InvalidTargetId,
                BuildEventContext.InvalidProjectContextId,
                BuildEventContext.InvalidTaskId);
}