Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions documentation/wiki/ChangeWaves.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ Change wave checks around features will be removed in the release that accompani

## Current Rotation of Change Waves

### 18.8
- [Console, parallel console, and terminal loggers print the paths of log files written by registered loggers (e.g. file logger and binary logger) as part of the end-of-build summary.](https://github.com/dotnet/msbuild/pull/13577)

### 18.7
- [Fix ASP.NET WebSite projects to resolve netstandard2.0 dependencies](https://github.com/dotnet/msbuild/pull/13058) - Pass TargetFrameworkVersion to RAR task and copy netstandard.dll facade for .NET Framework 4.7.1+ web projects.

Expand Down
54 changes: 54 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,53 @@ 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.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 LoggersRegisteredEventArgs containing the file logger path
var registeredEvent = mockLogger.AllBuildEvents
.OfType<LoggersRegisteredEventArgs>()
.FirstOrDefault(e => e.Loggers.Any(l => l.LoggerName == nameof(FileLogger)));
registeredEvent.ShouldNotBeNull();
var fileLoggerDesc = registeredEvent.Loggers.First(l => l.LoggerName == nameof(FileLogger));
var expectedPath = Path.GetFullPath(logFilePath);
fileLoggerDesc.OutputFilePaths.ShouldContain(expectedPath);
fileLoggerDesc.LoggerTypeFullName.ShouldBe(typeof(FileLogger).FullName);
fileLoggerDesc.Parameters.ShouldBe(fileLogger.Parameters);

// 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 +1843,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 +1910,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
7 changes: 7 additions & 0 deletions src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public void VerifyEventType()
BuildCheckTracingEventArgs buildCheckTracing = new();
BuildCanceledEventArgs buildCanceled = new("message", DateTime.UtcNow);
WorkerNodeTelemetryEventArgs workerNodeTelemetry = new();
LoggersRegisteredEventArgs loggersRegistered = new(new List<RegisteredLoggerInfo> { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }) });

VerifyLoggingPacket(buildFinished, LoggingEventType.BuildFinishedEvent);
VerifyLoggingPacket(buildStarted, LoggingEventType.BuildStartedEvent);
Expand Down Expand Up @@ -119,6 +120,7 @@ public void VerifyEventType()
VerifyLoggingPacket(buildCheckTracing, LoggingEventType.BuildCheckTracingEvent);
VerifyLoggingPacket(buildCanceled, LoggingEventType.BuildCanceledEvent);
VerifyLoggingPacket(workerNodeTelemetry, LoggingEventType.WorkerNodeTelemetryEvent);
VerifyLoggingPacket(loggersRegistered, LoggingEventType.LoggersRegisteredEvent);
}

private static BuildEventContext CreateBuildEventContext()
Expand Down Expand Up @@ -321,6 +323,11 @@ public void TestTranslation()
BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7)
},
new GeneratedFileUsedEventArgs("path", "some content"),
new LoggersRegisteredEventArgs(new List<RegisteredLoggerInfo>
{
new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }),
new RegisteredLoggerInfo("BinaryLogger"),
}),
};
foreach (BuildEventArgs arg in testArgs)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void InvalidCacheFilesShouldLogError(byte[] cacheContents)
result.OverallResult.ShouldBe(BuildResultCode.Failure);

_logger.FullLog.ShouldContain("MSB4256:");
_logger.AllBuildEvents.Count.ShouldBe(6);
_logger.AllBuildEvents.Count.ShouldBe(8);
_logger.ErrorCount.ShouldBe(1);
}

Expand Down Expand Up @@ -564,8 +564,8 @@ public void NonExistingInputResultsCacheShouldLogError()

result.OverallResult.ShouldBe(BuildResultCode.Failure);

_logger.AllBuildEvents.Count.ShouldBe(6);
_logger.Errors.First().Message.ShouldContain("MSB4255:");
_logger.AllBuildEvents.Count.ShouldBe(8);
_logger.Errors.First().Message.ShouldContain("MSB4255:");
_logger.Errors.First().Message.ShouldContain("FileDoesNotExist1");
_logger.Errors.First().Message.ShouldContain("FileDoesNotExist2");
_logger.ErrorCount.ShouldBe(1);
Expand Down
67 changes: 66 additions & 1 deletion src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
Expand All @@ -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,12 @@ 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)
{
LogAndRegisterLoggers();
}
}

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

/// <summary>
/// In a single pass over the registered loggers, emits a message listing the enabled logger
/// type names and a <see cref="LoggersRegisteredEventArgs"/> describing each logger (including
/// any output file paths for <see cref="IFileOutputLogger"/> implementations).
/// </summary>
private void LogAndRegisterLoggers()
{
List<string> listOfLoggers = new();
var loggerDescriptions = new List<RegisteredLoggerInfo>();

foreach (ILogger logger in Loggers)
{
ILogger actualLogger = UnwrapLogger(logger);
Type loggerType = actualLogger.GetType();

listOfLoggers.Add(loggerType.FullName ?? loggerType.Name);

var outputFilePaths = new List<string>();
if (actualLogger is IFileOutputLogger fileLogger)
{
foreach (string outputFilePath in fileLogger.OutputFilePaths)
{
if (!string.IsNullOrEmpty(outputFilePath))
{
outputFilePaths.Add(outputFilePath);
}
}
}

loggerDescriptions.Add(new RegisteredLoggerInfo(
loggerName: loggerType.Name,
outputFilePaths: outputFilePaths.Count > 0 ? outputFilePaths : null,
verbosity: actualLogger.Verbosity,
loggerTypeFullName: loggerType.FullName,
parameters: actualLogger.Parameters));
}

if (listOfLoggers.Count != 0)
{
var msgEvent = new BuildMessageEventArgs(
ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogEnabledLogs", string.Join(", ", listOfLoggers)),
null, null, MessageImportance.Low);
msgEvent.BuildEventContext = BuildEventContext.Invalid;
ProcessLoggingEvent(msgEvent);
}

if (loggerDescriptions.Count > 0)
{
var registerEvent = new LoggersRegisteredEventArgs(loggerDescriptions);
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
3 changes: 2 additions & 1 deletion src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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
Expand Down Expand Up @@ -45,5 +45,6 @@ public enum BinaryLogRecordKind
BuildCheckAcquisition,
BuildSubmissionStarted,
BuildCanceled,
LoggersRegistered,
}
}
12 changes: 10 additions & 2 deletions 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 @@ -109,6 +109,8 @@ public sealed class BinaryLogger : ILogger
// - new record kind: BuildCanceledEventArgs
// version 25:
// - add extra information to PropertyInitialValueSetEventArgs and PropertyReassignmentEventArgs and change message formatting logic.
// version 26:
// - new record kind: LoggersRegisteredEventArgs (reports registered loggers and their output file paths)

// MAKE SURE YOU KEEP BuildEventArgsWriter AND StructuredLogViewer.BuildEventArgsWriter IN SYNC WITH THE CHANGES ABOVE.
// Both components must stay in sync to avoid issues with logging or event handling in the products.
Expand All @@ -119,7 +121,7 @@ public sealed class BinaryLogger : ILogger

// The current version of the binary log representation.
// Changes with each update of the binary log format.
internal const int FileFormatVersion = 25;
internal const int FileFormatVersion = 26;
Comment thread
AlesProkop marked this conversation as resolved.

// The minimum version of the binary log reader that can read log of above version.
// This should be changed only when the binary log format is changed in a way that would prevent it from being
Expand Down Expand Up @@ -297,6 +299,12 @@ private static bool TryParsePathParameter(string parameter, out string filePath)

internal string FilePath { get; private set; }

/// <inheritdoc/>
System.Collections.Generic.IReadOnlyList<string> IFileOutputLogger.OutputFilePaths
=> AdditionalFilePaths is null || AdditionalFilePaths.Count == 0
? [FilePath]
: [FilePath, .. AdditionalFilePaths];

/// <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
34 changes: 34 additions & 0 deletions src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ void HandleError(FormatErrorMessage msgFactory, bool noThrow, ReaderErrorType re
BinaryLogRecordKind.BuildCheckTracing => ReadBuildCheckTracingEventArgs(),
BinaryLogRecordKind.BuildCheckAcquisition => ReadBuildCheckAcquisitionEventArgs(),
BinaryLogRecordKind.BuildCanceled => ReadBuildCanceledEventArgs(),
BinaryLogRecordKind.LoggersRegistered => ReadLoggersRegisteredEventArgs(),
_ => null
};

Expand Down Expand Up @@ -1287,6 +1288,39 @@ private BuildEventArgs ReadBuildCanceledEventArgs()
return e;
}

private BuildEventArgs ReadLoggersRegisteredEventArgs()
{
var fields = ReadBuildEventArgsFields();
int count = ReadInt32();
var loggers = new List<RegisteredLoggerInfo>(count);
for (int i = 0; i < count; i++)
{
string loggerName = ReadDeduplicatedString()!;
string loggerTypeFullName = ReadDeduplicatedString()!;
string parameters = ReadDeduplicatedString()!;

LoggerVerbosity? verbosity = null;
if (ReadBoolean())
{
verbosity = (LoggerVerbosity)ReadInt32();
}

int pathCount = ReadInt32();
var outputFilePaths = new string[pathCount];
for (int j = 0; j < pathCount; j++)
{
outputFilePaths[j] = ReadDeduplicatedString()!;
}

loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, loggerTypeFullName, parameters));
}

var e = new LoggersRegisteredEventArgs(loggers);
SetCommonFields(e, fields);

return e;
}

/// <summary>
/// For errors and warnings these 8 fields are written out explicitly
/// (their presence is not marked as a bit in the flags). So we have to
Expand Down
26 changes: 26 additions & 0 deletions src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ private BinaryLogRecordKind WriteCore(BuildEventArgs e)
case ProjectEvaluationFinishedEventArgs projectEvaluationFinished: return Write(projectEvaluationFinished);
case BuildCheckTracingEventArgs buildCheckTracing: return Write(buildCheckTracing);
case BuildCheckAcquisitionEventArgs buildCheckAcquisition: return Write(buildCheckAcquisition);
case LoggersRegisteredEventArgs loggersRegistered: return Write(loggersRegistered);
default:
// convert all unrecognized objects to message
// and just preserve the message
Expand Down Expand Up @@ -316,6 +317,31 @@ private BinaryLogRecordKind Write(BuildCanceledEventArgs e)
return BinaryLogRecordKind.BuildCanceled;
}

private BinaryLogRecordKind Write(LoggersRegisteredEventArgs e)
{
WriteBuildEventArgsFields(e);
Write(e.Loggers.Count);
foreach (var logger in e.Loggers)
{
WriteDeduplicatedString(logger.LoggerName);
WriteDeduplicatedString(logger.LoggerTypeFullName);
WriteDeduplicatedString(logger.Parameters);
Write(logger.Verbosity.HasValue);
if (logger.Verbosity.HasValue)
{
Write((int)logger.Verbosity.Value);
}

Write(logger.OutputFilePaths.Count);
foreach (var path in logger.OutputFilePaths)
{
WriteDeduplicatedString(path);
}
}

return BinaryLogRecordKind.LoggersRegistered;
}

private BinaryLogRecordKind Write(ProjectEvaluationStartedEventArgs e)
{
WriteBuildEventArgsFields(e, writeMessage: false);
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/>
System.Collections.Generic.IReadOnlyList<string> IFileOutputLogger.OutputFilePaths => new[] { FilePath };

/// <summary>
/// fileWriter is the stream that has been opened on our log file.
/// </summary>
Expand Down
Loading