| File: Logging\FileLogger.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.IO; using System.Text; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Shared; #nullable disable namespace Microsoft.Build.Logging { /// <summary> /// A specialization of the ConsoleLogger that logs to a file instead of the console. /// The output in terms of what is written and how it looks is identical. For example you can /// log verbosely to a file using the FileLogger while simultaneously logging only high priority events /// to the console using a ConsoleLogger. /// </summary> /// <remarks> /// It's unfortunate that this is derived from ConsoleLogger, which is itself a facade; it makes things more /// complex -- for example, there is parameter parsing in this class, plus in BaseConsoleLogger. However we have /// to derive FileLogger from ConsoleLogger because it shipped that way in Whidbey. /// </remarks> public class FileLogger : ConsoleLogger, IFileOutputLogger { #region Constructors /// <summary> /// Default constructor. /// </summary> public FileLogger() : base( LoggerVerbosity.Normal, write: null, // Overwritten below colorSet: BaseConsoleLogger.DontSetColor, colorReset: BaseConsoleLogger.DontResetColor) { WriteHandler = Write; if (EncodingUtilities.GetExternalOverriddenUILanguageIfSupportableWithEncoding() != null) { _encoding = Encoding.UTF8; } } #endregion /// <summary> /// Signs up the console file logger for all build events. /// This is the backward-compatible overload. /// </summary> /// <param name="eventSource">Available events.</param> public override void Initialize(IEventSource eventSource) { ArgumentNullException.ThrowIfNull(eventSource); eventSource.BuildFinished += FileLoggerBuildFinished; InitializeFileLogger(eventSource, 1); } private void FileLoggerBuildFinished(object sender, BuildFinishedEventArgs e) { _fileWriter?.Flush(); } /// <summary> /// Creates new file for logging /// </summary> private void InitializeFileLogger(IEventSource eventSource, int nodeCount) { // Prepend the default setting of "forcenoalign": no alignment is needed as we're // writing to a file string parameters = Parameters; if (parameters != null) { Parameters = "FORCENOALIGN;" + parameters; } else { Parameters = "FORCENOALIGN;"; } ParseFileLoggerParameters(); // Finally, ask the base console logger class to initialize. It may // want to make decisions based on our verbosity, so we do this last. base.Initialize(eventSource, nodeCount); KnownTelemetry.LoggingConfigurationTelemetry.FileLogger = true; KnownTelemetry.LoggingConfigurationTelemetry.FileLoggerVerbosity = Verbosity.ToString(); if (!SkipProjectStartedText && Verbosity >= LoggerVerbosity.Normal) { eventSource.BuildStarted += (obj, args) => WriteHandler(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("LogLoggerVerbosity", Verbosity)); } try { string logDirectory = null; try { logDirectory = Path.GetDirectoryName(Path.GetFullPath(_logFileName)); } catch { // Directory creation is best-effort; if finding its path fails don't create the directory // and possibly let OpenWrite() below report the failure } if (logDirectory != null) { Directory.CreateDirectory(logDirectory); } _fileWriter = FileUtilities.OpenWrite(_logFileName, _append, _encoding); _fileWriter.AutoFlush = _autoFlush; } catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(out errorCode, out helpKeyword, "InvalidFileLoggerFile", _logFileName, e.Message); _fileWriter?.Dispose(); throw new LoggerException(message, e.InnerException, errorCode, helpKeyword); } } /// <summary> /// Multiproc aware initialization /// </summary> public override void Initialize(IEventSource eventSource, int nodeCount) { InitializeFileLogger(eventSource, nodeCount); } /// <summary> /// The handler for the write delegate of the console logger we are deriving from. /// </summary> /// <param name="text">The text to write to the log</param> private void Write(string text) { try { _fileWriter.Write(text); } catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(out errorCode, out helpKeyword, "InvalidFileLoggerFile", _logFileName, ex.Message); _fileWriter?.Dispose(); throw new LoggerException(message, ex.InnerException, errorCode, helpKeyword); } } /// <summary> /// Shutdown method implementation of ILogger - we need to flush and close our logfile. /// </summary> public override void Shutdown() { _fileWriter?.Dispose(); } /// <summary> /// Parses out the logger parameters from the Parameters string. /// </summary> private void ParseFileLoggerParameters() { if (Parameters == null) { return; } foreach (string parameter in Parameters.Split(s_fileLoggerParameterDelimiters)) { if (parameter.Length <= 0) { continue; } var parameterAndValue = parameter.Split(s_fileLoggerParameterValueSplitCharacter); ApplyFileLoggerParameter(parameterAndValue[0], parameterAndValue.Length > 1 ? parameterAndValue[1] : null); } } /// <summary> /// Apply a parameter parsed by the file logger. /// </summary> private void ApplyFileLoggerParameter(string parameterName, string parameterValue) { switch (parameterName.ToUpperInvariant()) { case "LOGFILE": _logFileName = FileUtilities.FixFilePath(parameterValue); break; case "APPEND": _append = true; break; case "NOAUTOFLUSH": _autoFlush = false; break; case "ERRORSONLY": case "WARNINGSONLY": ShowSummary = false; SkipProjectStartedText = true; break; case "ENCODING": try { _encoding = Encoding.GetEncoding(parameterValue); } catch (ArgumentException ex) { // Can't change strings at this point, so for now we are using the exception string // verbatim, and supplying a error code directly. // This should move into the .resx later. throw new LoggerException(ex.Message, ex.InnerException, "MSB4128", null); } break; default: // We will not error for unrecognized parameters, since someone may wish to // extend this class and call this base method before theirs. break; } } #region Private member data /// <summary> /// logFileName is the name of the log file that we will generate /// the default value is msbuild.log /// </summary> private string _logFileName = "msbuild.log"; /// <summary> /// The path to the log file. /// </summary> internal string FilePath => Path.GetFullPath(_logFileName); /// <inheritdoc/> System.Collections.Generic.IReadOnlyList<string> IFileOutputLogger.OutputFilePaths => new[] { FilePath }; /// <summary> /// fileWriter is the stream that has been opened on our log file. /// </summary> private StreamWriter _fileWriter; /// <summary> /// Whether the logger should append to any existing file. /// Default is to overwrite. /// </summary> private bool _append; /// <summary> /// Whether the logger should flush aggressively to disk. /// Default is true. This preserves the most information in the case /// of a crash, but may slow the logger down. /// </summary> private bool _autoFlush = true; #if FEATURE_ENCODING_DEFAULT /// <summary> /// Encoding for the output. Defaults to ANSI. /// </summary> private Encoding _encoding = Encoding.Default; #else /// <summary> /// Encoding for the output. Defaults to UTF-8. /// </summary> private Encoding _encoding = new UTF8Encoding(false); #endif /// <summary> /// File logger parameters delimiters. /// </summary> private static readonly char[] s_fileLoggerParameterDelimiters = MSBuildConstants.SemicolonChar; /// <summary> /// File logger parameter value split character. /// </summary> private static readonly char[] s_fileLoggerParameterValueSplitCharacter = MSBuildConstants.EqualsChar; #endregion } }