Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Logging;
using Shouldly;
using Xunit;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
Expand Down Expand Up @@ -1046,6 +1047,50 @@ public void LogBuildFinished()
buildEvent = new BuildFinishedEventArgs(string.Empty, null /* no help keyword */, true, service.ProcessedBuildEvent.Timestamp);
Assert.True(((BuildFinishedEventArgs)service.ProcessedBuildEvent).IsEquivalent(buildEvent));
}
[Fact]
public void LogBuildStartedLoggerNames()
{
ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1);
ConsoleLogger consoleLogger = new ConsoleLogger();
service.RegisterLogger(consoleLogger);

service.LogBuildStarted();
var enabledLogsEvent = service.AllProcessedBuildEvents
.OfType<BuildMessageEventArgs>()
.FirstOrDefault(e => e is not LoggerRegisteredEventArgs && e.Message?.Contains("ConsoleLogger") == true);
enabledLogsEvent.ShouldNotBeNull();
}
Comment thread
AlesProkop marked this conversation as resolved.

[Fact]
public void LogFilePathsPresentInFileLog()
{
using var env = TestEnvironment.Create();
var logFilePath = env.ExpectFile(".log").Path;

var fileLogger = new FileLogger { Parameters = "logfile=" + logFilePath };
var mockLogger = new MockLogger();

using (var collection = new ProjectCollection())
{
var project = ObjectModelHelpers.CreateInMemoryProject(collection, @"
<Project>
<Target Name=""Build"" />
</Project>");
project.Build(new ILogger[] { fileLogger, mockLogger }).ShouldBeTrue();
}

// Check that MockLogger captured a LoggerRegisteredEventArgs with the file logger path
var registeredEvent = mockLogger.AllBuildEvents
.OfType<LoggerRegisteredEventArgs>()
.FirstOrDefault(e => e.LoggerName == nameof(FileLogger));
registeredEvent.ShouldNotBeNull();
var expectedPath = Path.GetFullPath(logFilePath);
registeredEvent.OutputFilePath.ShouldBe(expectedPath);

// Check the file log itself contains the exact path
var fileLogContents = File.ReadAllText(logFilePath);
fileLogContents.ShouldContain(expectedPath);
}

[Fact]
public void LogBuildCanceled()
Expand Down Expand Up @@ -1795,6 +1840,11 @@ internal sealed class ProcessBuildEventHelper : LoggingService
/// to verify that a buildEvent was sent to ProcessLoggingEvent.
/// </summary>
private BuildEventArgs _processedBuildEvent;

/// <summary>
/// All events processed by ProcessLoggingEvent.
/// </summary>
internal List<BuildEventArgs> AllProcessedBuildEvents { get; } = new();
#endregion
#region Constructor
/// <summary>
Expand Down Expand Up @@ -1857,6 +1907,7 @@ protected internal override void ProcessLoggingEvent(object buildEvent)
if (buildEvent is BuildEventArgs buildEventArgs)
{
_processedBuildEvent = buildEventArgs;
AllProcessedBuildEvents.Add(buildEventArgs);
}
else if (buildEvent is KeyValuePair<int, BuildEventArgs> kvp)
{
Expand Down
64 changes: 64 additions & 0 deletions src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Profiler;
using Microsoft.Build.Logging;
using Microsoft.Build.Shared;

using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
Expand Down Expand Up @@ -360,6 +361,13 @@ public void LogBuildStarted()

// Make sure we process this event before going any further
WaitForLoggingToProcessEvents();

// Register Loggers and print out all the enabled loggers.
if (!OnlyLogCriticalEvents)
{
LogEnabledLoggers();
RegisterLoggers();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It pains me to iterate the list twice. Could you collapse these to one method that emits both messages?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, will do.

}
}

/// <summary>
Expand Down Expand Up @@ -391,6 +399,62 @@ public void LogBuildFinished(bool success)
WaitForLoggingToProcessEvents();
}

/// <summary>
/// Logs the names of enabled logs (except for Forwarding logs).
/// </summary>
private void LogEnabledLoggers()
{
List<string> listOfLoggers = new();
foreach (ILogger logger in Loggers)
{
ILogger actualLogger = UnwrapLogger(logger);
if (actualLogger is CentralForwardingLogger == true)
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
{
continue;
}
listOfLoggers.Add(actualLogger.GetType().Name);
}
if (listOfLoggers.Count != 0)
{
var msgEvent = new BuildMessageEventArgs(
ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogEnabledLogs", string.Join(", ", listOfLoggers)),
null, null, MessageImportance.Low);
msgEvent.BuildEventContext = BuildEventContext.Invalid;
ProcessLoggingEvent(msgEvent);
}
}

/// <summary>
/// Logs the file paths of enabled logs.
/// </summary>
private void RegisterLoggers()
{
foreach (ILogger logger in Loggers)
{
ILogger actualLogger = UnwrapLogger(logger);

string outputFilePath = actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath)
? fileLogger.OutputFilePath
: null;

IReadOnlyList<string> additionalPaths = actualLogger is BinaryLogger bl
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
? bl.AdditionalFilePaths
: null;

var registerEvent = new LoggerRegisteredEventArgs(
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
loggerName: actualLogger.GetType().Name,
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
outputFilePath: outputFilePath,
verbosity: actualLogger.Verbosity,
additionalOutputFilePaths: additionalPaths);
registerEvent.BuildEventContext = BuildEventContext.Invalid;
ProcessLoggingEvent(registerEvent);
}
}
private ILogger UnwrapLogger(ILogger logger)
{
return logger is ReusableLogger reusable ? reusable.OriginalLogger : logger;
}

/// <inheritdoc />
public void LogBuildCanceled()
{
Expand Down
5 changes: 4 additions & 1 deletion src/Build/Logging/BinaryLogger/BinaryLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public sealed class BinaryLoggerParameters
/// text logs that erase a lot of useful information.
/// </summary>
/// <remarks>The logger is public so that it can be instantiated from MSBuild.exe via command-line switch.</remarks>
public sealed class BinaryLogger : ILogger
public sealed class BinaryLogger : ILogger, IFileOutputLogger
{
// version 2:
// - new BuildEventContext.EvaluationId
Expand Down Expand Up @@ -297,6 +297,9 @@ private static bool TryParsePathParameter(string parameter, out string filePath)

internal string FilePath { get; private set; }

/// <inheritdoc/>
string IFileOutputLogger.OutputFilePath => FilePath;

/// <summary>
/// Gets or sets additional output file paths. When set, the binlog will be copied to all these paths
/// after the build completes. The primary FilePath will be used as the temporary write location.
Expand Down
11 changes: 9 additions & 2 deletions src/Build/Logging/FileLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ namespace Microsoft.Build.Logging
/// complex -- for example, there is parameter parsing in this class, plus in BaseConsoleLogger. However we have
/// to derive FileLogger from ConsoleLogger because it shipped that way in Whidbey.
/// </remarks>
public class FileLogger : ConsoleLogger
public class FileLogger : ConsoleLogger, IFileOutputLogger
{
#region Constructors

/// <summary>
/// Default constructor.
/// </summary>
Expand Down Expand Up @@ -239,6 +238,14 @@ private void ApplyFileLoggerParameter(string parameterName, string parameterValu
/// </summary>
private string _logFileName = "msbuild.log";

/// <summary>
/// The path to the log file.
/// </summary>
internal string FilePath => Path.GetFullPath(_logFileName);

/// <inheritdoc/>
string IFileOutputLogger.OutputFilePath => FilePath;

/// <summary>
/// fileWriter is the stream that has been opened on our log file.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Build/Logging/IFileOutputLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Build.Logging
{
internal interface IFileOutputLogger
{
string OutputFilePath { get; }
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
}
}
25 changes: 23 additions & 2 deletions src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Logging;
using Microsoft.Build.Shared;
using ColorResetter = Microsoft.Build.Logging.ColorResetter;
using ColorSetter = Microsoft.Build.Logging.ColorSetter;
Expand All @@ -28,7 +29,7 @@ internal class ParallelConsoleLogger : BaseConsoleLogger
/// Associate a (nodeID and project_context_id) to a target framework.
/// </summary>
internal Dictionary<(int nodeId, int contextId), string> propertyOutputMap = new Dictionary<(int nodeId, int contextId), string>();

private readonly List<LoggerRegisteredEventArgs> _registeredLoggers = new List<LoggerRegisteredEventArgs>();
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
#region Constructors
Comment thread
AlesProkop marked this conversation as resolved.
/// <summary>
/// Default constructor.
Expand Down Expand Up @@ -204,6 +205,7 @@ internal override void ResetConsoleLoggerState()
_hasBuildStarted = false;

// Reset the data structures created when the logger was created
_registeredLoggers.Clear();
propertyOutputMap = new Dictionary<(int, int), string>();
_buildEventManager = new BuildEventManager();
_deferredMessages = new Dictionary<BuildEventContext, List<BuildMessageEventArgs>>(s_compareContextNodeId);
Expand Down Expand Up @@ -273,6 +275,21 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs
ShowPerfSummary();
}

// Show paths to the files created by enabled loggers.
if (ShowSummary == true)
{
foreach (var logger in _registeredLoggers)
{
if (!string.IsNullOrEmpty(logger.OutputFilePath))
{
string displayPath = setColor != DontSetColor
? $"{AnsiCodes.LinkPrefix}{new Uri(logger.OutputFilePath).AbsoluteUri}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"
: logger.OutputFilePath;
WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath));
}
}
}

// Write the "Build Finished" event if verbosity is normal, detailed or diagnostic or the user
// specified to show the summary.
if (ShowSummary == true)
Comment thread
AlesProkop marked this conversation as resolved.
Expand Down Expand Up @@ -1124,7 +1141,11 @@ public override void MessageHandler(object sender, BuildMessageEventArgs e)
{
return;
}

if (e is LoggerRegisteredEventArgs loggerEvent)
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
{
_registeredLoggers.Add(loggerEvent);
return;
}
if (e.BuildEventContext == null && e is AssemblyLoadBuildEventArgs)
{
return;
Expand Down
25 changes: 24 additions & 1 deletion src/Build/Logging/TerminalLogger/TerminalLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ public EvalContext(BuildEventContext context)
/// </summary>
private bool _showNodesDisplay = true;

/// <summary>
/// Stores the registered loggers.
/// </summary>
private readonly List<LoggerRegisteredEventArgs> _registeredLoggers = new();


Comment thread
AlesProkop marked this conversation as resolved.
private uint? _originalConsoleMode;

/// <summary>
Expand Down Expand Up @@ -633,6 +639,18 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e)
RenderBuildSummary();
}

if (_showSummary == true)
{
foreach (var logger in _registeredLoggers)
{
if (!string.IsNullOrEmpty(logger.OutputFilePath))
{
string displayPath = $"{AnsiCodes.LinkPrefix}{new Uri(logger.OutputFilePath).AbsoluteUri}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}";
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath));
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
}
}
}

if (_restoreFailed)
{
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage",
Expand All @@ -659,6 +677,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e)

_projects.Clear();
_testRunSummaries.Clear();
_registeredLoggers.Clear();
_buildErrorsCount = 0;
_buildWarningsCount = 0;
_restoreFailed = false;
Expand Down Expand Up @@ -1180,7 +1199,11 @@ private void MessageRaised(object sender, BuildMessageEventArgs e)
{
return;
}

if (e is LoggerRegisteredEventArgs loggerEvent)
{
_registeredLoggers.Add(loggerEvent);
return;
}
string? message = e.Message;

if (message is not null && e.Importance == MessageImportance.High)
Expand Down
3 changes: 2 additions & 1 deletion src/Build/Microsoft.Build.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\Shared\DebuggingSources.proj" />

Expand Down Expand Up @@ -574,6 +574,7 @@
<Compile Include="Logging\SimpleErrorLogger.cs" />
<Compile Include="Logging\ParallelLogger\ConsoleOutputAligner.cs" />
<Compile Include="Logging\FileLogger.cs" />
<Compile Include="Logging\IFileOutputLogger.cs" />
<Compile Include="Logging\LogFormatter.cs" />
<Compile Include="ObjectModelRemoting\DefinitionObjectsLinks\ProjectMetadataLink.cs" />
<Compile Include="ObjectModelRemoting\DefinitionObjectsLinks\ProjectPropertyLink.cs" />
Expand Down
8 changes: 8 additions & 0 deletions src/Build/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@
<data name="BuildFinishedSuccess" xml:space="preserve">
<value>Build succeeded.</value>
</data>
<data name="LogFileOutputPath" xml:space="preserve">
Comment thread
AlesProkop marked this conversation as resolved.
<value>{0}: {1}</value>
Comment thread
AlesProkop marked this conversation as resolved.
Outdated
<comment>{0} is the logger name. {1} is the full file path.</comment>
</data>
<data name="LogEnabledLogs" xml:space="preserve">
<value>Enabled logs: {0}</value>
<comment>{0} is a comma-separated list of log types (e.g. "Binary log", "File log").</comment>
</data>
<data name="BuildStartedWithTime" xml:space="preserve">
<value>Build started {0}.</value>
</data>
Expand Down
10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading