From ab41996f2eb21b87ff1c6502feadb6b777198158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Mon, 20 Apr 2026 13:22:51 +0200 Subject: [PATCH 01/19] Added a message of enabled logs, and their paths. --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 63 +++++++++++++++++++ .../Logging/LoggingServiceLogMethods.cs | 56 +++++++++++++++++ src/Build/Logging/FileLogger.cs | 5 ++ src/Build/Resources/Strings.resx | 8 +++ src/Build/Resources/xlf/Strings.cs.xlf | 10 +++ src/Build/Resources/xlf/Strings.de.xlf | 10 +++ src/Build/Resources/xlf/Strings.es.xlf | 10 +++ src/Build/Resources/xlf/Strings.fr.xlf | 10 +++ src/Build/Resources/xlf/Strings.it.xlf | 10 +++ src/Build/Resources/xlf/Strings.ja.xlf | 10 +++ src/Build/Resources/xlf/Strings.ko.xlf | 10 +++ src/Build/Resources/xlf/Strings.pl.xlf | 10 +++ src/Build/Resources/xlf/Strings.pt-BR.xlf | 10 +++ src/Build/Resources/xlf/Strings.ru.xlf | 10 +++ src/Build/Resources/xlf/Strings.tr.xlf | 10 +++ src/Build/Resources/xlf/Strings.zh-Hans.xlf | 10 +++ src/Build/Resources/xlf/Strings.zh-Hant.xlf | 10 +++ 17 files changed, 262 insertions(+) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 3fe5fcd8e6e..6469d9aeb70 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -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; @@ -1046,6 +1047,62 @@ 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(); + Assert.IsType(service.ProcessedBuildEvent); + Assert.Contains("ConsoleLogger", service.ProcessedBuildEvent.Message); + } + + [Fact] + public void LogBuildFinishedLoggerPaths() + { + ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); + BinaryLogger binaryLogger = new BinaryLogger { Parameters = "test.binlog" }; ; + service.RegisterLogger(binaryLogger); + service.LogBuildFinished(true); + var pathMessage = service.AllProcessedBuildEvents + .OfType() + .FirstOrDefault(e => e.Message.Contains("test.binlog")); + Assert.NotNull(pathMessage); + Assert.Contains("Binary log", pathMessage.Message); + } + + [Fact] + public void LogFilePathsPresentInBinaryLog() + { + using var env = TestEnvironment.Create(); + var binlogPath = env.ExpectFile(".binlog").Path; + + var binaryLogger = new BinaryLogger { Parameters = binlogPath }; + var mockLogger = new MockLogger(); + + using (var collection = new ProjectCollection()) + { + var project = ObjectModelHelpers.CreateInMemoryProject(collection, @" + + + "); + project.Build(new ILogger[] { binaryLogger, mockLogger }).ShouldBeTrue(); + } + + // Check the text log (MockLogger captures all messages) + mockLogger.FullLog.ShouldContain(".binlog"); + + // Check the binary log by replaying it + var replayLogger = new MockLogger(); + var reader = new BinaryLogReplayEventSource(); + replayLogger.Initialize(reader); + reader.Replay(binlogPath); + replayLogger.Shutdown(); + + replayLogger.FullLog.ShouldContain(".binlog"); + } [Fact] public void LogBuildCanceled() @@ -1795,6 +1852,11 @@ internal sealed class ProcessBuildEventHelper : LoggingService /// to verify that a buildEvent was sent to ProcessLoggingEvent. /// private BuildEventArgs _processedBuildEvent; + + /// + /// All events processed by ProcessLoggingEvent. + /// + internal List AllProcessedBuildEvents { get; } = new(); #endregion #region Constructor /// @@ -1857,6 +1919,7 @@ protected internal override void ProcessLoggingEvent(object buildEvent) if (buildEvent is BuildEventArgs buildEventArgs) { _processedBuildEvent = buildEventArgs; + AllProcessedBuildEvents.Add(buildEventArgs); } else if (buildEvent is KeyValuePair kvp) { diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 48f04b93e61..d516e076bf1 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -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; @@ -360,6 +361,9 @@ public void LogBuildStarted() // Make sure we process this event before going any further WaitForLoggingToProcessEvents(); + + // Print out all enabled logs. + LogLoggersUsed(fileNames: false); } /// @@ -382,6 +386,8 @@ public void LogBuildFinished(bool success) message = ResourceUtilities.GetResourceString(success ? "BuildFinishedSuccess" : "BuildFinishedFailure"); } } + // Before ending the build, print out all logs that outputted a file and the location of the file. + LogLoggersUsed(fileNames: true); BuildFinishedEventArgs buildEvent = new BuildFinishedEventArgs(message, null /* no help keyword */, success); @@ -391,6 +397,56 @@ public void LogBuildFinished(bool success) WaitForLoggingToProcessEvents(); } + /// + /// Prints either the names of enabled logs (except for Forwarding logs) + /// or the file paths of the logs, depending on the value of the fileNames parameter. + /// + /// If true, logs the file paths of the logs; otherwise, logs the names of the enabled logs. + private void LogLoggersUsed(bool fileNames) + { + var list_of_log_names = new List(); + foreach (ILogger logger in Loggers) + { + ILogger actual = logger is ReusableLogger reusable + ? reusable.OriginalLogger + : logger; + // We don't want to log the name of the forwarding logger, since it may be confusing. + if (actual is CentralForwardingLogger == true) + { + continue; + } + + if (fileNames) + { + (string logType, string path) = actual switch + { + BinaryLogger bl => ("Binary log", bl.FilePath), + FileLogger fl => ("File log", fl.FilePath), + _ => (null, null) + }; + if (!string.IsNullOrEmpty(path)) + { + var msgEvent = new BuildMessageEventArgs( + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logType, path), + null, null, MessageImportance.High); + msgEvent.BuildEventContext = BuildEventContext.Invalid; + ProcessLoggingEvent(msgEvent); + } + } + else + { + list_of_log_names.Add(actual.GetType().Name); + } + } + if (list_of_log_names.Count != 0) + { + var msgEvent = new BuildMessageEventArgs( + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogEnabledLogs", string.Join(", ", list_of_log_names)), + null, null, MessageImportance.High); + msgEvent.BuildEventContext = BuildEventContext.Invalid; + ProcessLoggingEvent(msgEvent); + } + } /// public void LogBuildCanceled() { diff --git a/src/Build/Logging/FileLogger.cs b/src/Build/Logging/FileLogger.cs index a2d306a1341..0db699ea2de 100644 --- a/src/Build/Logging/FileLogger.cs +++ b/src/Build/Logging/FileLogger.cs @@ -239,6 +239,11 @@ private void ApplyFileLoggerParameter(string parameterName, string parameterValu /// private string _logFileName = "msbuild.log"; + /// + /// The path to the log file. + /// + internal string FilePath => Path.GetFullPath(_logFileName); + /// /// fileWriter is the stream that has been opened on our log file. /// diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 551b617d3ed..2752ea6f352 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -170,6 +170,14 @@ Build succeeded. + + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + + + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + Build started {0}. diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 6be75946e85..8829ba8016e 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -590,6 +590,16 @@ Načítá se následující modul plug-in mezipaměti projektu: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. Podrobnost protokolování je nastavená na: {0}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 6ddc83aa214..83ac6e4404b 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -590,6 +590,16 @@ Folgendes Projektcache-Plug-In wird geladen: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. Die Ausführlichkeit der Protokollierung ist auf "{0}" festgelegt. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 3076fcb2451..5f200f7a6cc 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -590,6 +590,16 @@ Cargando el complemento de caché de proyectos siguiente:{0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. El nivel de detalle de registro está establecido en {0}. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index bff0a8d988b..ca5339f22a5 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -590,6 +590,16 @@ Chargement du plug-in de cache de projet suivant :{0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. La verbosité de la journalisation a la valeur {0}. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 942869c420a..eb6ac9e097c 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -590,6 +590,16 @@ Caricamento del plug-in della cache del progetto seguente: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. Il livello di dettaglio della registrazione è impostato su: {0}. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 7bdbc8f4d7a..d718b373d2b 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -590,6 +590,16 @@ 次のプロジェクト キャッシュ プラグインを読み込んでいます: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. ログの詳細度は次のように設定されています: {0}。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 1cb2d90d641..d70bb6a0ad9 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -590,6 +590,16 @@ 다음 프로젝트 캐시 플러그 인을 로드하는 중:{0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. 로깅의 세부 정보 표시가 {0}(으)로 설정되었습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index be8119c6b9c..620ee2c7486 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -590,6 +590,16 @@ Ładowanie następującej wtyczki pamięci podręcznej projektu: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. Szczegółowość rejestrowania została ustawiona na: {0}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 8cc115856a6..2de14511193 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -590,6 +590,16 @@ Carregando o seguinte plug-in de cache do projeto: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. O detalhamento do log está definido como: {0}. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 9563004fb21..c6ddad9c525 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -590,6 +590,16 @@ Идет загрузка следующего подключаемого модуля кэша проектов: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. Уровень детализации журнала: {0}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 052ba8f14a0..8c348a4f162 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -590,6 +590,16 @@ Şu proje önbelleği eklentisi yükleniyor:{0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. Günlük kaydı ayrıntı düzeyi {0} olarak ayarlandı. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 68d1004f90c..adc197b11d1 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -590,6 +590,16 @@ 正在加载以下项目缓存插件: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. 日志记录详细程度设置为: {0}。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 43a985a0871..6286f72e893 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -590,6 +590,16 @@ 載入下列專案快取外掛程式: {0} + + Enabled logs: {0} + Enabled logs: {0} + {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + + + {0} output file: {1} + {0} output file: {1} + {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Logging verbosity is set to: {0}. 記錄詳細程度設定為: {0}。 From 59e14fc3a4fa74663c4c4e7c5947f1d93912d79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Mon, 20 Apr 2026 15:24:06 +0200 Subject: [PATCH 02/19] separated the function, added interface for handling file names --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 2 +- .../Logging/LoggingServiceLogMethods.cs | 78 ++++++++++--------- .../Logging/BinaryLogger/BinaryLogger.cs | 5 +- src/Build/Logging/FileLogger.cs | 6 +- src/Build/Logging/IFileOutputLogger.cs | 10 +++ src/Build/Microsoft.Build.csproj | 1 + src/Build/Resources/Strings.resx | 4 +- src/Build/Resources/xlf/Strings.cs.xlf | 6 +- src/Build/Resources/xlf/Strings.de.xlf | 6 +- src/Build/Resources/xlf/Strings.es.xlf | 6 +- src/Build/Resources/xlf/Strings.fr.xlf | 6 +- src/Build/Resources/xlf/Strings.it.xlf | 6 +- src/Build/Resources/xlf/Strings.ja.xlf | 6 +- src/Build/Resources/xlf/Strings.ko.xlf | 6 +- src/Build/Resources/xlf/Strings.pl.xlf | 6 +- src/Build/Resources/xlf/Strings.pt-BR.xlf | 6 +- src/Build/Resources/xlf/Strings.ru.xlf | 6 +- src/Build/Resources/xlf/Strings.tr.xlf | 6 +- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 6 +- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 6 +- 20 files changed, 102 insertions(+), 82 deletions(-) create mode 100644 src/Build/Logging/IFileOutputLogger.cs diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 6469d9aeb70..1d89b8c862b 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1070,7 +1070,7 @@ public void LogBuildFinishedLoggerPaths() .OfType() .FirstOrDefault(e => e.Message.Contains("test.binlog")); Assert.NotNull(pathMessage); - Assert.Contains("Binary log", pathMessage.Message); + Assert.Contains("Log output file", pathMessage.Message); } [Fact] diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index d516e076bf1..60b42e6735c 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -363,7 +363,7 @@ public void LogBuildStarted() WaitForLoggingToProcessEvents(); // Print out all enabled logs. - LogLoggersUsed(fileNames: false); + LogEnabledLoggers(); } /// @@ -387,7 +387,7 @@ public void LogBuildFinished(bool success) } } // Before ending the build, print out all logs that outputted a file and the location of the file. - LogLoggersUsed(fileNames: true); + LogFileNamesOfLoggersUsed(); BuildFinishedEventArgs buildEvent = new BuildFinishedEventArgs(message, null /* no help keyword */, success); @@ -398,55 +398,59 @@ public void LogBuildFinished(bool success) } /// - /// Prints either the names of enabled logs (except for Forwarding logs) - /// or the file paths of the logs, depending on the value of the fileNames parameter. + /// Logs the names of enabled logs (except for Forwarding logs). /// - /// If true, logs the file paths of the logs; otherwise, logs the names of the enabled logs. - private void LogLoggersUsed(bool fileNames) + private void LogEnabledLoggers() { - var list_of_log_names = new List(); + List listOfLoggers = new(); foreach (ILogger logger in Loggers) { - ILogger actual = logger is ReusableLogger reusable - ? reusable.OriginalLogger - : logger; - // We don't want to log the name of the forwarding logger, since it may be confusing. - if (actual is CentralForwardingLogger == true) + ILogger actualLogger = UnwrapLogger(logger); + if (actualLogger is CentralForwardingLogger == true) { continue; } - - if (fileNames) - { - (string logType, string path) = actual switch - { - BinaryLogger bl => ("Binary log", bl.FilePath), - FileLogger fl => ("File log", fl.FilePath), - _ => (null, null) - }; - if (!string.IsNullOrEmpty(path)) - { - var msgEvent = new BuildMessageEventArgs( - ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logType, path), - null, null, MessageImportance.High); - msgEvent.BuildEventContext = BuildEventContext.Invalid; - ProcessLoggingEvent(msgEvent); - } - } - else - { - list_of_log_names.Add(actual.GetType().Name); - } + listOfLoggers.Add(actualLogger.GetType().Name); } - if (list_of_log_names.Count != 0) + if (listOfLoggers.Count != 0) { var msgEvent = new BuildMessageEventArgs( - ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogEnabledLogs", string.Join(", ", list_of_log_names)), - null, null, MessageImportance.High); + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogEnabledLogs", string.Join(", ", listOfLoggers)), + null, null, MessageImportance.Low); msgEvent.BuildEventContext = BuildEventContext.Invalid; ProcessLoggingEvent(msgEvent); } } + + /// + /// Logs the file paths of enabled logs. + /// + private void LogFileNamesOfLoggersUsed() + { + foreach (ILogger logger in Loggers) + { + ILogger actualLogger = UnwrapLogger(logger); + if (actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath)) + { + var msgEvent = new BuildMessageEventArgs( + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", fileLogger.OutputFilePath), + null, null, MessageImportance.Low); + msgEvent.BuildEventContext = BuildEventContext.Invalid; + ProcessLoggingEvent(msgEvent); + } + } + } + + /// + /// Unwraps the name of the logger to get the actual logger. + /// + /// A logger to unwrap. + /// The actual logger. + private ILogger UnwrapLogger(ILogger logger) + { + return logger is ReusableLogger reusable ? reusable.OriginalLogger : logger; + } + /// public void LogBuildCanceled() { diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 73031a02798..fea26573a32 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -48,7 +48,7 @@ public sealed class BinaryLoggerParameters /// text logs that erase a lot of useful information. /// /// The logger is public so that it can be instantiated from MSBuild.exe via command-line switch. - public sealed class BinaryLogger : ILogger + public sealed class BinaryLogger : ILogger, IFileOutputLogger { // version 2: // - new BuildEventContext.EvaluationId @@ -297,6 +297,9 @@ private static bool TryParsePathParameter(string parameter, out string filePath) internal string FilePath { get; private set; } + /// + string IFileOutputLogger.OutputFilePath => FilePath; + /// /// 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. diff --git a/src/Build/Logging/FileLogger.cs b/src/Build/Logging/FileLogger.cs index 0db699ea2de..62b275c3311 100644 --- a/src/Build/Logging/FileLogger.cs +++ b/src/Build/Logging/FileLogger.cs @@ -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. /// - public class FileLogger : ConsoleLogger + public class FileLogger : ConsoleLogger, IFileOutputLogger { #region Constructors - /// /// Default constructor. /// @@ -244,6 +243,9 @@ private void ApplyFileLoggerParameter(string parameterName, string parameterValu /// internal string FilePath => Path.GetFullPath(_logFileName); + /// + string IFileOutputLogger.OutputFilePath => FilePath; + /// /// fileWriter is the stream that has been opened on our log file. /// diff --git a/src/Build/Logging/IFileOutputLogger.cs b/src/Build/Logging/IFileOutputLogger.cs new file mode 100644 index 00000000000..7336155903c --- /dev/null +++ b/src/Build/Logging/IFileOutputLogger.cs @@ -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; } + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 7f3066e974b..67a7baa3634 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -574,6 +574,7 @@ + diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 2752ea6f352..bcc84b60689 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -171,8 +171,8 @@ Build succeeded. - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + {0} is the full file path. Enabled logs: {0} diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 8829ba8016e..2e7a9a6ba14 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 83ac6e4404b..adcd250c7d0 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 5f200f7a6cc..06648fbd246 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index ca5339f22a5..a32398e387a 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index eb6ac9e097c..112956dced0 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index d718b373d2b..a7364ae64bd 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index d70bb6a0ad9..432aa07b64a 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 620ee2c7486..3d6a59e3789 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 2de14511193..cbe53499c8f 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index c6ddad9c525..3eddd79728a 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 8c348a4f162..a83a678a86f 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index adc197b11d1..d0d33fc6eee 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 6286f72e893..4b4ee4146a3 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0} output file: {1} - {0} output file: {1} - {0} is the log type (e.g. "Binary log", "File log"). {1} is the full file path. + Log output file: {0} + Log output file: {0} + {0} is the full file path. Logging verbosity is set to: {0}. From ec6cee1ee3cd9f4cc472111df3d4766b7f5ed521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Tue, 21 Apr 2026 17:17:52 +0200 Subject: [PATCH 03/19] First draft of paths of logs in BuildSummary. --- .../Logging/LoggingServiceLogMethods.cs | 37 ++++++++++--------- .../ParallelLogger/ParallelConsoleLogger.cs | 17 ++++++++- .../Logging/TerminalLogger/TerminalLogger.cs | 19 +++++++++- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 60b42e6735c..38f6fdcf8a0 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -362,7 +362,8 @@ public void LogBuildStarted() // Make sure we process this event before going any further WaitForLoggingToProcessEvents(); - // Print out all enabled logs. + // Register Loggers and print out all the enabled loggers. + RegisterLoggers(); LogEnabledLoggers(); } @@ -386,8 +387,6 @@ public void LogBuildFinished(bool success) message = ResourceUtilities.GetResourceString(success ? "BuildFinishedSuccess" : "BuildFinishedFailure"); } } - // Before ending the build, print out all logs that outputted a file and the location of the file. - LogFileNamesOfLoggersUsed(); BuildFinishedEventArgs buildEvent = new BuildFinishedEventArgs(message, null /* no help keyword */, success); @@ -425,27 +424,29 @@ private void LogEnabledLoggers() /// /// Logs the file paths of enabled logs. /// - private void LogFileNamesOfLoggersUsed() + private void RegisterLoggers() { foreach (ILogger logger in Loggers) { ILogger actualLogger = UnwrapLogger(logger); - if (actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath)) - { - var msgEvent = new BuildMessageEventArgs( - ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", fileLogger.OutputFilePath), - null, null, MessageImportance.Low); - msgEvent.BuildEventContext = BuildEventContext.Invalid; - ProcessLoggingEvent(msgEvent); - } + + string outputFilePath = actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath) + ? fileLogger.OutputFilePath + : null; + + IReadOnlyList additionalPaths = actualLogger is BinaryLogger bl + ? bl.AdditionalFilePaths + : null; + + var registerEvent = new LoggerRegisteredEventArgs( + loggerName: actualLogger.GetType().Name, + outputFilePath: outputFilePath, + verbosity: actualLogger.Verbosity, + additionalOutputFilePaths: additionalPaths); + registerEvent.BuildEventContext = BuildEventContext.Invalid; + ProcessLoggingEvent(registerEvent); } } - - /// - /// Unwraps the name of the logger to get the actual logger. - /// - /// A logger to unwrap. - /// The actual logger. private ILogger UnwrapLogger(ILogger logger) { return logger is ReusableLogger reusable ? reusable.OriginalLogger : logger; diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index fbeaefe7e15..8bc7a6e694d 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -28,7 +28,7 @@ internal class ParallelConsoleLogger : BaseConsoleLogger /// Associate a (nodeID and project_context_id) to a target framework. /// internal Dictionary<(int nodeId, int contextId), string> propertyOutputMap = new Dictionary<(int nodeId, int contextId), string>(); - + private readonly List _registeredLoggers = new List(); #region Constructors /// /// Default constructor. @@ -273,6 +273,15 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs ShowPerfSummary(); } + foreach (var logger in _registeredLoggers) + { + if (!string.IsNullOrEmpty(logger.OutputFilePath)) + { + WriteNewLine(); + WriteLinePretty($" {logger.LoggerName}: {logger.OutputFilePath}"); + } + } + // Write the "Build Finished" event if verbosity is normal, detailed or diagnostic or the user // specified to show the summary. if (ShowSummary == true) @@ -1124,7 +1133,11 @@ public override void MessageHandler(object sender, BuildMessageEventArgs e) { return; } - + if (e is LoggerRegisteredEventArgs loggerEvent) + { + _registeredLoggers.Add(loggerEvent); + return; + } if (e.BuildEventContext == null && e is AssemblyLoadBuildEventArgs) { return; diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 02df04d569d..f5ade2c962d 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -221,6 +221,12 @@ public EvalContext(BuildEventContext context) /// private bool _showNodesDisplay = true; + /// + /// Stores the registered loggers. + /// + private readonly List _registeredLoggers = new(); + + private uint? _originalConsoleMode; /// @@ -636,6 +642,13 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) } else { + foreach (var logger in _registeredLoggers) + { + if (!string.IsNullOrEmpty(logger.OutputFilePath)) + { + Terminal.WriteLine($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); + } + } Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("BuildFinished", buildResult, duration)); @@ -1175,7 +1188,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) From 7299b72f561f69687797ecefceb1174236976bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Wed, 22 Apr 2026 08:43:14 +0200 Subject: [PATCH 04/19] Added clickable links into the build summary. --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 41 +++++--------- .../ParallelLogger/ParallelConsoleLogger.cs | 13 +++-- src/Framework/LoggerRegisteredEventArgs.cs | 53 +++++++++++++++++++ 3 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 src/Framework/LoggerRegisteredEventArgs.cs diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 1d89b8c862b..4bea34a8346 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1060,26 +1060,12 @@ public void LogBuildStartedLoggerNames() } [Fact] - public void LogBuildFinishedLoggerPaths() - { - ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - BinaryLogger binaryLogger = new BinaryLogger { Parameters = "test.binlog" }; ; - service.RegisterLogger(binaryLogger); - service.LogBuildFinished(true); - var pathMessage = service.AllProcessedBuildEvents - .OfType() - .FirstOrDefault(e => e.Message.Contains("test.binlog")); - Assert.NotNull(pathMessage); - Assert.Contains("Log output file", pathMessage.Message); - } - - [Fact] - public void LogFilePathsPresentInBinaryLog() + public void LogFilePathsPresentInFileLog() { using var env = TestEnvironment.Create(); - var binlogPath = env.ExpectFile(".binlog").Path; + var logFilePath = env.ExpectFile(".log").Path; - var binaryLogger = new BinaryLogger { Parameters = binlogPath }; + var fileLogger = new FileLogger { Parameters = "logfile=" + logFilePath }; var mockLogger = new MockLogger(); using (var collection = new ProjectCollection()) @@ -1088,20 +1074,19 @@ public void LogFilePathsPresentInBinaryLog() "); - project.Build(new ILogger[] { binaryLogger, mockLogger }).ShouldBeTrue(); + project.Build(new ILogger[] { fileLogger, mockLogger }).ShouldBeTrue(); } - // Check the text log (MockLogger captures all messages) - mockLogger.FullLog.ShouldContain(".binlog"); - - // Check the binary log by replaying it - var replayLogger = new MockLogger(); - var reader = new BinaryLogReplayEventSource(); - replayLogger.Initialize(reader); - reader.Replay(binlogPath); - replayLogger.Shutdown(); + // Check that MockLogger captured a LoggerRegisteredEventArgs with the file logger path + var registeredEvent = mockLogger.AllBuildEvents + .OfType() + .FirstOrDefault(e => e.LoggerName == nameof(FileLogger)); + registeredEvent.ShouldNotBeNull(); + registeredEvent.OutputFilePath.ShouldContain(".log"); - replayLogger.FullLog.ShouldContain(".binlog"); + // Check the file log itself contains the path + var fileLogContents = File.ReadAllText(logFilePath); + fileLogContents.ShouldContain(".log"); } [Fact] diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index 8bc7a6e694d..9f6258dac5a 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -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; @@ -277,13 +278,19 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs { if (!string.IsNullOrEmpty(logger.OutputFilePath)) { - WriteNewLine(); - WriteLinePretty($" {logger.LoggerName}: {logger.OutputFilePath}"); + if (setColor != DontSetColor) + { + WriteLinePretty($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); + } + else + { + WriteLinePretty($" {logger.LoggerName}: {logger.OutputFilePath}"); + } } } // Write the "Build Finished" event if verbosity is normal, detailed or diagnostic or the user - // specified to show the summary. + // specified to show the summary.but if it was ne if (ShowSummary == true) { if (e.Succeeded) diff --git a/src/Framework/LoggerRegisteredEventArgs.cs b/src/Framework/LoggerRegisteredEventArgs.cs new file mode 100644 index 00000000000..49f1b091e1d --- /dev/null +++ b/src/Framework/LoggerRegisteredEventArgs.cs @@ -0,0 +1,53 @@ +// 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; + +namespace Microsoft.Build.Framework +{ + /// + /// Arguments for the logger registered event, indicating that a logger has been registered. + /// + [Serializable] + public class LoggerRegisteredEventArgs : BuildMessageEventArgs + { + public LoggerRegisteredEventArgs() + { + } + /// + /// Initialize a new instance of the LoggerRegisteredEventArgs class. + /// + /// The name of the logger. + /// The full path to the log output file. + /// The verbosity level of the logger. + /// Additional output file paths for the logger. + public LoggerRegisteredEventArgs(string loggerName, string? outputFilePath, LoggerVerbosity? verbosity, IReadOnlyList? additionalOutputFilePaths) : base() + { + OutputFilePath = outputFilePath; + LoggerName = loggerName; + Verbosity = verbosity; + AdditionalOutputFilePaths = additionalOutputFilePaths; + } + + /// + /// The name of the logger. + /// + public string LoggerName { get; set; } = string.Empty; + + /// + /// The verbosity level of the logger. + /// + public LoggerVerbosity? Verbosity { get; set; } + + /// + /// The full path to the log output file. + /// + public string? OutputFilePath { get; set; } + + /// + /// Additional output file paths for the logger. + /// + public IReadOnlyList? AdditionalOutputFilePaths { get; set; } + } +} From 6d86d465e382c11e6895db8ef4e2d21752367b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Wed, 22 Apr 2026 11:48:14 +0200 Subject: [PATCH 05/19] messages added into summary. added files into binarylog. --- .../Logging/LoggingServiceLogMethods.cs | 2 +- .../ParallelLogger/ParallelConsoleLogger.cs | 20 +++++++++++-------- .../Logging/TerminalLogger/TerminalLogger.cs | 18 ++++++++++------- src/Framework/LoggerRegisteredEventArgs.cs | 15 +++++++++++++- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 38f6fdcf8a0..bc0d53ee16e 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -363,8 +363,8 @@ public void LogBuildStarted() WaitForLoggingToProcessEvents(); // Register Loggers and print out all the enabled loggers. - RegisterLoggers(); LogEnabledLoggers(); + RegisterLoggers(); } /// diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index 9f6258dac5a..b1841cd07aa 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -274,17 +274,21 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs ShowPerfSummary(); } - foreach (var logger in _registeredLoggers) + // Show paths to the files created by enabled loggers. + if (ShowSummary == true) { - if (!string.IsNullOrEmpty(logger.OutputFilePath)) + foreach (var logger in _registeredLoggers) { - if (setColor != DontSetColor) + if (!string.IsNullOrEmpty(logger.OutputFilePath)) { - WriteLinePretty($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); - } - else - { - WriteLinePretty($" {logger.LoggerName}: {logger.OutputFilePath}"); + if (setColor != DontSetColor) + { + WriteLinePretty($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); + } + else + { + WriteLinePretty($" {logger.LoggerName}: {logger.OutputFilePath}"); + } } } } diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index f5ade2c962d..3b3891a3394 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -634,13 +634,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) RenderBuildSummary(); } - if (_restoreFailed) - { - Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage", - buildResult, - duration)); - } - else + if (_showSummary == true) { foreach (var logger in _registeredLoggers) { @@ -649,6 +643,16 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) Terminal.WriteLine($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); } } + } + + if (_restoreFailed) + { + Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage", + buildResult, + duration)); + } + else + { Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("BuildFinished", buildResult, duration)); diff --git a/src/Framework/LoggerRegisteredEventArgs.cs b/src/Framework/LoggerRegisteredEventArgs.cs index 49f1b091e1d..4f3514bf4fb 100644 --- a/src/Framework/LoggerRegisteredEventArgs.cs +++ b/src/Framework/LoggerRegisteredEventArgs.cs @@ -22,13 +22,26 @@ public LoggerRegisteredEventArgs() /// The full path to the log output file. /// The verbosity level of the logger. /// Additional output file paths for the logger. - public LoggerRegisteredEventArgs(string loggerName, string? outputFilePath, LoggerVerbosity? verbosity, IReadOnlyList? additionalOutputFilePaths) : base() + public LoggerRegisteredEventArgs(string loggerName, string? outputFilePath, LoggerVerbosity? verbosity, IReadOnlyList? additionalOutputFilePaths) + : base(FormatMessage(loggerName, outputFilePath), null, null, MessageImportance.Low) { OutputFilePath = outputFilePath; LoggerName = loggerName; Verbosity = verbosity; AdditionalOutputFilePaths = additionalOutputFilePaths; } + /// + /// Formats the message for the logger registered event, including the logger name and output file path if available. This only serves as a message for loggers that do not handle this event. + /// + /// The name of the logger. + /// The full path to the log output file. + /// A formatted message string. + private static string? FormatMessage(string loggerName, string? outputFilePath) + { + return !string.IsNullOrEmpty(outputFilePath) + ? $"{loggerName}: {outputFilePath}" + : null; + } /// /// The name of the logger. From e34ab6eebb61e48c0e520b5504355a06c350c472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Thu, 23 Apr 2026 09:09:02 +0200 Subject: [PATCH 06/19] adressed copilot review --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 13 ++++++++----- .../Logging/LoggingServiceLogMethods.cs | 7 +++++-- .../ParallelLogger/ParallelConsoleLogger.cs | 15 ++++++--------- .../Logging/TerminalLogger/TerminalLogger.cs | 4 +++- src/Build/Resources/Strings.resx | 4 ++-- src/Build/Resources/xlf/Strings.cs.xlf | 6 +++--- src/Build/Resources/xlf/Strings.de.xlf | 6 +++--- src/Build/Resources/xlf/Strings.es.xlf | 6 +++--- src/Build/Resources/xlf/Strings.fr.xlf | 6 +++--- src/Build/Resources/xlf/Strings.it.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ja.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ko.xlf | 6 +++--- src/Build/Resources/xlf/Strings.pl.xlf | 6 +++--- src/Build/Resources/xlf/Strings.pt-BR.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ru.xlf | 6 +++--- src/Build/Resources/xlf/Strings.tr.xlf | 6 +++--- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 6 +++--- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 6 +++--- src/Framework/LoggerRegisteredEventArgs.cs | 2 +- 19 files changed, 64 insertions(+), 59 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 4bea34a8346..b5f97b2a674 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1055,8 +1055,10 @@ public void LogBuildStartedLoggerNames() service.RegisterLogger(consoleLogger); service.LogBuildStarted(); - Assert.IsType(service.ProcessedBuildEvent); - Assert.Contains("ConsoleLogger", service.ProcessedBuildEvent.Message); + var enabledLogsEvent = service.AllProcessedBuildEvents + .OfType() + .FirstOrDefault(e => e is not LoggerRegisteredEventArgs && e.Message?.Contains("ConsoleLogger") == true); + enabledLogsEvent.ShouldNotBeNull(); } [Fact] @@ -1082,11 +1084,12 @@ public void LogFilePathsPresentInFileLog() .OfType() .FirstOrDefault(e => e.LoggerName == nameof(FileLogger)); registeredEvent.ShouldNotBeNull(); - registeredEvent.OutputFilePath.ShouldContain(".log"); + var expectedPath = Path.GetFullPath(logFilePath); + registeredEvent.OutputFilePath.ShouldBe(expectedPath); - // Check the file log itself contains the path + // Check the file log itself contains the exact path var fileLogContents = File.ReadAllText(logFilePath); - fileLogContents.ShouldContain(".log"); + fileLogContents.ShouldContain(expectedPath); } [Fact] diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index bc0d53ee16e..962d6550bb8 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -363,8 +363,11 @@ public void LogBuildStarted() WaitForLoggingToProcessEvents(); // Register Loggers and print out all the enabled loggers. - LogEnabledLoggers(); - RegisterLoggers(); + if (!OnlyLogCriticalEvents) + { + LogEnabledLoggers(); + RegisterLoggers(); + } } /// diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index b1841cd07aa..7feaef155b9 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -205,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>(s_compareContextNodeId); @@ -281,20 +282,16 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs { if (!string.IsNullOrEmpty(logger.OutputFilePath)) { - if (setColor != DontSetColor) - { - WriteLinePretty($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); - } - else - { - WriteLinePretty($" {logger.LoggerName}: {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.but if it was ne + // specified to show the summary. if (ShowSummary == true) { if (e.Succeeded) diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 3b3891a3394..f10f3e59316 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -640,7 +640,8 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) { if (!string.IsNullOrEmpty(logger.OutputFilePath)) { - Terminal.WriteLine($" {logger.LoggerName}: {AnsiCodes.LinkPrefix}file:///{logger.OutputFilePath}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"); + string displayPath = $"{AnsiCodes.LinkPrefix}{new Uri(logger.OutputFilePath).AbsoluteUri}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"; + Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath)); } } } @@ -671,6 +672,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) _projects.Clear(); _testRunSummaries.Clear(); + _registeredLoggers.Clear(); _buildErrorsCount = 0; _buildWarningsCount = 0; _restoreFailed = false; diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index bcc84b60689..920f21949d7 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -171,8 +171,8 @@ Build succeeded. - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0} is the logger name. {1} is the full file path. Enabled logs: {0} diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 2e7a9a6ba14..8372176b2ed 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index adcd250c7d0..5f2fafcee39 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 06648fbd246..7ad82fd98f8 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index a32398e387a..52e76f05d16 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 112956dced0..19b13e7cfbd 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index a7364ae64bd..76b9b457fba 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 432aa07b64a..1236d6796ca 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 3d6a59e3789..3236553ef4e 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index cbe53499c8f..5f595327eb8 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 3eddd79728a..4bd98e9ad06 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index a83a678a86f..9b8d2c60021 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index d0d33fc6eee..880e66c7684 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 4b4ee4146a3..3cda0787f54 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - Log output file: {0} - Log output file: {0} - {0} is the full file path. + {0}: {1} + {0}: {1} + {0} is the logger name. {1} is the full file path. Logging verbosity is set to: {0}. diff --git a/src/Framework/LoggerRegisteredEventArgs.cs b/src/Framework/LoggerRegisteredEventArgs.cs index 4f3514bf4fb..7aca96a5031 100644 --- a/src/Framework/LoggerRegisteredEventArgs.cs +++ b/src/Framework/LoggerRegisteredEventArgs.cs @@ -12,7 +12,7 @@ namespace Microsoft.Build.Framework [Serializable] public class LoggerRegisteredEventArgs : BuildMessageEventArgs { - public LoggerRegisteredEventArgs() + protected LoggerRegisteredEventArgs() { } /// From aa42bf5ef77adbb1302839f4a7bc1d2b150f2fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Thu, 23 Apr 2026 12:24:49 +0200 Subject: [PATCH 07/19] adressed build issues and comments --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 7 +- .../BackEnd/NodePackets_Tests.cs | 7 + .../Logging/LoggingServiceLogMethods.cs | 32 +++-- .../ParallelLogger/ParallelConsoleLogger.cs | 10 +- .../Logging/TerminalLogger/TerminalLogger.cs | 8 +- src/Framework/LoggerRegisteredEventArgs.cs | 130 +++++++++++++----- src/Shared/LogMessagePacketBase.cs | 10 ++ 7 files changed, 147 insertions(+), 57 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index b5f97b2a674..1c062961c24 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1079,13 +1079,14 @@ public void LogFilePathsPresentInFileLog() project.Build(new ILogger[] { fileLogger, mockLogger }).ShouldBeTrue(); } - // Check that MockLogger captured a LoggerRegisteredEventArgs with the file logger path + // Check that MockLogger captured a LoggerRegisteredEventArgs containing the file logger path var registeredEvent = mockLogger.AllBuildEvents .OfType() - .FirstOrDefault(e => e.LoggerName == nameof(FileLogger)); + .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); - registeredEvent.OutputFilePath.ShouldBe(expectedPath); + fileLoggerDesc.OutputFilePaths.ShouldContain(expectedPath); // Check the file log itself contains the exact path var fileLogContents = File.ReadAllText(logFilePath); diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs index 8ca50416de7..6286e4dcd60 100644 --- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs @@ -82,6 +82,7 @@ public void VerifyEventType() BuildCheckTracingEventArgs buildCheckTracing = new(); BuildCanceledEventArgs buildCanceled = new("message", DateTime.UtcNow); WorkerNodeTelemetryEventArgs workerNodeTelemetry = new(); + LoggerRegisteredEventArgs loggerRegistered = new(new List { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }) }); VerifyLoggingPacket(buildFinished, LoggingEventType.BuildFinishedEvent); VerifyLoggingPacket(buildStarted, LoggingEventType.BuildStartedEvent); @@ -119,6 +120,7 @@ public void VerifyEventType() VerifyLoggingPacket(buildCheckTracing, LoggingEventType.BuildCheckTracingEvent); VerifyLoggingPacket(buildCanceled, LoggingEventType.BuildCanceledEvent); VerifyLoggingPacket(workerNodeTelemetry, LoggingEventType.WorkerNodeTelemetryEvent); + VerifyLoggingPacket(loggerRegistered, LoggingEventType.LoggerRegisteredEvent); } private static BuildEventContext CreateBuildEventContext() @@ -321,6 +323,11 @@ public void TestTranslation() BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) }, new GeneratedFileUsedEventArgs("path", "some content"), + new LoggerRegisteredEventArgs(new List + { + new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }), + new RegisteredLoggerInfo("BinaryLogger"), + }), }; foreach (BuildEventArgs arg in testArgs) { diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 962d6550bb8..824e288fe3b 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -408,10 +408,6 @@ private void LogEnabledLoggers() foreach (ILogger logger in Loggers) { ILogger actualLogger = UnwrapLogger(logger); - if (actualLogger is CentralForwardingLogger == true) - { - continue; - } listOfLoggers.Add(actualLogger.GetType().Name); } if (listOfLoggers.Count != 0) @@ -429,23 +425,31 @@ private void LogEnabledLoggers() /// private void RegisterLoggers() { + var loggerDescriptions = new List(); foreach (ILogger logger in Loggers) { ILogger actualLogger = UnwrapLogger(logger); - string outputFilePath = actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath) - ? fileLogger.OutputFilePath - : null; + var outputFilePaths = new List(); + if (actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath)) + { + outputFilePaths.Add(fileLogger.OutputFilePath); + } - IReadOnlyList additionalPaths = actualLogger is BinaryLogger bl - ? bl.AdditionalFilePaths - : null; + if (actualLogger is BinaryLogger bl && bl.AdditionalFilePaths != null) + { + outputFilePaths.AddRange(bl.AdditionalFilePaths); + } - var registerEvent = new LoggerRegisteredEventArgs( + loggerDescriptions.Add(new RegisteredLoggerInfo( loggerName: actualLogger.GetType().Name, - outputFilePath: outputFilePath, - verbosity: actualLogger.Verbosity, - additionalOutputFilePaths: additionalPaths); + outputFilePaths: outputFilePaths.Count > 0 ? outputFilePaths : null, + verbosity: actualLogger.Verbosity)); + } + + if (loggerDescriptions.Count > 0) + { + var registerEvent = new LoggerRegisteredEventArgs(loggerDescriptions); registerEvent.BuildEventContext = BuildEventContext.Invalid; ProcessLoggingEvent(registerEvent); } diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index 7feaef155b9..3ded717f033 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -29,7 +29,7 @@ internal class ParallelConsoleLogger : BaseConsoleLogger /// Associate a (nodeID and project_context_id) to a target framework. /// internal Dictionary<(int nodeId, int contextId), string> propertyOutputMap = new Dictionary<(int nodeId, int contextId), string>(); - private readonly List _registeredLoggers = new List(); + private readonly List _registeredLoggers = new List(); #region Constructors /// /// Default constructor. @@ -280,11 +280,11 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs { foreach (var logger in _registeredLoggers) { - if (!string.IsNullOrEmpty(logger.OutputFilePath)) + foreach (var outputPath in logger.OutputFilePaths) { string displayPath = setColor != DontSetColor - ? $"{AnsiCodes.LinkPrefix}{new Uri(logger.OutputFilePath).AbsoluteUri}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}" - : logger.OutputFilePath; + ? $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}" + : outputPath; WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath)); } } @@ -1143,7 +1143,7 @@ public override void MessageHandler(object sender, BuildMessageEventArgs e) } if (e is LoggerRegisteredEventArgs loggerEvent) { - _registeredLoggers.Add(loggerEvent); + _registeredLoggers.AddRange(loggerEvent.Loggers); return; } if (e.BuildEventContext == null && e is AssemblyLoadBuildEventArgs) diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index f10f3e59316..b66aeb915b1 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -224,7 +224,7 @@ public EvalContext(BuildEventContext context) /// /// Stores the registered loggers. /// - private readonly List _registeredLoggers = new(); + private readonly List _registeredLoggers = new(); private uint? _originalConsoleMode; @@ -638,9 +638,9 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) { foreach (var logger in _registeredLoggers) { - if (!string.IsNullOrEmpty(logger.OutputFilePath)) + foreach (var outputPath in logger.OutputFilePaths) { - string displayPath = $"{AnsiCodes.LinkPrefix}{new Uri(logger.OutputFilePath).AbsoluteUri}{AnsiCodes.LinkInfix}{logger.OutputFilePath}{AnsiCodes.LinkSuffix}"; + string displayPath = $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}"; Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath)); } } @@ -1196,7 +1196,7 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) } if (e is LoggerRegisteredEventArgs loggerEvent) { - _registeredLoggers.Add(loggerEvent); + _registeredLoggers.AddRange(loggerEvent.Loggers); return; } string? message = e.Message; diff --git a/src/Framework/LoggerRegisteredEventArgs.cs b/src/Framework/LoggerRegisteredEventArgs.cs index 7aca96a5031..ec414b60657 100644 --- a/src/Framework/LoggerRegisteredEventArgs.cs +++ b/src/Framework/LoggerRegisteredEventArgs.cs @@ -3,64 +3,132 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; namespace Microsoft.Build.Framework { /// - /// Arguments for the logger registered event, indicating that a logger has been registered. + /// Describes a single registered logger. /// [Serializable] - public class LoggerRegisteredEventArgs : BuildMessageEventArgs + public sealed class RegisteredLoggerInfo { - protected LoggerRegisteredEventArgs() - { - } /// - /// Initialize a new instance of the LoggerRegisteredEventArgs class. + /// Initialize a new instance of the RegisteredLoggerInfo class. /// - /// The name of the logger. - /// The full path to the log output file. - /// The verbosity level of the logger. - /// Additional output file paths for the logger. - public LoggerRegisteredEventArgs(string loggerName, string? outputFilePath, LoggerVerbosity? verbosity, IReadOnlyList? additionalOutputFilePaths) - : base(FormatMessage(loggerName, outputFilePath), null, null, MessageImportance.Low) + public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFilePaths = null, LoggerVerbosity? verbosity = null) { - OutputFilePath = outputFilePath; LoggerName = loggerName; + OutputFilePaths = outputFilePaths ?? Array.Empty(); Verbosity = verbosity; - AdditionalOutputFilePaths = additionalOutputFilePaths; } + /// - /// Formats the message for the logger registered event, including the logger name and output file path if available. This only serves as a message for loggers that do not handle this event. + /// The name of the logger. /// - /// The name of the logger. - /// The full path to the log output file. - /// A formatted message string. - private static string? FormatMessage(string loggerName, string? outputFilePath) - { - return !string.IsNullOrEmpty(outputFilePath) - ? $"{loggerName}: {outputFilePath}" - : null; - } + public string LoggerName { get; } /// - /// The name of the logger. + /// The output file paths for the logger. /// - public string LoggerName { get; set; } = string.Empty; + public IReadOnlyList OutputFilePaths { get; } /// /// The verbosity level of the logger. /// - public LoggerVerbosity? Verbosity { get; set; } + public LoggerVerbosity? Verbosity { get; } + } + + /// + /// Arguments for the logger registered event, containing one or more logger registrations. + /// + [Serializable] + public sealed class LoggerRegisteredEventArgs : BuildMessageEventArgs + { + internal LoggerRegisteredEventArgs() + { + } + + /// + /// Initialize a new instance of the LoggerRegisteredEventArgs class. + /// + /// The list of registered loggers. + public LoggerRegisteredEventArgs(IReadOnlyList loggers) + : base(FormatMessage(loggers), null, null, MessageImportance.Low) + { + Loggers = loggers; + } /// - /// The full path to the log output file. + /// Formats a summary message listing loggers with output paths. + /// This serves as a fallback message for loggers that do not handle this event. /// - public string? OutputFilePath { get; set; } + private static string? FormatMessage(IReadOnlyList loggers) + { + var withPaths = loggers.Where(l => l.OutputFilePaths.Count > 0).ToList(); + if (withPaths.Count == 0) + { + return null; + } + + return string.Join("; ", withPaths.Select(l => $"{l.LoggerName}: {string.Join(", ", l.OutputFilePaths)}")); + } /// - /// Additional output file paths for the logger. + /// The registered loggers. /// - public IReadOnlyList? AdditionalOutputFilePaths { get; set; } + public IReadOnlyList Loggers { get; internal set; } = Array.Empty(); + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Loggers.Count); + foreach (var logger in Loggers) + { + writer.Write(logger.LoggerName); + writer.Write(logger.Verbosity.HasValue); + if (logger.Verbosity.HasValue) + { + writer.Write((int)logger.Verbosity.Value); + } + + writer.Write(logger.OutputFilePaths.Count); + foreach (var path in logger.OutputFilePaths) + { + writer.Write(path); + } + } + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + int count = reader.ReadInt32(); + var loggers = new List(count); + for (int i = 0; i < count; i++) + { + string loggerName = reader.ReadString(); + + LoggerVerbosity? verbosity = null; + if (reader.ReadBoolean()) + { + verbosity = (LoggerVerbosity)reader.ReadInt32(); + } + + int pathCount = reader.ReadInt32(); + var outputFilePaths = new string[pathCount]; + for (int j = 0; j < pathCount; j++) + { + outputFilePaths[j] = reader.ReadString(); + } + + loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity)); + } + + Loggers = loggers; + } } } diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 4306a91986d..dc98e84c5a6 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -243,6 +243,11 @@ internal enum LoggingEventType : int /// Event is /// WorkerNodeTelemetryEvent = 42, + + /// + /// Event is + /// + LoggerRegisteredEvent = 43, } #endregion @@ -543,6 +548,7 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.BuildSubmissionStartedEvent => new BuildSubmissionStartedEventArgs(), LoggingEventType.BuildCanceledEvent => new BuildCanceledEventArgs("Build canceled."), LoggingEventType.WorkerNodeTelemetryEvent => new WorkerNodeTelemetryEventArgs(), + LoggingEventType.LoggerRegisteredEvent => new LoggerRegisteredEventArgs(), _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) }; @@ -690,6 +696,10 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.WorkerNodeTelemetryEvent; } + else if (eventType == typeof(LoggerRegisteredEventArgs)) + { + return LoggingEventType.LoggerRegisteredEvent; + } else if (eventType == typeof(TargetStartedEventArgs)) { return LoggingEventType.TargetStartedEvent; From f8cd09f63304887a32edba4af0c1bf5934f52b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Thu, 23 Apr 2026 16:34:24 +0200 Subject: [PATCH 08/19] changed inheritance, added binary log handling --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 2 +- .../BinaryLogger/BinaryLogRecordKind.cs | 1 + .../Logging/BinaryLogger/BinaryLogger.cs | 2 +- .../BinaryLogger/BuildEventArgsReader.cs | 32 +++++++++++++++++++ .../BinaryLogger/BuildEventArgsWriter.cs | 24 ++++++++++++++ .../ParallelLogger/ParallelConsoleLogger.cs | 11 +++---- .../Logging/TerminalLogger/TerminalLogger.cs | 8 ++--- src/Framework/LoggerRegisteredEventArgs.cs | 6 ++-- 8 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 1c062961c24..5faf42503c2 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1057,7 +1057,7 @@ public void LogBuildStartedLoggerNames() service.LogBuildStarted(); var enabledLogsEvent = service.AllProcessedBuildEvents .OfType() - .FirstOrDefault(e => e is not LoggerRegisteredEventArgs && e.Message?.Contains("ConsoleLogger") == true); + .FirstOrDefault(e => e.Message?.Contains("ConsoleLogger") == true); enabledLogsEvent.ShouldNotBeNull(); } diff --git a/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs b/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs index d87b80c363f..99d33c6de2e 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs @@ -45,5 +45,6 @@ public enum BinaryLogRecordKind BuildCheckAcquisition, BuildSubmissionStarted, BuildCanceled, + LoggerRegistered, } } diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index fea26573a32..06937285840 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -119,7 +119,7 @@ public sealed class BinaryLogger : ILogger, IFileOutputLogger // 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; // 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 diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index 69afeee1674..d8fa358b00a 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -330,6 +330,7 @@ void HandleError(FormatErrorMessage msgFactory, bool noThrow, ReaderErrorType re BinaryLogRecordKind.BuildCheckTracing => ReadBuildCheckTracingEventArgs(), BinaryLogRecordKind.BuildCheckAcquisition => ReadBuildCheckAcquisitionEventArgs(), BinaryLogRecordKind.BuildCanceled => ReadBuildCanceledEventArgs(), + BinaryLogRecordKind.LoggerRegistered => ReadLoggerRegisteredEventArgs(), _ => null }; @@ -1287,6 +1288,37 @@ private BuildEventArgs ReadBuildCanceledEventArgs() return e; } + private BuildEventArgs ReadLoggerRegisteredEventArgs() + { + var fields = ReadBuildEventArgsFields(); + int count = ReadInt32(); + var loggers = new List(count); + for (int i = 0; i < count; i++) + { + string loggerName = 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)); + } + + var e = new LoggerRegisteredEventArgs(loggers); + SetCommonFields(e, fields); + + return e; + } + /// /// 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 diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs index f1a1b6d47e1..2678b86b385 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs @@ -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 LoggerRegisteredEventArgs loggerRegistered: return Write(loggerRegistered); default: // convert all unrecognized objects to message // and just preserve the message @@ -316,6 +317,29 @@ private BinaryLogRecordKind Write(BuildCanceledEventArgs e) return BinaryLogRecordKind.BuildCanceled; } + private BinaryLogRecordKind Write(LoggerRegisteredEventArgs e) + { + WriteBuildEventArgsFields(e); + Write(e.Loggers.Count); + foreach (var logger in e.Loggers) + { + WriteDeduplicatedString(logger.LoggerName); + 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.LoggerRegistered; + } + private BinaryLogRecordKind Write(ProjectEvaluationStartedEventArgs e) { WriteBuildEventArgsFields(e, writeMessage: false); diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index 3ded717f033..6a43a8d55af 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -29,7 +29,7 @@ internal class ParallelConsoleLogger : BaseConsoleLogger /// Associate a (nodeID and project_context_id) to a target framework. /// internal Dictionary<(int nodeId, int contextId), string> propertyOutputMap = new Dictionary<(int nodeId, int contextId), string>(); - private readonly List _registeredLoggers = new List(); + private readonly List _registeredLoggers = new(); #region Constructors /// /// Default constructor. @@ -1141,11 +1141,6 @@ public override void MessageHandler(object sender, BuildMessageEventArgs e) { return; } - if (e is LoggerRegisteredEventArgs loggerEvent) - { - _registeredLoggers.AddRange(loggerEvent.Loggers); - return; - } if (e.BuildEventContext == null && e is AssemblyLoadBuildEventArgs) { return; @@ -1234,6 +1229,10 @@ public override void StatusEventHandler(object sender, BuildStatusEventArgs e) propertyOutputMap[evaluationKey] = value; } } + else if (e is LoggerRegisteredEventArgs loggerEvent) + { + _registeredLoggers.AddRange(loggerEvent.Loggers); + } else if (e is BuildCanceledEventArgs buildCanceled) { Console.WriteLine(e.Message); diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index b66aeb915b1..e495d10a9b2 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -719,6 +719,9 @@ private void StatusEventRaised(object sender, BuildStatusEventArgs e) case ProjectEvaluationFinishedEventArgs evalFinish: CaptureEvalContext(evalFinish); break; + case LoggerRegisteredEventArgs loggerEvent: + _registeredLoggers.AddRange(loggerEvent.Loggers); + break; } } @@ -1194,11 +1197,6 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) { return; } - if (e is LoggerRegisteredEventArgs loggerEvent) - { - _registeredLoggers.AddRange(loggerEvent.Loggers); - return; - } string? message = e.Message; if (message is not null && e.Importance == MessageImportance.High) diff --git a/src/Framework/LoggerRegisteredEventArgs.cs b/src/Framework/LoggerRegisteredEventArgs.cs index ec414b60657..a8a96d929e2 100644 --- a/src/Framework/LoggerRegisteredEventArgs.cs +++ b/src/Framework/LoggerRegisteredEventArgs.cs @@ -44,7 +44,7 @@ public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFile /// Arguments for the logger registered event, containing one or more logger registrations. /// [Serializable] - public sealed class LoggerRegisteredEventArgs : BuildMessageEventArgs + public sealed class LoggerRegisteredEventArgs : BuildStatusEventArgs { internal LoggerRegisteredEventArgs() { @@ -55,7 +55,7 @@ internal LoggerRegisteredEventArgs() /// /// The list of registered loggers. public LoggerRegisteredEventArgs(IReadOnlyList loggers) - : base(FormatMessage(loggers), null, null, MessageImportance.Low) + : base(FormatMessage(loggers), null, null) { Loggers = loggers; } @@ -69,7 +69,7 @@ public LoggerRegisteredEventArgs(IReadOnlyList loggers) var withPaths = loggers.Where(l => l.OutputFilePaths.Count > 0).ToList(); if (withPaths.Count == 0) { - return null; + return string.Empty; } return string.Join("; ", withPaths.Select(l => $"{l.LoggerName}: {string.Join(", ", l.OutputFilePaths)}")); From f824ba2027cdc5a966d51cf0178d019f370fb6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Fri, 24 Apr 2026 08:25:30 +0200 Subject: [PATCH 09/19] adressed failing unit tests --- src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs | 6 +++--- src/Utilities.UnitTests/MuxLogger_Tests.cs | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs b/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs index fe19764230c..489b7b67d98 100644 --- a/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs +++ b/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs @@ -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); } @@ -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); diff --git a/src/Utilities.UnitTests/MuxLogger_Tests.cs b/src/Utilities.UnitTests/MuxLogger_Tests.cs index b4228b11909..a2f0ab0f746 100644 --- a/src/Utilities.UnitTests/MuxLogger_Tests.cs +++ b/src/Utilities.UnitTests/MuxLogger_Tests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Text.RegularExpressions; using System.Threading; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; @@ -126,7 +127,10 @@ public void BuildWithMuxLoggerEquivalentToNormalLogger() // This test was changed to not compare new lines because of https://github.com/dotnet/msbuild/issues/10493 // It will need to be changed once we fix the root cause of the issue - mockLogger.FullLog.Replace(Environment.NewLine, "").ShouldBe(mockLogger2.FullLog.Replace(Environment.NewLine, "")); + // Strip "Enabled logs: ..." line because MuxLogger sub-loggers are registered after BuildStarted + // and cannot receive messages emitted during BuildStarted processing. + string StripEnabledLogs(string log) => Regex.Replace(log.Replace(Environment.NewLine, ""), @"Enabled logs: .+?(?=Project |$)", ""); + StripEnabledLogs(mockLogger.FullLog).ShouldBe(StripEnabledLogs(mockLogger2.FullLog)); } /// From 7db65095385e0614a676c6d35f11cbd1f5b0722c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Tue, 5 May 2026 15:41:45 +0200 Subject: [PATCH 10/19] adressing comments --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 6 ++-- .../BackEnd/NodePackets_Tests.cs | 6 ++-- .../Logging/LoggingServiceLogMethods.cs | 29 ++++++++------- .../BinaryLogger/BinaryLogRecordKind.cs | 4 +-- .../Logging/BinaryLogger/BinaryLogger.cs | 15 +++++++- .../BinaryLogger/BuildEventArgsReader.cs | 10 +++--- .../BinaryLogger/BuildEventArgsWriter.cs | 8 +++-- src/Build/Logging/FileLogger.cs | 2 +- src/Build/Logging/IFileOutputLogger.cs | 2 +- .../ParallelLogger/ParallelConsoleLogger.cs | 22 ++++++------ .../Logging/TerminalLogger/TerminalLogger.cs | 18 +++++----- src/Build/Resources/Strings.resx | 4 +-- ...tArgs.cs => LoggersRegisteredEventArgs.cs} | 36 ++++++++++++++----- src/Framework/Resources/SR.resx | 6 +++- src/Shared/LogMessagePacketBase.cs | 10 +++--- src/Utilities.UnitTests/MuxLogger_Tests.cs | 7 ++-- 16 files changed, 120 insertions(+), 65 deletions(-) rename src/Framework/{LoggerRegisteredEventArgs.cs => LoggersRegisteredEventArgs.cs} (73%) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 5faf42503c2..75a0ec1f9ad 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1079,14 +1079,16 @@ public void LogFilePathsPresentInFileLog() project.Build(new ILogger[] { fileLogger, mockLogger }).ShouldBeTrue(); } - // Check that MockLogger captured a LoggerRegisteredEventArgs containing the file logger path + // Check that MockLogger captured a LoggersRegisteredEventArgs containing the file logger path var registeredEvent = mockLogger.AllBuildEvents - .OfType() + .OfType() .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); diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs index 6286e4dcd60..79594cde9c1 100644 --- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs @@ -82,7 +82,7 @@ public void VerifyEventType() BuildCheckTracingEventArgs buildCheckTracing = new(); BuildCanceledEventArgs buildCanceled = new("message", DateTime.UtcNow); WorkerNodeTelemetryEventArgs workerNodeTelemetry = new(); - LoggerRegisteredEventArgs loggerRegistered = new(new List { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }) }); + LoggersRegisteredEventArgs loggersRegistered = new(new List { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }) }); VerifyLoggingPacket(buildFinished, LoggingEventType.BuildFinishedEvent); VerifyLoggingPacket(buildStarted, LoggingEventType.BuildStartedEvent); @@ -120,7 +120,7 @@ public void VerifyEventType() VerifyLoggingPacket(buildCheckTracing, LoggingEventType.BuildCheckTracingEvent); VerifyLoggingPacket(buildCanceled, LoggingEventType.BuildCanceledEvent); VerifyLoggingPacket(workerNodeTelemetry, LoggingEventType.WorkerNodeTelemetryEvent); - VerifyLoggingPacket(loggerRegistered, LoggingEventType.LoggerRegisteredEvent); + VerifyLoggingPacket(loggersRegistered, LoggingEventType.LoggersRegisteredEvent); } private static BuildEventContext CreateBuildEventContext() @@ -323,7 +323,7 @@ public void TestTranslation() BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) }, new GeneratedFileUsedEventArgs("path", "some content"), - new LoggerRegisteredEventArgs(new List + new LoggersRegisteredEventArgs(new List { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }), new RegisteredLoggerInfo("BinaryLogger"), diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 824e288fe3b..321933a19e9 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -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; @@ -408,7 +408,8 @@ private void LogEnabledLoggers() foreach (ILogger logger in Loggers) { ILogger actualLogger = UnwrapLogger(logger); - listOfLoggers.Add(actualLogger.GetType().Name); + Type loggerType = actualLogger.GetType(); + listOfLoggers.Add(loggerType.FullName ?? loggerType.Name); } if (listOfLoggers.Count != 0) { @@ -429,27 +430,31 @@ private void RegisterLoggers() foreach (ILogger logger in Loggers) { ILogger actualLogger = UnwrapLogger(logger); + Type loggerType = actualLogger.GetType(); var outputFilePaths = new List(); - if (actualLogger is IFileOutputLogger fileLogger && !string.IsNullOrEmpty(fileLogger.OutputFilePath)) + if (actualLogger is IFileOutputLogger fileLogger) { - outputFilePaths.Add(fileLogger.OutputFilePath); - } - - if (actualLogger is BinaryLogger bl && bl.AdditionalFilePaths != null) - { - outputFilePaths.AddRange(bl.AdditionalFilePaths); + foreach (string outputFilePath in fileLogger.OutputFilePaths) + { + if (!string.IsNullOrEmpty(outputFilePath)) + { + outputFilePaths.Add(outputFilePath); + } + } } loggerDescriptions.Add(new RegisteredLoggerInfo( - loggerName: actualLogger.GetType().Name, + loggerName: loggerType.Name, outputFilePaths: outputFilePaths.Count > 0 ? outputFilePaths : null, - verbosity: actualLogger.Verbosity)); + verbosity: actualLogger.Verbosity, + loggerTypeFullName: loggerType.FullName, + parameters: actualLogger.Parameters)); } if (loggerDescriptions.Count > 0) { - var registerEvent = new LoggerRegisteredEventArgs(loggerDescriptions); + var registerEvent = new LoggersRegisteredEventArgs(loggerDescriptions); registerEvent.BuildEventContext = BuildEventContext.Invalid; ProcessLoggingEvent(registerEvent); } diff --git a/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs b/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs index 99d33c6de2e..969cb0f400a 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs @@ -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 @@ -45,6 +45,6 @@ public enum BinaryLogRecordKind BuildCheckAcquisition, BuildSubmissionStarted, BuildCanceled, - LoggerRegistered, + LoggersRegistered, } } diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 06937285840..6f5be922679 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -298,7 +298,20 @@ private static bool TryParsePathParameter(string parameter, out string filePath) internal string FilePath { get; private set; } /// - string IFileOutputLogger.OutputFilePath => FilePath; + System.Collections.Generic.IReadOnlyList IFileOutputLogger.OutputFilePaths + { + get + { + if (AdditionalFilePaths == null || AdditionalFilePaths.Count == 0) + { + return new[] { FilePath }; + } + + var outputFilePaths = new List(AdditionalFilePaths.Count + 1) { FilePath }; + outputFilePaths.AddRange(AdditionalFilePaths); + return outputFilePaths; + } + } /// /// Gets or sets additional output file paths. When set, the binlog will be copied to all these paths diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index d8fa358b00a..ece6c23e610 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -330,7 +330,7 @@ void HandleError(FormatErrorMessage msgFactory, bool noThrow, ReaderErrorType re BinaryLogRecordKind.BuildCheckTracing => ReadBuildCheckTracingEventArgs(), BinaryLogRecordKind.BuildCheckAcquisition => ReadBuildCheckAcquisitionEventArgs(), BinaryLogRecordKind.BuildCanceled => ReadBuildCanceledEventArgs(), - BinaryLogRecordKind.LoggerRegistered => ReadLoggerRegisteredEventArgs(), + BinaryLogRecordKind.LoggersRegistered => ReadLoggersRegisteredEventArgs(), _ => null }; @@ -1288,7 +1288,7 @@ private BuildEventArgs ReadBuildCanceledEventArgs() return e; } - private BuildEventArgs ReadLoggerRegisteredEventArgs() + private BuildEventArgs ReadLoggersRegisteredEventArgs() { var fields = ReadBuildEventArgsFields(); int count = ReadInt32(); @@ -1296,6 +1296,8 @@ private BuildEventArgs ReadLoggerRegisteredEventArgs() for (int i = 0; i < count; i++) { string loggerName = ReadDeduplicatedString()!; + string loggerTypeFullName = ReadDeduplicatedString()!; + string parameters = ReadDeduplicatedString()!; LoggerVerbosity? verbosity = null; if (ReadBoolean()) @@ -1310,10 +1312,10 @@ private BuildEventArgs ReadLoggerRegisteredEventArgs() outputFilePaths[j] = ReadDeduplicatedString()!; } - loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity)); + loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, loggerTypeFullName, parameters)); } - var e = new LoggerRegisteredEventArgs(loggers); + var e = new LoggersRegisteredEventArgs(loggers); SetCommonFields(e, fields); return e; diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs index 2678b86b385..907fe74e711 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs @@ -221,7 +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 LoggerRegisteredEventArgs loggerRegistered: return Write(loggerRegistered); + case LoggersRegisteredEventArgs loggersRegistered: return Write(loggersRegistered); default: // convert all unrecognized objects to message // and just preserve the message @@ -317,13 +317,15 @@ private BinaryLogRecordKind Write(BuildCanceledEventArgs e) return BinaryLogRecordKind.BuildCanceled; } - private BinaryLogRecordKind Write(LoggerRegisteredEventArgs e) + 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) { @@ -337,7 +339,7 @@ private BinaryLogRecordKind Write(LoggerRegisteredEventArgs e) } } - return BinaryLogRecordKind.LoggerRegistered; + return BinaryLogRecordKind.LoggersRegistered; } private BinaryLogRecordKind Write(ProjectEvaluationStartedEventArgs e) diff --git a/src/Build/Logging/FileLogger.cs b/src/Build/Logging/FileLogger.cs index 62b275c3311..e7763fb43cf 100644 --- a/src/Build/Logging/FileLogger.cs +++ b/src/Build/Logging/FileLogger.cs @@ -244,7 +244,7 @@ private void ApplyFileLoggerParameter(string parameterName, string parameterValu internal string FilePath => Path.GetFullPath(_logFileName); /// - string IFileOutputLogger.OutputFilePath => FilePath; + System.Collections.Generic.IReadOnlyList IFileOutputLogger.OutputFilePaths => new[] { FilePath }; /// /// fileWriter is the stream that has been opened on our log file. diff --git a/src/Build/Logging/IFileOutputLogger.cs b/src/Build/Logging/IFileOutputLogger.cs index 7336155903c..530a1506dd1 100644 --- a/src/Build/Logging/IFileOutputLogger.cs +++ b/src/Build/Logging/IFileOutputLogger.cs @@ -5,6 +5,6 @@ namespace Microsoft.Build.Logging { internal interface IFileOutputLogger { - string OutputFilePath { get; } + System.Collections.Generic.IReadOnlyList OutputFilePaths { get; } } } diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index 6a43a8d55af..331a5350cd3 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -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; @@ -276,17 +276,19 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs } // Show paths to the files created by enabled loggers. - if (ShowSummary == true) + if (ShowSummary == true && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) { - foreach (var logger in _registeredLoggers) + WriteNewLine(); + + foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) { - foreach (var outputPath in logger.OutputFilePaths) - { - string displayPath = setColor != DontSetColor + string displayPaths = string.Join( + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", + logger.OutputFilePaths.Select(outputPath => setColor != DontSetColor ? $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}" - : outputPath; - WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath)); - } + : outputPath)); + + WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); } } @@ -1229,7 +1231,7 @@ public override void StatusEventHandler(object sender, BuildStatusEventArgs e) propertyOutputMap[evaluationKey] = value; } } - else if (e is LoggerRegisteredEventArgs loggerEvent) + else if (e is LoggersRegisteredEventArgs loggerEvent) { _registeredLoggers.AddRange(loggerEvent.Loggers); } diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index e495d10a9b2..fa2f7090e5a 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -634,15 +634,17 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) RenderBuildSummary(); } - if (_showSummary == true) + if (_showSummary == true && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) { - foreach (var logger in _registeredLoggers) + Terminal.WriteLine(string.Empty); + + foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) { - foreach (var outputPath in logger.OutputFilePaths) - { - string displayPath = $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}"; - Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPath)); - } + string displayPaths = string.Join( + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", + logger.OutputFilePaths.Select(outputPath => $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}")); + + Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); } } @@ -719,7 +721,7 @@ private void StatusEventRaised(object sender, BuildStatusEventArgs e) case ProjectEvaluationFinishedEventArgs evalFinish: CaptureEvalContext(evalFinish); break; - case LoggerRegisteredEventArgs loggerEvent: + case LoggersRegisteredEventArgs loggerEvent: _registeredLoggers.AddRange(loggerEvent.Loggers); break; } diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 920f21949d7..42f195f0ce3 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -171,8 +171,8 @@ Build succeeded. - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Enabled logs: {0} diff --git a/src/Framework/LoggerRegisteredEventArgs.cs b/src/Framework/LoggersRegisteredEventArgs.cs similarity index 73% rename from src/Framework/LoggerRegisteredEventArgs.cs rename to src/Framework/LoggersRegisteredEventArgs.cs index a8a96d929e2..1dd2ba5a8aa 100644 --- a/src/Framework/LoggerRegisteredEventArgs.cs +++ b/src/Framework/LoggersRegisteredEventArgs.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; @@ -17,11 +18,13 @@ public sealed class RegisteredLoggerInfo /// /// Initialize a new instance of the RegisteredLoggerInfo class. /// - public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFilePaths = null, LoggerVerbosity? verbosity = null) + public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFilePaths = null, LoggerVerbosity? verbosity = null, string? loggerTypeFullName = null, string? parameters = null) { LoggerName = loggerName; OutputFilePaths = outputFilePaths ?? Array.Empty(); Verbosity = verbosity; + LoggerTypeFullName = loggerTypeFullName; + Parameters = parameters; } /// @@ -29,6 +32,16 @@ public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFile /// public string LoggerName { get; } + /// + /// The full type name of the logger. + /// + public string? LoggerTypeFullName { get; } + + /// + /// The logger parameters. + /// + public string? Parameters { get; } + /// /// The output file paths for the logger. /// @@ -41,20 +54,20 @@ public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFile } /// - /// Arguments for the logger registered event, containing one or more logger registrations. + /// Arguments for the loggers registered event, containing one or more logger registrations. /// [Serializable] - public sealed class LoggerRegisteredEventArgs : BuildStatusEventArgs + public sealed class LoggersRegisteredEventArgs : BuildStatusEventArgs { - internal LoggerRegisteredEventArgs() + internal LoggersRegisteredEventArgs() { } /// - /// Initialize a new instance of the LoggerRegisteredEventArgs class. + /// Initialize a new instance of the LoggersRegisteredEventArgs class. /// /// The list of registered loggers. - public LoggerRegisteredEventArgs(IReadOnlyList loggers) + public LoggersRegisteredEventArgs(IReadOnlyList loggers) : base(FormatMessage(loggers), null, null) { Loggers = loggers; @@ -72,7 +85,10 @@ public LoggerRegisteredEventArgs(IReadOnlyList loggers) return string.Empty; } - return string.Join("; ", withPaths.Select(l => $"{l.LoggerName}: {string.Join(", ", l.OutputFilePaths)}")); + return string.Join("; ", withPaths.Select(l => FormatResourceStringIgnoreCodeAndKeyword( + "LogFileOutputPath", + l.LoggerName, + string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", l.OutputFilePaths)))); } /// @@ -88,6 +104,8 @@ internal override void WriteToStream(BinaryWriter writer) foreach (var logger in Loggers) { writer.Write(logger.LoggerName); + writer.Write(logger.LoggerTypeFullName ?? string.Empty); + writer.Write(logger.Parameters ?? string.Empty); writer.Write(logger.Verbosity.HasValue); if (logger.Verbosity.HasValue) { @@ -111,6 +129,8 @@ internal override void CreateFromStream(BinaryReader reader, int version) for (int i = 0; i < count; i++) { string loggerName = reader.ReadString(); + string loggerTypeFullName = reader.ReadString(); + string parameters = reader.ReadString(); LoggerVerbosity? verbosity = null; if (reader.ReadBoolean()) @@ -125,7 +145,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) outputFilePaths[j] = reader.ReadString(); } - loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity)); + loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, loggerTypeFullName, parameters)); } Loggers = loggers; diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index ebcc88a74d5..cf6aed4da5c 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -153,6 +153,10 @@ Path must be rooted. + + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. @@ -199,4 +203,4 @@ '{0}' contains no elements. - \ No newline at end of file + diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index dc98e84c5a6..a53356706d4 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -245,9 +245,9 @@ internal enum LoggingEventType : int WorkerNodeTelemetryEvent = 42, /// - /// Event is + /// Event is /// - LoggerRegisteredEvent = 43, + LoggersRegisteredEvent = 43, } #endregion @@ -548,7 +548,7 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.BuildSubmissionStartedEvent => new BuildSubmissionStartedEventArgs(), LoggingEventType.BuildCanceledEvent => new BuildCanceledEventArgs("Build canceled."), LoggingEventType.WorkerNodeTelemetryEvent => new WorkerNodeTelemetryEventArgs(), - LoggingEventType.LoggerRegisteredEvent => new LoggerRegisteredEventArgs(), + LoggingEventType.LoggersRegisteredEvent => new LoggersRegisteredEventArgs(), _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) }; @@ -696,9 +696,9 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.WorkerNodeTelemetryEvent; } - else if (eventType == typeof(LoggerRegisteredEventArgs)) + else if (eventType == typeof(LoggersRegisteredEventArgs)) { - return LoggingEventType.LoggerRegisteredEvent; + return LoggingEventType.LoggersRegisteredEvent; } else if (eventType == typeof(TargetStartedEventArgs)) { diff --git a/src/Utilities.UnitTests/MuxLogger_Tests.cs b/src/Utilities.UnitTests/MuxLogger_Tests.cs index a2f0ab0f746..1e75d9fec93 100644 --- a/src/Utilities.UnitTests/MuxLogger_Tests.cs +++ b/src/Utilities.UnitTests/MuxLogger_Tests.cs @@ -127,8 +127,11 @@ public void BuildWithMuxLoggerEquivalentToNormalLogger() // This test was changed to not compare new lines because of https://github.com/dotnet/msbuild/issues/10493 // It will need to be changed once we fix the root cause of the issue - // Strip "Enabled logs: ..." line because MuxLogger sub-loggers are registered after BuildStarted - // and cannot receive messages emitted during BuildStarted processing. + // Strip "Enabled logs: ..." because the direct logger is registered before BeginBuild and receives all + // BuildStarted-time events. The MuxLogger itself is also registered before BeginBuild, but its per-submission + // sub-logger is added only after PendBuildRequest returns a submission ID. By then the global BuildStarted + // event has already been processed, so the MuxLogger replays BuildStarted to the sub-logger but cannot replay + // follow-up messages emitted during BuildStarted processing. string StripEnabledLogs(string log) => Regex.Replace(log.Replace(Environment.NewLine, ""), @"Enabled logs: .+?(?=Project |$)", ""); StripEnabledLogs(mockLogger.FullLog).ShouldBe(StripEnabledLogs(mockLogger2.FullLog)); } From d7ee4d5702d732b268a62dfc975747832f7ee075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Tue, 5 May 2026 16:31:17 +0200 Subject: [PATCH 11/19] fixed localization files --- src/Build/Logging/TerminalLogger/TerminalLogger.cs | 2 +- src/Framework/Resources/xlf/SR.cs.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.de.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.es.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.fr.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.it.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.ja.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.ko.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.pl.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.pt-BR.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.ru.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.tr.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.zh-Hans.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.zh-Hant.xlf | 7 ++++++- 14 files changed, 79 insertions(+), 14 deletions(-) diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index fa2f7090e5a..2c775b85e68 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -634,7 +634,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) RenderBuildSummary(); } - if (_showSummary == true && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) + if (_registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) { Terminal.WriteLine(string.Empty); diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index 32d075daa4f..5bf75709f15 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Cesta musí začínat kořenem. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Cesta: {0} překračuje maximální limit pro cestu k OS. Plně kvalifikovaný název souboru musí být kratší než {1} znaků. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 6b53f58a3fa..6a66a264318 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Der Pfad muss einen Stamm besitzen. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Der Pfad "{0}" überschreitet das maximale Pfadlimit des Betriebssystems. Der vollqualifizierte Dateiname muss weniger als {1} Zeichen umfassen. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index daf26188dba..742e6bd4185 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Debe ser una ruta de acceso raíz. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. La ruta de acceso {0} supera el límite máximo para la ruta de acceso del sistema operativo. El nombre de archivo completo debe ser inferior a {1} caracteres. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index 90d5bf48fa1..f966a0da348 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Le chemin doit être associé à une racine. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Le chemin {0} dépasse la limite maximale de chemin du système d'exploitation. Le nom du fichier qualifié complet doit contenir moins de {1} caractères. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 9f24bafe1ca..0d3a03f75ee 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Il percorso deve contenere una radice. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Il percorso {0} supera il limite massimo dei percorsi del sistema operativo. Il nome completo del file deve essere composto da meno di {1}. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 4490b72a34e..2b6d69569ea 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ パスはルート指定パスである必要があります。 + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index d4df3b1c552..4e15c4b401b 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ 루트 경로로 지정해야 합니다. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index 8a0af80daaf..d0df914c3e5 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Ścieżka musi zaczynać się od katalogu głównego. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Ścieżka: {0} przekracza limit maksymalnej długości ścieżki w systemie operacyjnym. W pełni kwalifikowana nazwa pliku musi się składać z mniej niż {1} znaków. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 0cf2bf2f94b..c2f667441fe 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Caminho deve ter raiz. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Caminho: {0} excede o limite máximo do caminho do SO. O nome do arquivo totalmente qualificado deve ter menos de {1} caracteres. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 19c782d0903..ec46636d56a 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Путь должен иметь корень. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index fd1ff3fa370..d7233b6017d 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Yol kökü belirtilmelidir. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Yol: {0}, işletim sisteminin en yüksek yol sınırını aşıyor. Tam dosya adı en fazla {1} karakter olmalıdır. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 5dcdb7945bc..84813033213 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ 路径必须是根路径。 + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index f70b3542096..1bbb0e4ceb0 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ 路徑必須為根路徑。 + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 From 221d1dd83a1c94f6d153f7c27fcf464ed2492520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Wed, 6 May 2026 08:36:56 +0200 Subject: [PATCH 12/19] updated xlf --- src/Build/Resources/xlf/Strings.cs.xlf | 6 +++--- src/Build/Resources/xlf/Strings.de.xlf | 6 +++--- src/Build/Resources/xlf/Strings.es.xlf | 6 +++--- src/Build/Resources/xlf/Strings.fr.xlf | 6 +++--- src/Build/Resources/xlf/Strings.it.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ja.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ko.xlf | 6 +++--- src/Build/Resources/xlf/Strings.pl.xlf | 6 +++--- src/Build/Resources/xlf/Strings.pt-BR.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ru.xlf | 6 +++--- src/Build/Resources/xlf/Strings.tr.xlf | 6 +++--- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 6 +++--- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 6 +++--- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 8372176b2ed..f115339bf22 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 5f2fafcee39..b6dce1680b5 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 7ad82fd98f8..9449996a6cb 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 52e76f05d16..f8b7eda8bc3 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 19b13e7cfbd..78631d39aa8 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 76b9b457fba..4bab38ef168 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 1236d6796ca..2dc7c5858c0 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 3236553ef4e..ab650580853 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 5f595327eb8..2408397cc91 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 4bd98e9ad06..0e35fa7eb5f 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 9b8d2c60021..93b3df84b56 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 880e66c7684..de2e26dadd5 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 3cda0787f54..0a92928fab6 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -596,9 +596,9 @@ {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - {0}: {1} - {0}: {1} - {0} is the logger name. {1} is the full file path. + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a comma-separated list of full file paths. Logging verbosity is set to: {0}. From 919503b1b8e67afb0f5b4fedc7424cf83c72d5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Wed, 6 May 2026 09:15:06 +0200 Subject: [PATCH 13/19] fix failing unit tests --- src/Framework/LoggersRegisteredEventArgs.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Framework/LoggersRegisteredEventArgs.cs b/src/Framework/LoggersRegisteredEventArgs.cs index 1dd2ba5a8aa..e36a65e1d2f 100644 --- a/src/Framework/LoggersRegisteredEventArgs.cs +++ b/src/Framework/LoggersRegisteredEventArgs.cs @@ -104,8 +104,8 @@ internal override void WriteToStream(BinaryWriter writer) foreach (var logger in Loggers) { writer.Write(logger.LoggerName); - writer.Write(logger.LoggerTypeFullName ?? string.Empty); - writer.Write(logger.Parameters ?? string.Empty); + writer.WriteOptionalString(logger.LoggerTypeFullName); + writer.WriteOptionalString(logger.Parameters); writer.Write(logger.Verbosity.HasValue); if (logger.Verbosity.HasValue) { @@ -129,8 +129,8 @@ internal override void CreateFromStream(BinaryReader reader, int version) for (int i = 0; i < count; i++) { string loggerName = reader.ReadString(); - string loggerTypeFullName = reader.ReadString(); - string parameters = reader.ReadString(); + string? loggerTypeFullName = reader.ReadOptionalString(); + string? parameters = reader.ReadOptionalString(); LoggerVerbosity? verbosity = null; if (reader.ReadBoolean()) From 1cf7eb2db8e12bccb656fac223abf4221636dd82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Mon, 11 May 2026 15:47:03 +0200 Subject: [PATCH 14/19] addressing comments --- documentation/wiki/ChangeWaves.md | 1 + .../Logging/BinaryLogger/BinaryLogger.cs | 18 +++------- src/Build/Logging/IFileOutputLogger.cs | 14 ++++++++ .../ParallelLogger/ParallelConsoleLogger.cs | 35 +++++++++---------- .../Logging/TerminalLogger/TerminalLogger.cs | 21 +++++------ src/Framework/LoggersRegisteredEventArgs.cs | 8 +++-- src/Framework/Resources/SR.resx | 4 --- src/Framework/Resources/xlf/SR.cs.xlf | 5 --- src/Framework/Resources/xlf/SR.de.xlf | 5 --- src/Framework/Resources/xlf/SR.es.xlf | 5 --- src/Framework/Resources/xlf/SR.fr.xlf | 5 --- src/Framework/Resources/xlf/SR.it.xlf | 5 --- src/Framework/Resources/xlf/SR.ja.xlf | 5 --- src/Framework/Resources/xlf/SR.ko.xlf | 5 --- src/Framework/Resources/xlf/SR.pl.xlf | 5 --- src/Framework/Resources/xlf/SR.pt-BR.xlf | 5 --- src/Framework/Resources/xlf/SR.ru.xlf | 5 --- src/Framework/Resources/xlf/SR.tr.xlf | 5 --- src/Framework/Resources/xlf/SR.zh-Hans.xlf | 5 --- src/Framework/Resources/xlf/SR.zh-Hant.xlf | 5 --- 20 files changed, 53 insertions(+), 113 deletions(-) diff --git a/documentation/wiki/ChangeWaves.md b/documentation/wiki/ChangeWaves.md index 2ca32f08433..a6fd5b23f0f 100644 --- a/documentation/wiki/ChangeWaves.md +++ b/documentation/wiki/ChangeWaves.md @@ -31,6 +31,7 @@ Change wave checks around features will be removed in the release that accompani ### 18.8 - [RAR task: across multiple input properties, resolve relative paths against the project directory (not the process current directory)](https://github.com/dotnet/msbuild/pull/13319) +- [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 - [Copy task retries on ERROR_ACCESS_DENIED on non-Windows platforms to handle transient lock conflicts (e.g. macOS CoW filesystems)](https://github.com/dotnet/msbuild/issues/13463) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 6f5be922679..ffabf948e86 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -109,6 +109,8 @@ public sealed class BinaryLogger : ILogger, IFileOutputLogger // - 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. @@ -299,19 +301,9 @@ private static bool TryParsePathParameter(string parameter, out string filePath) /// System.Collections.Generic.IReadOnlyList IFileOutputLogger.OutputFilePaths - { - get - { - if (AdditionalFilePaths == null || AdditionalFilePaths.Count == 0) - { - return new[] { FilePath }; - } - - var outputFilePaths = new List(AdditionalFilePaths.Count + 1) { FilePath }; - outputFilePaths.AddRange(AdditionalFilePaths); - return outputFilePaths; - } - } + => AdditionalFilePaths is null || AdditionalFilePaths.Count == 0 + ? [FilePath] + : [FilePath, .. AdditionalFilePaths]; /// /// Gets or sets additional output file paths. When set, the binlog will be copied to all these paths diff --git a/src/Build/Logging/IFileOutputLogger.cs b/src/Build/Logging/IFileOutputLogger.cs index 530a1506dd1..2a2c23241b5 100644 --- a/src/Build/Logging/IFileOutputLogger.cs +++ b/src/Build/Logging/IFileOutputLogger.cs @@ -3,8 +3,22 @@ namespace Microsoft.Build.Logging { + /// + /// Implemented by loggers that write the build log to one or more files on disk + /// (for example and ). + /// + /// + /// This is used solely to surface the log file paths in the build summary printed + /// by the console logger at the end of a build so that users can easily locate the + /// log files that were produced. It is not intended to represent project build + /// outputs (e.g. produced assemblies) or any other artifacts unrelated to logging. + /// internal interface IFileOutputLogger { + /// + /// Gets the absolute paths of the log files that this logger writes to. + /// Reported in the end-of-build summary emitted by the console logger. + /// System.Collections.Generic.IReadOnlyList OutputFilePaths { get; } } } diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index 331a5350cd3..f507da0c939 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -8,7 +8,6 @@ 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; @@ -275,23 +274,6 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs ShowPerfSummary(); } - // Show paths to the files created by enabled loggers. - if (ShowSummary == true && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) - { - WriteNewLine(); - - foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) - { - string displayPaths = string.Join( - CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", - logger.OutputFilePaths.Select(outputPath => setColor != DontSetColor - ? $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}" - : outputPath)); - - WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); - } - } - // Write the "Build Finished" event if verbosity is normal, detailed or diagnostic or the user // specified to show the summary. if (ShowSummary == true) @@ -307,6 +289,23 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs resetColor(); } + // Show paths to the files created by enabled loggers. + if (ShowSummary == true + && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_8) + && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) + { + WriteNewLine(); + + foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) + { + string displayPaths = string.Join( + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", + logger.OutputFilePaths); + + WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); + } + } + // The decision whether or not to show a summary at this verbosity // was made during initialization. We just do what we're told. if (ShowSummary == true) diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 2c775b85e68..764a18760cf 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -632,19 +632,20 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) if (_showSummary == true) { RenderBuildSummary(); - } - - if (_registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) - { - Terminal.WriteLine(string.Empty); - foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_8) + && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) { - string displayPaths = string.Join( - CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", - logger.OutputFilePaths.Select(outputPath => $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}")); + Terminal.WriteLine(string.Empty); - Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); + foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) + { + string displayPaths = string.Join( + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", + logger.OutputFilePaths.Select(outputPath => $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}")); + + Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); + } } } diff --git a/src/Framework/LoggersRegisteredEventArgs.cs b/src/Framework/LoggersRegisteredEventArgs.cs index e36a65e1d2f..9f346b550ec 100644 --- a/src/Framework/LoggersRegisteredEventArgs.cs +++ b/src/Framework/LoggersRegisteredEventArgs.cs @@ -75,7 +75,8 @@ public LoggersRegisteredEventArgs(IReadOnlyList loggers) /// /// Formats a summary message listing loggers with output paths. - /// This serves as a fallback message for loggers that do not handle this event. + /// This serves as a fallback message for consumers that do not handle this event; + /// the console/terminal loggers format their own localized output from . /// private static string? FormatMessage(IReadOnlyList loggers) { @@ -85,8 +86,9 @@ public LoggersRegisteredEventArgs(IReadOnlyList loggers) return string.Empty; } - return string.Join("; ", withPaths.Select(l => FormatResourceStringIgnoreCodeAndKeyword( - "LogFileOutputPath", + return string.Join("; ", withPaths.Select(l => string.Format( + CultureInfo.CurrentCulture, + "{0} wrote to: {1}", l.LoggerName, string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", l.OutputFilePaths)))); } diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index cf6aed4da5c..25f5240b533 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -153,10 +153,6 @@ Path must be rooted. - - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index 5bf75709f15..c5688aa6713 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -107,11 +107,6 @@ Cesta musí začínat kořenem. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Cesta: {0} překračuje maximální limit pro cestu k OS. Plně kvalifikovaný název souboru musí být kratší než {1} znaků. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 6a66a264318..073fa04dfd9 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -107,11 +107,6 @@ Der Pfad muss einen Stamm besitzen. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Der Pfad "{0}" überschreitet das maximale Pfadlimit des Betriebssystems. Der vollqualifizierte Dateiname muss weniger als {1} Zeichen umfassen. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index 742e6bd4185..3654cc0334f 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -107,11 +107,6 @@ Debe ser una ruta de acceso raíz. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. La ruta de acceso {0} supera el límite máximo para la ruta de acceso del sistema operativo. El nombre de archivo completo debe ser inferior a {1} caracteres. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index f966a0da348..6fc3dec1ba4 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -107,11 +107,6 @@ Le chemin doit être associé à une racine. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Le chemin {0} dépasse la limite maximale de chemin du système d'exploitation. Le nom du fichier qualifié complet doit contenir moins de {1} caractères. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 0d3a03f75ee..3be0c4ca9cf 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -107,11 +107,6 @@ Il percorso deve contenere una radice. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Il percorso {0} supera il limite massimo dei percorsi del sistema operativo. Il nome completo del file deve essere composto da meno di {1}. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 2b6d69569ea..63e20c62eea 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -107,11 +107,6 @@ パスはルート指定パスである必要があります。 - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index 4e15c4b401b..c994f84c82b 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -107,11 +107,6 @@ 루트 경로로 지정해야 합니다. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index d0df914c3e5..254ef6a1ec6 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -107,11 +107,6 @@ Ścieżka musi zaczynać się od katalogu głównego. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Ścieżka: {0} przekracza limit maksymalnej długości ścieżki w systemie operacyjnym. W pełni kwalifikowana nazwa pliku musi się składać z mniej niż {1} znaków. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index c2f667441fe..07befc78304 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -107,11 +107,6 @@ Caminho deve ter raiz. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Caminho: {0} excede o limite máximo do caminho do SO. O nome do arquivo totalmente qualificado deve ter menos de {1} caracteres. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index ec46636d56a..4360a9cf6b1 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -107,11 +107,6 @@ Путь должен иметь корень. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index d7233b6017d..40cf723c946 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -107,11 +107,6 @@ Yol kökü belirtilmelidir. - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Yol: {0}, işletim sisteminin en yüksek yol sınırını aşıyor. Tam dosya adı en fazla {1} karakter olmalıdır. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 84813033213..9afbf63b44e 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -107,11 +107,6 @@ 路径必须是根路径。 - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index 1bbb0e4ceb0..88763cdf9f3 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -107,11 +107,6 @@ 路徑必須為根路徑。 - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 From 852de3d6aa7ee44d1cfd21de8af3de029b66e4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Tue, 12 May 2026 08:15:13 +0200 Subject: [PATCH 15/19] collapsed registration, reverted xlf,resx --- .../Logging/LoggingServiceLogMethods.cs | 42 ++++++++----------- src/Framework/Resources/SR.resx | 2 +- src/Framework/Resources/xlf/SR.cs.xlf | 2 +- src/Framework/Resources/xlf/SR.de.xlf | 2 +- src/Framework/Resources/xlf/SR.es.xlf | 2 +- src/Framework/Resources/xlf/SR.fr.xlf | 2 +- src/Framework/Resources/xlf/SR.it.xlf | 2 +- src/Framework/Resources/xlf/SR.ja.xlf | 2 +- src/Framework/Resources/xlf/SR.ko.xlf | 2 +- src/Framework/Resources/xlf/SR.pl.xlf | 2 +- src/Framework/Resources/xlf/SR.pt-BR.xlf | 2 +- src/Framework/Resources/xlf/SR.ru.xlf | 2 +- src/Framework/Resources/xlf/SR.tr.xlf | 2 +- src/Framework/Resources/xlf/SR.zh-Hans.xlf | 2 +- src/Framework/Resources/xlf/SR.zh-Hant.xlf | 2 +- 15 files changed, 31 insertions(+), 39 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 321933a19e9..dd1cb0d9a25 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -365,8 +365,7 @@ public void LogBuildStarted() // Register Loggers and print out all the enabled loggers. if (!OnlyLogCriticalEvents) { - LogEnabledLoggers(); - RegisterLoggers(); + LogAndRegisterLoggers(); } } @@ -400,38 +399,22 @@ public void LogBuildFinished(bool success) } /// - /// Logs the names of enabled logs (except for Forwarding logs). + /// In a single pass over the registered loggers, emits a message listing the enabled logger + /// type names and a describing each logger (including + /// any output file paths for implementations). /// - private void LogEnabledLoggers() + private void LogAndRegisterLoggers() { List listOfLoggers = new(); - foreach (ILogger logger in Loggers) - { - ILogger actualLogger = UnwrapLogger(logger); - Type loggerType = actualLogger.GetType(); - listOfLoggers.Add(loggerType.FullName ?? loggerType.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); - } - } - - /// - /// Logs the file paths of enabled logs. - /// - private void RegisterLoggers() - { var loggerDescriptions = new List(); + foreach (ILogger logger in Loggers) { ILogger actualLogger = UnwrapLogger(logger); Type loggerType = actualLogger.GetType(); + listOfLoggers.Add(loggerType.FullName ?? loggerType.Name); + var outputFilePaths = new List(); if (actualLogger is IFileOutputLogger fileLogger) { @@ -452,6 +435,15 @@ private void RegisterLoggers() 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); diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index 25f5240b533..ebcc88a74d5 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -199,4 +199,4 @@ '{0}' contains no elements. - + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index c5688aa6713..32d075daa4f 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 073fa04dfd9..6b53f58a3fa 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index 3654cc0334f..daf26188dba 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index 6fc3dec1ba4..90d5bf48fa1 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 3be0c4ca9cf..9f24bafe1ca 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 63e20c62eea..4490b72a34e 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index c994f84c82b..d4df3b1c552 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index 254ef6a1ec6..8a0af80daaf 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 07befc78304..0cf2bf2f94b 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 4360a9cf6b1..19c782d0903 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index 40cf723c946..fd1ff3fa370 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 9afbf63b44e..5dcdb7945bc 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index 88763cdf9f3..f70b3542096 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -1,4 +1,4 @@ - + From e17738e2314f5958bfe0c11b855ec9b48b567ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Wed, 20 May 2026 11:13:34 +0200 Subject: [PATCH 16/19] addressed comments --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 3 +- .../BackEnd/NodePackets_Tests.cs | 10 ++ .../BuildEventArgsSerialization_Tests.cs | 27 ++++++ src/Build.UnitTests/ConsoleLogger_Tests.cs | 78 ++++++++++++++++ .../Logging/LoggingServiceLogMethods.cs | 3 +- .../Logging/BinaryLogger/BinaryLogger.cs | 2 +- .../BinaryLogger/BuildEventArgsReader.cs | 3 +- .../BinaryLogger/BuildEventArgsWriter.cs | 1 - .../ParallelLogger/ParallelConsoleLogger.cs | 34 +++---- src/Build/Resources/Strings.resx | 91 +++++++++---------- src/Build/Resources/xlf/Strings.cs.xlf | 4 +- src/Build/Resources/xlf/Strings.de.xlf | 4 +- src/Build/Resources/xlf/Strings.es.xlf | 4 +- src/Build/Resources/xlf/Strings.fr.xlf | 4 +- src/Build/Resources/xlf/Strings.it.xlf | 4 +- src/Build/Resources/xlf/Strings.ja.xlf | 4 +- src/Build/Resources/xlf/Strings.ko.xlf | 4 +- src/Build/Resources/xlf/Strings.pl.xlf | 4 +- src/Build/Resources/xlf/Strings.pt-BR.xlf | 4 +- src/Build/Resources/xlf/Strings.ru.xlf | 4 +- src/Build/Resources/xlf/Strings.tr.xlf | 4 +- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 4 +- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 4 +- src/Framework/LoggersRegisteredEventArgs.cs | 14 +-- src/Framework/Resources/SR.resx | 6 +- src/Framework/Resources/xlf/SR.cs.xlf | 5 + src/Framework/Resources/xlf/SR.de.xlf | 5 + src/Framework/Resources/xlf/SR.es.xlf | 5 + src/Framework/Resources/xlf/SR.fr.xlf | 5 + src/Framework/Resources/xlf/SR.it.xlf | 5 + src/Framework/Resources/xlf/SR.ja.xlf | 5 + src/Framework/Resources/xlf/SR.ko.xlf | 5 + src/Framework/Resources/xlf/SR.pl.xlf | 5 + src/Framework/Resources/xlf/SR.pt-BR.xlf | 5 + src/Framework/Resources/xlf/SR.ru.xlf | 5 + src/Framework/Resources/xlf/SR.tr.xlf | 5 + src/Framework/Resources/xlf/SR.zh-Hans.xlf | 5 + src/Framework/Resources/xlf/SR.zh-Hant.xlf | 5 + 38 files changed, 277 insertions(+), 112 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 75a0ec1f9ad..32f5e89dcbf 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -1048,7 +1048,7 @@ public void LogBuildFinished() Assert.True(((BuildFinishedEventArgs)service.ProcessedBuildEvent).IsEquivalent(buildEvent)); } [Fact] - public void LogBuildStartedLoggerNames() + public void LogBuildStartedLogsLoggerNames() { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); ConsoleLogger consoleLogger = new ConsoleLogger(); @@ -1087,7 +1087,6 @@ public void LogFilePathsPresentInFileLog() 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 diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs index 79594cde9c1..a2ed7cbebff 100644 --- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs @@ -327,6 +327,16 @@ public void TestTranslation() { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }), new RegisteredLoggerInfo("BinaryLogger"), + new RegisteredLoggerInfo( + "ConsoleLogger", + outputFilePaths: null, + verbosity: LoggerVerbosity.Detailed, + parameters: "ShowTimestamp;ShowEventId"), + new RegisteredLoggerInfo( + "MultiFileLogger", + outputFilePaths: new[] { @"C:\logs\a.log", @"C:\logs\b.log" }, + verbosity: LoggerVerbosity.Diagnostic, + parameters: "LogFile=a.log;LogFile=b.log"), }), }; foreach (BuildEventArgs arg in testArgs) diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs index 12d33ccc514..7caba415370 100644 --- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs +++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs @@ -109,6 +109,33 @@ public void RoundtripBuildCanceledEventArgs() e => e.Timestamp.ToString()); } + [Fact] + public void RoundtripLoggersRegisteredEventArgs() + { + var args = new LoggersRegisteredEventArgs(new List + { + new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }), + new RegisteredLoggerInfo("BinaryLogger"), + new RegisteredLoggerInfo( + "ConsoleLogger", + outputFilePaths: null, + verbosity: LoggerVerbosity.Detailed, + parameters: "ShowTimestamp;ShowEventId"), + new RegisteredLoggerInfo( + "MultiFileLogger", + outputFilePaths: new[] { @"C:\logs\a.log", @"C:\logs\b.log" }, + verbosity: LoggerVerbosity.Diagnostic, + parameters: "LogFile=a.log;LogFile=b.log"), + }); + + Roundtrip(args, + e => e.Loggers.Count.ToString(CultureInfo.InvariantCulture), + e => string.Join("|", e.Loggers.Select(l => l.LoggerName)), + e => string.Join("|", e.Loggers.Select(l => l.Parameters ?? "")), + e => string.Join("|", e.Loggers.Select(l => l.Verbosity?.ToString() ?? "")), + e => string.Join("|", e.Loggers.Select(l => string.Join(",", l.OutputFilePaths)))); + } + [Fact] public void RoundtripBuildSubmissionStartedEventArgs() { diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs index 1f04286b23b..50ad8ae640e 100644 --- a/src/Build.UnitTests/ConsoleLogger_Tests.cs +++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs @@ -1961,6 +1961,84 @@ public void TestPrintTargetNamePerMessage() actualLog.ShouldContain("t:"); } + /// + /// + /// When the loggers print their output paths feature wave is enabled (default), + /// the ParallelConsoleLogger end-of-build summary should list each registered logger + /// that wrote to a file along with its output path(s). + /// + [Fact] + public void LogFileOutputPaths_PrintedInSummary_WhenWaveEnabled() + { + try + { + ChangeWaves.ResetStateForTests(); + + SimulatedConsole sc = new SimulatedConsole(); + ParallelConsoleLogger cl = new ParallelConsoleLogger(LoggerVerbosity.Normal, sc.Write, null, null); + EventSourceSink es = new EventSourceSink(); + cl.Initialize(es); + + BuildStartedEventArgs bsea = new BuildStartedEventArgs("bs", null); + cl.BuildStartedHandler(null, bsea); + + LoggersRegisteredEventArgs lrea = new LoggersRegisteredEventArgs(new List + { + new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }), + }); + cl.StatusEventHandler(null, lrea); + + BuildFinishedEventArgs bfea = new BuildFinishedEventArgs("bf", null, true); + cl.BuildFinishedHandler(null, bfea); + + sc.ToString().ShouldContain("FileLogger"); + sc.ToString().ShouldContain(@"C:\logs\build.log"); + } + finally + { + ChangeWaves.ResetStateForTests(); + } + } + + /// + /// When the loggers print their output paths feature wave is disabled + /// via MSBUILDDISABLEFEATURESFROMVERSION, the ParallelConsoleLogger summary + /// must preserve the legacy behavior and NOT print logger output paths. + /// + [Fact] + public void LogFileOutputPaths_NotPrintedInSummary_WhenWaveDisabled() + { + using TestEnvironment env = TestEnvironment.Create(); + try + { + ChangeWaves.ResetStateForTests(); + env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_8.ToString()); + + SimulatedConsole sc = new SimulatedConsole(); + ParallelConsoleLogger cl = new ParallelConsoleLogger(LoggerVerbosity.Normal, sc.Write, null, null); + EventSourceSink es = new EventSourceSink(); + cl.Initialize(es); + + BuildStartedEventArgs bsea = new BuildStartedEventArgs("bs", null); + cl.BuildStartedHandler(null, bsea); + + LoggersRegisteredEventArgs lrea = new LoggersRegisteredEventArgs(new List + { + new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }), + }); + cl.StatusEventHandler(null, lrea); + + BuildFinishedEventArgs bfea = new BuildFinishedEventArgs("bf", null, true); + cl.BuildFinishedHandler(null, bfea); + + sc.ToString().ShouldNotContain(@"C:\logs\build.log"); + } + finally + { + ChangeWaves.ResetStateForTests(); + } + } + /// /// Check to see what kind of device we are outputting the log to, is it a character device, a file, or something else /// this can be used by loggers to modify their outputs based on the device they are writing to diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index dd1cb0d9a25..6c19cbda141 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -399,7 +399,7 @@ public void LogBuildFinished(bool success) } /// - /// In a single pass over the registered loggers, emits a message listing the enabled logger + /// Emits a message listing the enabled logger /// type names and a describing each logger (including /// any output file paths for implementations). /// @@ -431,7 +431,6 @@ private void LogAndRegisterLoggers() loggerName: loggerType.Name, outputFilePaths: outputFilePaths.Count > 0 ? outputFilePaths : null, verbosity: actualLogger.Verbosity, - loggerTypeFullName: loggerType.FullName, parameters: actualLogger.Parameters)); } diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index ffabf948e86..77d84cd94da 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -300,7 +300,7 @@ private static bool TryParsePathParameter(string parameter, out string filePath) internal string FilePath { get; private set; } /// - System.Collections.Generic.IReadOnlyList IFileOutputLogger.OutputFilePaths + IReadOnlyList IFileOutputLogger.OutputFilePaths => AdditionalFilePaths is null || AdditionalFilePaths.Count == 0 ? [FilePath] : [FilePath, .. AdditionalFilePaths]; diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index ece6c23e610..f07da38e10a 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -1296,7 +1296,6 @@ private BuildEventArgs ReadLoggersRegisteredEventArgs() for (int i = 0; i < count; i++) { string loggerName = ReadDeduplicatedString()!; - string loggerTypeFullName = ReadDeduplicatedString()!; string parameters = ReadDeduplicatedString()!; LoggerVerbosity? verbosity = null; @@ -1312,7 +1311,7 @@ private BuildEventArgs ReadLoggersRegisteredEventArgs() outputFilePaths[j] = ReadDeduplicatedString()!; } - loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, loggerTypeFullName, parameters)); + loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, parameters)); } var e = new LoggersRegisteredEventArgs(loggers); diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs index 907fe74e711..6bd274e532a 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs @@ -324,7 +324,6 @@ private BinaryLogRecordKind Write(LoggersRegisteredEventArgs e) foreach (var logger in e.Loggers) { WriteDeduplicatedString(logger.LoggerName); - WriteDeduplicatedString(logger.LoggerTypeFullName); WriteDeduplicatedString(logger.Parameters); Write(logger.Verbosity.HasValue); if (logger.Verbosity.HasValue) diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index f507da0c939..b17296ce7d8 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -289,23 +289,6 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs resetColor(); } - // Show paths to the files created by enabled loggers. - if (ShowSummary == true - && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_8) - && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) - { - WriteNewLine(); - - foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) - { - string displayPaths = string.Join( - CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", - logger.OutputFilePaths); - - WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); - } - } - // The decision whether or not to show a summary at this verbosity // was made during initialization. We just do what we're told. if (ShowSummary == true) @@ -340,6 +323,23 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs resetColor(); } + // Show paths to the files created by enabled loggers. + if (ShowSummary == true + && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_8) + && _registeredLoggers.Any(logger => logger.OutputFilePaths.Count > 0)) + { + WriteNewLine(); + + foreach (var logger in _registeredLoggers.Where(logger => logger.OutputFilePaths.Count > 0)) + { + string displayPaths = string.Join( + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", + logger.OutputFilePaths); + + WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); + } + } + // Show build time if verbosity is normal, detailed or diagnostic or the user specified to // show the summary. if (ShowSummary == true) diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 42f195f0ce3..a58b5a619f0 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -145,7 +145,7 @@ The operation cannot be completed because EndBuild has already been called but existing submissions have not yet completed. - + Property '{0}' with value '{1}' expanded from the environment. @@ -175,7 +175,7 @@ {0} is the logger name. {1} is a comma-separated list of full file paths. - Enabled logs: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). @@ -487,7 +487,7 @@ likely because of a programming error in the logger). When a logger dies, we cannot proceed with the build, and we throw a special exception to abort the build. - + MSB3094: "{2}" refers to {0} item(s), and "{3}" refers to {1} item(s). They must have the same number of items. {StrBegin="MSB3094: "} @@ -612,7 +612,7 @@ LOCALIZATION: "{0}" is the expression that was bad. "{1}" is a message from an FX exception that describes why the expression is bad. - + Found multiple overloads for method "{0}" with {1} parameter(s). That is currently not supported. @@ -1183,7 +1183,7 @@ LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. Also, Microsoft.Build.Framework should not be localized - + MSB4181: The "{0}" task returned false but did not log an error. {StrBegin="MSB4181: "} @@ -1317,7 +1317,7 @@ MSB4067: The element <{0}> beneath element <{1}> is unrecognized. {StrBegin="MSB4067: "} - + MSB4067: The element <{0}> beneath element <{1}> is unrecognized. If you intended this to be a property, enclose it within a <PropertyGroup> element. {StrBegin="MSB4067: "} @@ -1786,7 +1786,7 @@ Utilization: {0} Average Utilization: {1:###.0} MSB4231: ProjectRootElement can't reload if it contains unsaved changes. {StrBegin="MSB4231: "} - + The parameters have been truncated beyond this point. To view all parameters, clear the MSBUILDTRUNCATETASKINPUTLOGGING environment variable. @@ -2027,9 +2027,9 @@ Utilization: {0} Average Utilization: {1:###.0} - {0} -> Cache Hit + {0} -> Cache Hit - {StrBegin="{0} -> "}LOCALIZATION: This string is used to indicate progress and matches the format for a log message from Microsoft.Common.CurrentVersion.targets. {0} is a project name. + {StrBegin="{0} -> "}LOCALIZATION: This string is used to indicate progress and matches the format for a log message from Microsoft.Common.CurrentVersion.targets. {0} is a project name. @@ -2098,7 +2098,7 @@ Utilization: {0} Average Utilization: {1:###.0} Imported files archive exceeded 2GB limit and it's not embedded. - Forward compatible reading is not supported for file format version {0} (needs >= 18). + Forward compatible reading is not supported for file format version {0} (needs >= 18). LOCALIZATION: {0} is an integer number denoting version. @@ -2221,19 +2221,19 @@ Utilization: {0} Average Utilization: {1:###.0} It is recommended to specify explicit 'Culture' metadata, or 'WithCulture=false' metadata with 'EmbeddedResource' item in order to avoid wrong or nondeterministic culture estimation. - Terms in quotes are not to be translated. + Terms in quotes are not to be translated. Project {0} specifies 'EmbeddedResource' item '{1}', that has possibly a culture denoting extension ('{2}'), but explicit 'Culture' nor 'WithCulture=false' metadata are not specified. - Terms in quotes are not to be translated. + Terms in quotes are not to be translated. Avoid specifying 'Always' for 'CopyToOutputDirectory' as this can lead to unnecessary copy operations during build. Use 'PreserveNewest' or 'IfDifferent' metadata value, or set the 'SkipUnchangedFilesOnCopyAlways' property to true to employ more effective copying. - Terms in quotes are not to be translated. + Terms in quotes are not to be translated. Project {0} specifies '{1}' item '{2}', that has 'CopyToOutputDirectory' set as 'Always'. Change the metadata or use 'CopyToOutputDirectory' property. - Terms in quotes are not to be translated. + Terms in quotes are not to be translated. 'TargetFramework' (singular) and 'TargetFrameworks' (plural) properties should not be specified in the scripts at the same time. @@ -2494,11 +2494,4 @@ Utilization: {0} Average Utilization: {1:###.0} MSB4280: The environment variable DOTNET_HOST_PATH is set to a directory ("{0}") instead of a path to the dotnet executable. This can lead to build errors in tasks that use this variable such as the Roslyn compiler. Either unset the variable or update it to point to the dotnet executable directly (e.g. "C:\Program Files\dotnet\dotnet.exe"). {StrBegin="MSB4280: "}UE: This warning is shown when the DOTNET_HOST_PATH environment variable is set to a directory path rather than a file path. The variable should point to the dotnet executable, not a directory containing it. - - + \ No newline at end of file diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index f115339bf22..e2e8093deb3 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index b6dce1680b5..a173db2c9c1 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 9449996a6cb..3d69ef2ad65 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index f8b7eda8bc3..ee51401aec8 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 78631d39aa8..d4444ca7c04 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 4bab38ef168..b1aa5030e4e 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 2dc7c5858c0..a7b47b5296c 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index ab650580853..b737018c256 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 2408397cc91..ffa0329876d 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 0e35fa7eb5f..cd0b40f6a55 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 93b3df84b56..718ec14dce7 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index de2e26dadd5..6bada99465c 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 0a92928fab6..f23b2c59c84 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -591,8 +591,8 @@ - Enabled logs: {0} - Enabled logs: {0} + Enabled loggers: {0} + Enabled loggers: {0} {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). diff --git a/src/Framework/LoggersRegisteredEventArgs.cs b/src/Framework/LoggersRegisteredEventArgs.cs index 9f346b550ec..73618e19b3b 100644 --- a/src/Framework/LoggersRegisteredEventArgs.cs +++ b/src/Framework/LoggersRegisteredEventArgs.cs @@ -18,12 +18,11 @@ public sealed class RegisteredLoggerInfo /// /// Initialize a new instance of the RegisteredLoggerInfo class. /// - public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFilePaths = null, LoggerVerbosity? verbosity = null, string? loggerTypeFullName = null, string? parameters = null) + public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFilePaths = null, LoggerVerbosity? verbosity = null, string? parameters = null) { LoggerName = loggerName; OutputFilePaths = outputFilePaths ?? Array.Empty(); Verbosity = verbosity; - LoggerTypeFullName = loggerTypeFullName; Parameters = parameters; } @@ -32,11 +31,6 @@ public RegisteredLoggerInfo(string loggerName, IReadOnlyList? outputFile /// public string LoggerName { get; } - /// - /// The full type name of the logger. - /// - public string? LoggerTypeFullName { get; } - /// /// The logger parameters. /// @@ -88,7 +82,7 @@ public LoggersRegisteredEventArgs(IReadOnlyList loggers) return string.Join("; ", withPaths.Select(l => string.Format( CultureInfo.CurrentCulture, - "{0} wrote to: {1}", + SR.LogFileOutputPath, l.LoggerName, string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", l.OutputFilePaths)))); } @@ -106,7 +100,6 @@ internal override void WriteToStream(BinaryWriter writer) foreach (var logger in Loggers) { writer.Write(logger.LoggerName); - writer.WriteOptionalString(logger.LoggerTypeFullName); writer.WriteOptionalString(logger.Parameters); writer.Write(logger.Verbosity.HasValue); if (logger.Verbosity.HasValue) @@ -131,7 +124,6 @@ internal override void CreateFromStream(BinaryReader reader, int version) for (int i = 0; i < count; i++) { string loggerName = reader.ReadString(); - string? loggerTypeFullName = reader.ReadOptionalString(); string? parameters = reader.ReadOptionalString(); LoggerVerbosity? verbosity = null; @@ -147,7 +139,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) outputFilePaths[j] = reader.ReadString(); } - loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, loggerTypeFullName, parameters)); + loggers.Add(new RegisteredLoggerInfo(loggerName, outputFilePaths, verbosity, parameters)); } Loggers = loggers; diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index ebcc88a74d5..7e14683f004 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -162,6 +162,10 @@ The parameter '{0}' can only be a file name and cannot include a directory. + + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. @@ -199,4 +203,4 @@ '{0}' contains no elements. - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index 32d075daa4f..cadb27e7a52 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -102,6 +102,11 @@ Parametr {0} může být jenom název souboru a nemůže obsahovat adresář. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Cesta musí začínat kořenem. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 6b53f58a3fa..26a7add386e 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -102,6 +102,11 @@ Der Parameter '{0}' kann nur ein Dateiname sein und darf kein Verzeichnis enthalten. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Der Pfad muss einen Stamm besitzen. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index daf26188dba..615052f8bbe 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -102,6 +102,11 @@ El parámetro "{0}" solo puede ser el nombre de un archivo y no puede incluir un directorio. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Debe ser una ruta de acceso raíz. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index 90d5bf48fa1..d396abfc874 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -102,6 +102,11 @@ Le paramètre "{0}" peut uniquement être un nom de fichier et ne peut pas inclure de répertoire. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Le chemin doit être associé à une racine. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 9f24bafe1ca..878fcabdc29 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -102,6 +102,11 @@ Il parametro '{0}' può solo essere un nome file e non può includere una directory. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Il percorso deve contenere una radice. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 4490b72a34e..2c22f2781e7 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -102,6 +102,11 @@ パラメーター '{0}' に使用できるのはファイル名のみで、ディレクトリを含めることはできません。 + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. パスはルート指定パスである必要があります。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index d4df3b1c552..e6f29634735 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -102,6 +102,11 @@ '{0}' 매개 변수는 파일 이름일 수만 있으며 디렉터리를 포함할 수 없습니다. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. 루트 경로로 지정해야 합니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index 8a0af80daaf..113c8ae4384 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -102,6 +102,11 @@ Parametr „{0}” może zawierać tylko nazwę pliku i nie może zawierać katalogu. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Ścieżka musi zaczynać się od katalogu głównego. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 0cf2bf2f94b..337d73c30dc 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -102,6 +102,11 @@ O parâmetro '{0}' pode ser somente um nome de arquivo e não pode incluir um diretório. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Caminho deve ter raiz. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 19c782d0903..6647a888b25 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -102,6 +102,11 @@ Параметр "{0}" может быть только именем файла и не может включать в себя каталог. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Путь должен иметь корень. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index fd1ff3fa370..4d6fb9dbe6e 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -102,6 +102,11 @@ '{0}' yalnızca bir dosya adı olabilir ve dizin içeremez. + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. Yol kökü belirtilmelidir. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 5dcdb7945bc..dd31f87af9f 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -102,6 +102,11 @@ 参数“{0}”只能是文件名,不能包含目录。 + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. 路径必须是根路径。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index f70b3542096..8dbe9a2e10d 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -102,6 +102,11 @@ 參數 '{0}' 只可以是檔案名稱,不得包含目錄。 + + {0} wrote to: {1} + {0} wrote to: {1} + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + Path must be rooted. 路徑必須為根路徑。 From 4af09d5b670cef586b613dacdcba0d5125037e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Wed, 20 May 2026 12:44:53 +0200 Subject: [PATCH 17/19] fixed unit test --- src/Utilities.UnitTests/MuxLogger_Tests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Utilities.UnitTests/MuxLogger_Tests.cs b/src/Utilities.UnitTests/MuxLogger_Tests.cs index 1e75d9fec93..5109f024cbb 100644 --- a/src/Utilities.UnitTests/MuxLogger_Tests.cs +++ b/src/Utilities.UnitTests/MuxLogger_Tests.cs @@ -127,13 +127,13 @@ public void BuildWithMuxLoggerEquivalentToNormalLogger() // This test was changed to not compare new lines because of https://github.com/dotnet/msbuild/issues/10493 // It will need to be changed once we fix the root cause of the issue - // Strip "Enabled logs: ..." because the direct logger is registered before BeginBuild and receives all + // Strip "Enabled loggers: ..." because the direct logger is registered before BeginBuild and receives all // BuildStarted-time events. The MuxLogger itself is also registered before BeginBuild, but its per-submission // sub-logger is added only after PendBuildRequest returns a submission ID. By then the global BuildStarted // event has already been processed, so the MuxLogger replays BuildStarted to the sub-logger but cannot replay // follow-up messages emitted during BuildStarted processing. - string StripEnabledLogs(string log) => Regex.Replace(log.Replace(Environment.NewLine, ""), @"Enabled logs: .+?(?=Project |$)", ""); - StripEnabledLogs(mockLogger.FullLog).ShouldBe(StripEnabledLogs(mockLogger2.FullLog)); + string StripEnabledLoggers(string log) => Regex.Replace(log.Replace(Environment.NewLine, ""), @"Enabled loggers: .+?(?=Project |$)", ""); + StripEnabledLoggers(mockLogger.FullLog).ShouldBe(StripEnabledLoggers(mockLogger2.FullLog)); } /// From b406b491e35c45f599e81db54fae818192d1799a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Fri, 22 May 2026 08:37:34 +0200 Subject: [PATCH 18/19] addressing last round of comments --- .../Components/Logging/LoggingServiceLogMethods.cs | 7 +++++-- src/Build/Logging/IFileOutputLogger.cs | 3 ++- src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs | 2 +- src/Build/Logging/TerminalLogger/TerminalLogger.cs | 2 +- src/Build/Resources/Strings.resx | 8 ++------ src/Build/Resources/xlf/Strings.cs.xlf | 7 +------ src/Build/Resources/xlf/Strings.de.xlf | 7 +------ src/Build/Resources/xlf/Strings.es.xlf | 7 +------ src/Build/Resources/xlf/Strings.fr.xlf | 7 +------ src/Build/Resources/xlf/Strings.it.xlf | 7 +------ src/Build/Resources/xlf/Strings.ja.xlf | 7 +------ src/Build/Resources/xlf/Strings.ko.xlf | 7 +------ src/Build/Resources/xlf/Strings.pl.xlf | 7 +------ src/Build/Resources/xlf/Strings.pt-BR.xlf | 7 +------ src/Build/Resources/xlf/Strings.ru.xlf | 7 +------ src/Build/Resources/xlf/Strings.tr.xlf | 7 +------ src/Build/Resources/xlf/Strings.zh-Hans.xlf | 7 +------ src/Build/Resources/xlf/Strings.zh-Hant.xlf | 7 +------ src/Framework/LoggersRegisteredEventArgs.cs | 2 +- src/Framework/Resources/SR.resx | 2 +- src/Framework/Resources/xlf/SR.cs.xlf | 2 +- src/Framework/Resources/xlf/SR.de.xlf | 2 +- src/Framework/Resources/xlf/SR.es.xlf | 2 +- src/Framework/Resources/xlf/SR.fr.xlf | 2 +- src/Framework/Resources/xlf/SR.it.xlf | 2 +- src/Framework/Resources/xlf/SR.ja.xlf | 2 +- src/Framework/Resources/xlf/SR.ko.xlf | 2 +- src/Framework/Resources/xlf/SR.pl.xlf | 2 +- src/Framework/Resources/xlf/SR.pt-BR.xlf | 2 +- src/Framework/Resources/xlf/SR.ru.xlf | 2 +- src/Framework/Resources/xlf/SR.tr.xlf | 2 +- src/Framework/Resources/xlf/SR.zh-Hans.xlf | 2 +- src/Framework/Resources/xlf/SR.zh-Hant.xlf | 2 +- 33 files changed, 39 insertions(+), 104 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 6c19cbda141..983a62d9293 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -363,7 +363,10 @@ public void LogBuildStarted() WaitForLoggingToProcessEvents(); // Register Loggers and print out all the enabled loggers. - if (!OnlyLogCriticalEvents) + // Gated behind ChangeWaves.Wave18_8 so that disabling the wave fully suppresses + // the new "Enabled loggers" message and the new LoggersRegisteredEventArgs, + // not just their rendering in the console/terminal loggers. + if (!OnlyLogCriticalEvents && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_8)) { LogAndRegisterLoggers(); } @@ -413,7 +416,7 @@ private void LogAndRegisterLoggers() ILogger actualLogger = UnwrapLogger(logger); Type loggerType = actualLogger.GetType(); - listOfLoggers.Add(loggerType.FullName ?? loggerType.Name); + listOfLoggers.Add(loggerType.Name); var outputFilePaths = new List(); if (actualLogger is IFileOutputLogger fileLogger) diff --git a/src/Build/Logging/IFileOutputLogger.cs b/src/Build/Logging/IFileOutputLogger.cs index 2a2c23241b5..95a7e52f5e0 100644 --- a/src/Build/Logging/IFileOutputLogger.cs +++ b/src/Build/Logging/IFileOutputLogger.cs @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; namespace Microsoft.Build.Logging { @@ -19,6 +20,6 @@ internal interface IFileOutputLogger /// Gets the absolute paths of the log files that this logger writes to. /// Reported in the end-of-build summary emitted by the console logger. /// - System.Collections.Generic.IReadOnlyList OutputFilePaths { get; } + IReadOnlyList OutputFilePaths { get; } } } diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index b17296ce7d8..f180de40fc2 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -336,7 +336,7 @@ public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", logger.OutputFilePaths); - WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); + WriteLinePretty(string.Format(CultureInfo.CurrentCulture, Microsoft.Build.Framework.Resources.SR.LogFileOutputPath, logger.LoggerName, displayPaths)); } } diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 764a18760cf..c4bf2af4aa4 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -644,7 +644,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", logger.OutputFilePaths.Select(outputPath => $"{AnsiCodes.LinkPrefix}{new Uri(outputPath).AbsoluteUri}{AnsiCodes.LinkInfix}{outputPath}{AnsiCodes.LinkSuffix}")); - Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("LogFileOutputPath", logger.LoggerName, displayPaths)); + Terminal.WriteLine(string.Format(CultureInfo.CurrentCulture, Microsoft.Build.Framework.Resources.SR.LogFileOutputPath, logger.LoggerName, displayPaths)); } } } diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index a58b5a619f0..4dfca94e333 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -170,13 +170,9 @@ Build succeeded. - - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. - Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Build started {0}. @@ -2494,4 +2490,4 @@ Utilization: {0} Average Utilization: {1:###.0} MSB4280: The environment variable DOTNET_HOST_PATH is set to a directory ("{0}") instead of a path to the dotnet executable. This can lead to build errors in tasks that use this variable such as the Roslyn compiler. Either unset the variable or update it to point to the dotnet executable directly (e.g. "C:\Program Files\dotnet\dotnet.exe"). {StrBegin="MSB4280: "}UE: This warning is shown when the DOTNET_HOST_PATH environment variable is set to a directory path rather than a file path. The variable should point to the dotnet executable, not a directory containing it. - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index e2e8093deb3..156fdfdf4b7 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index a173db2c9c1..3f8b5c2fab2 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 3d69ef2ad65..bf66606b94f 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index ee51401aec8..74d755ccd0f 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index d4444ca7c04..7300be92fea 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index b1aa5030e4e..e28db0f0a80 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index a7b47b5296c..3b699f4633b 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index b737018c256..ab6addefc89 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index ffa0329876d..7b0e9327274 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index cd0b40f6a55..a3537edf997 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 718ec14dce7..d64f48c37d4 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 6bada99465c..3e8fe53f92c 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index f23b2c59c84..8caa22c8bb3 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -593,12 +593,7 @@ Enabled loggers: {0} Enabled loggers: {0} - {0} is a comma-separated list of log types (e.g. "Binary log", "File log"). - - - {0} wrote to: {1} - {0} wrote to: {1} - {0} is the logger name. {1} is a comma-separated list of full file paths. + {0} is a comma-separated list of logger types (e.g. "Binary logger", "File logger"). Logging verbosity is set to: {0}. diff --git a/src/Framework/LoggersRegisteredEventArgs.cs b/src/Framework/LoggersRegisteredEventArgs.cs index 73618e19b3b..97c128ed5e8 100644 --- a/src/Framework/LoggersRegisteredEventArgs.cs +++ b/src/Framework/LoggersRegisteredEventArgs.cs @@ -90,7 +90,7 @@ public LoggersRegisteredEventArgs(IReadOnlyList loggers) /// /// The registered loggers. /// - public IReadOnlyList Loggers { get; internal set; } = Array.Empty(); + public IReadOnlyList Loggers { get; private set; } = Array.Empty(); internal override void WriteToStream(BinaryWriter writer) { diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index 7e14683f004..3ffac876523 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -164,7 +164,7 @@ {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index cadb27e7a52..f638a77a50c 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 26a7add386e..ae4edd05a9b 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index 615052f8bbe..4a89306b8e6 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index d396abfc874..04407b8a196 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 878fcabdc29..6d74479592f 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 2c22f2781e7..bcd289e9cdf 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index e6f29634735..672af58685b 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index 113c8ae4384..48e6d87f315 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 337d73c30dc..b93272a86b1 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 6647a888b25..b938871c2b8 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index 4d6fb9dbe6e..44aa7e417f6 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index dd31f87af9f..af7907f2b12 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index 8dbe9a2e10d..9e561450861 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -105,7 +105,7 @@ {0} wrote to: {1} {0} wrote to: {1} - {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator. + {0} is the logger name. {1} is a list of full file paths separated by the current culture's list separator (followed by a space). Path must be rooted. From 09964049e3b9370158e0c63017a7abacac65d9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Fri, 22 May 2026 09:33:35 +0200 Subject: [PATCH 19/19] fixed flaky unit test --- .../BackEnd/TaskHostCallback_Tests.cs | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/TaskHostCallback_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostCallback_Tests.cs index 5848692f084..1867f40879e 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostCallback_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostCallback_Tests.cs @@ -277,10 +277,13 @@ public void GlobalProperties_ForwardedToAutoEjectedTaskInMultiThreadedMode() public void GlobalProperties_UseBuildLevelWhenChangeWaveDisabled() { using TestEnvironment env = TestEnvironment.Create(_output); - env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_6.ToString()); - string testDir = env.CreateFolder().Path; + try + { + ChangeWaves.ResetStateForTests(); + env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_6.ToString()); + string testDir = env.CreateFolder().Path; - string projectContents = $@" + string projectContents = $@" @@ -290,32 +293,37 @@ public void GlobalProperties_UseBuildLevelWhenChangeWaveDisabled() "; - string projectFile = Path.Combine(testDir, "Test.proj"); - File.WriteAllText(projectFile, projectContents); - - // These request-level properties should NOT be forwarded when the wave is disabled - var requestGlobalProperties = new Dictionary - { - ["TestRequestProperty"] = "RequestValue", - }; + string projectFile = Path.Combine(testDir, "Test.proj"); + File.WriteAllText(projectFile, projectContents); - var logger = new MockLogger(_output); - BuildResult buildResult = BuildManager.DefaultBuildManager.Build( - new BuildParameters + // These request-level properties should NOT be forwarded when the wave is disabled + var requestGlobalProperties = new Dictionary { - MultiThreaded = true, - MaxNodeCount = 4, - Loggers = [logger], - EnableNodeReuse = false, - }, - new BuildRequestData(projectFile, requestGlobalProperties, null, ["Test"], null)); - - buildResult.OverallResult.ShouldBe(BuildResultCode.Success); - - // With wave disabled, build-level properties are used (empty in this test), - // so request-level properties should NOT appear - logger.FullLog.ShouldNotContain("GlobalProperty: TestRequestProperty=RequestValue"); - logger.FullLog.ShouldContain("GlobalPropertyCount = 0"); + ["TestRequestProperty"] = "RequestValue", + }; + + var logger = new MockLogger(_output); + BuildResult buildResult = BuildManager.DefaultBuildManager.Build( + new BuildParameters + { + MultiThreaded = true, + MaxNodeCount = 4, + Loggers = [logger], + EnableNodeReuse = false, + }, + new BuildRequestData(projectFile, requestGlobalProperties, null, ["Test"], null)); + + buildResult.OverallResult.ShouldBe(BuildResultCode.Success); + + // With wave disabled, build-level properties are used (empty in this test), + // so request-level properties should NOT appear + logger.FullLog.ShouldNotContain("GlobalProperty: TestRequestProperty=RequestValue"); + logger.FullLog.ShouldContain("GlobalPropertyCount = 0"); + } + finally + { + ChangeWaves.ResetStateForTests(); + } } ///