File: Logging\LoggerDescription.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Reflection;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using InternalLoggerException = Microsoft.Build.Exceptions.InternalLoggerException;
 
#nullable disable
 
namespace Microsoft.Build.Logging
{
    /// <summary>
    /// This class is used to contain information about a logger as a collection of values that
    /// can be used to instantiate the logger and can be serialized to be passed between different
    /// processes.
    /// </summary>
    public class LoggerDescription : ITranslatable
    {
        #region Constructor
 
        internal LoggerDescription()
        {
        }
 
        /// <summary>
        /// Creates a logger description from given data
        /// </summary>
        public LoggerDescription(
            string loggerClassName,
            string loggerAssemblyName,
            string loggerAssemblyFile,
            string loggerSwitchParameters,
            LoggerVerbosity verbosity) : this(loggerClassName,
            loggerAssemblyName,
            loggerAssemblyFile,
            loggerSwitchParameters,
            verbosity,
            isOptional: false)
        {
        }
 
        /// <summary>
        /// Creates a logger description from given data
        /// </summary>
        public LoggerDescription(
            string loggerClassName,
            string loggerAssemblyName,
            string loggerAssemblyFile,
            string loggerSwitchParameters,
            LoggerVerbosity verbosity,
            bool isOptional)
        {
            _loggerClassName = loggerClassName;
 
            if (loggerAssemblyFile != null && !Path.IsPathRooted(loggerAssemblyFile))
            {
                loggerAssemblyFile = FileUtilities.NormalizePath(loggerAssemblyFile);
            }
 
            _loggerAssembly = AssemblyLoadInfo.Create(loggerAssemblyName, loggerAssemblyFile);
            _loggerSwitchParameters = loggerSwitchParameters;
            _verbosity = verbosity;
            _isOptional = isOptional;
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// This property exposes the logger id which identifies each distributed logger uniquiely
        /// </summary>
        internal int LoggerId
        {
            get
            {
                return _loggerId;
            }
            set
            {
                _loggerId = value;
            }
        }
 
        /// <summary>
        /// This property generates the logger name by appending together the class name and assembly name
        /// </summary>
        public string Name
        {
            get
            {
                if (!string.IsNullOrEmpty(_loggerClassName) &&
                    !string.IsNullOrEmpty(_loggerAssembly.AssemblyFile))
                {
                    return _loggerClassName + ":" + _loggerAssembly.AssemblyFile;
                }
                else if (!string.IsNullOrEmpty(_loggerClassName))
                {
                    return _loggerClassName;
                }
                else
                {
                    return _loggerAssembly.AssemblyFile ?? _loggerAssembly.AssemblyName;
                }
            }
        }
 
        /// <summary>
        /// Returns the string of logger parameters, null if there are none
        /// </summary>
        public string LoggerSwitchParameters
        {
            get
            {
                return _loggerSwitchParameters;
            }
        }
 
        public bool IsOptional
        {
            get
            {
                return _isOptional;
            }
        }
 
        /// <summary>
        /// Return the verbosity for this logger (from command line all loggers get same verbosity)
        /// </summary>
        public LoggerVerbosity Verbosity
        {
            get
            {
                return _verbosity;
            }
        }
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Create an IForwardingLogger out of the data in this description. This method may throw a variety of
        /// reflection exceptions if the data is invalid. It is the resposibility of the caller to handle these
        /// exceptions if desired.
        /// </summary>
        /// <returns></returns>
        internal IForwardingLogger CreateForwardingLogger()
        {
            IForwardingLogger forwardingLogger = null;
            try
            {
                forwardingLogger = (IForwardingLogger)CreateLogger(true);
 
                // Check if the class was not found in the assembly
                if (forwardingLogger == null)
                {
                    InternalLoggerException.Throw(null, null, "LoggerNotFoundError", true, this.Name);
                }
            }
            catch (Exception e) // Wrap other exceptions in a more meaningful exception. LoggerException and InternalLoggerException are already meaningful.
            when (!(e is LoggerException /* Polite logger Failure*/ || e is InternalLoggerException /* LoggerClass not found*/ || ExceptionHandling.IsCriticalException(e)))
            {
                InternalLoggerException.Throw(e, null, "LoggerCreationError", true, Name);
            }
 
            return forwardingLogger;
        }
 
        /// <summary>
        /// Create an ILogger out of the data in this description. This method may throw a variety of
        /// reflection exceptions if the data is invalid. It is the resposibility of the caller to handle these
        /// exceptions if desired.
        /// </summary>
        /// <returns></returns>
        public ILogger CreateLogger()
        {
            return CreateLogger(false);
        }
 
        /// <summary>
        /// Loads a logger from its assembly, instantiates it, and handles errors.
        /// </summary>
        /// <returns>Instantiated logger.</returns>
        private ILogger CreateLogger(bool forwardingLogger)
        {
            ILogger logger = null;
 
            try
            {
                if (forwardingLogger)
                {
                    // load the logger from its assembly
                    LoadedType loggerClass = (new TypeLoader(s_forwardingLoggerClassFilter)).Load(_loggerClassName, _loggerAssembly);
 
                    if (loggerClass != null)
                    {
                        // instantiate the logger
                        logger = (IForwardingLogger)Activator.CreateInstance(loggerClass.Type);
                    }
                }
                else
                {
                    // load the logger from its assembly
                    LoadedType loggerClass = (new TypeLoader(s_loggerClassFilter)).Load(_loggerClassName, _loggerAssembly);
 
                    if (loggerClass != null)
                    {
                        // instantiate the logger
                        logger = (ILogger)Activator.CreateInstance(loggerClass.Type);
                    }
                }
            }
            catch (InvalidCastException e)
            {
                // The logger when trying to load has hit an invalid case, this is usually due to the framework assembly being a different version
                string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("LoggerInstantiationFailureErrorInvalidCast", _loggerClassName, _loggerAssembly.AssemblyLocation, e.Message);
                throw new LoggerException(message, e.InnerException);
            }
            catch (TargetInvocationException e) when (e.InnerException is LoggerException le)
            {
                // At this point, the interesting stack is the internal exception;
                // the outer exception is System.Reflection stuff that says nothing
                // about the nature of the logger failure.
                // Logger failed politely during construction. In order to preserve
                // the stack trace at which the error occurred we wrap the original
                // exception instead of throwing.
                throw new LoggerException(le.Message, le, le.ErrorCode, le.HelpKeyword);
            }
 
            return logger;
        }
 
        /// <summary>
        /// Used for finding loggers when reflecting through assemblies.
        /// </summary>
        private static readonly Func<Type, object, bool> s_forwardingLoggerClassFilter = IsForwardingLoggerClass;
 
        /// <summary>
        /// Used for finding loggers when reflecting through assemblies.
        /// </summary>
        private static readonly Func<Type, object, bool> s_loggerClassFilter = IsLoggerClass;
 
        /// <summary>
        /// Checks if the given type is a logger class.
        /// </summary>
        /// <remarks>This method is used as a Type Filter delegate.</remarks>
        /// <returns>true, if specified type is a logger</returns>
        private static bool IsForwardingLoggerClass(Type type, object unused)
        {
            return type.GetTypeInfo().IsClass &&
                !type.GetTypeInfo().IsAbstract &&
                (type.GetTypeInfo().GetInterface("IForwardingLogger") != null);
        }
 
        /// <summary>
        /// Checks if the given type is a logger class.
        /// </summary>
        /// <remarks>This method is used as a TypeFilter delegate.</remarks>
        /// <returns>true, if specified type is a logger</returns>
        private static bool IsLoggerClass(Type type, object unused)
        {
            return type.GetTypeInfo().IsClass &&
                !type.GetTypeInfo().IsAbstract &&
                (type.GetTypeInfo().GetInterface("ILogger") != null);
        }
 
        /// <summary>
        /// Converts the path to the logger assembly to a full path
        /// </summary>
        internal void ConvertPathsToFullPaths()
        {
            if (_loggerAssembly.AssemblyFile != null)
            {
                _loggerAssembly =
                    AssemblyLoadInfo.Create(_loggerAssembly.AssemblyName, Path.GetFullPath(_loggerAssembly.AssemblyFile));
            }
        }
 
        #endregion
 
        #region Data
        private string _loggerClassName;
        private string _loggerSwitchParameters;
        private AssemblyLoadInfo _loggerAssembly;
        private LoggerVerbosity _verbosity;
        private int _loggerId;
        private bool _isOptional;
        #endregion
 
        #region CustomSerializationToStream
        internal void WriteToStream(BinaryWriter writer)
        {
            writer.WriteOptionalString(_loggerClassName);
            writer.WriteOptionalString(_loggerSwitchParameters);
 
            if (_loggerAssembly == null)
            {
                writer.Write((byte)0);
            }
            else
            {
                writer.Write((byte)1);
 
                writer.WriteOptionalString(_loggerAssembly.AssemblyFile);
                writer.WriteOptionalString(_loggerAssembly.AssemblyName);
            }
 
            writer.Write((Int32)_verbosity);
            writer.Write((Int32)_loggerId);
        }
 
        internal void CreateFromStream(BinaryReader reader)
        {
            _loggerClassName = reader.ReadByte() == 0 ? null : reader.ReadString();
            _loggerSwitchParameters = reader.ReadByte() == 0 ? null : reader.ReadString();
 
            if (reader.ReadByte() == 0)
            {
                _loggerAssembly = null;
            }
            else
            {
                string assemblyFile = reader.ReadByte() == 0 ? null : reader.ReadString();
                string assemblyName = reader.ReadByte() == 0 ? null : reader.ReadString();
 
                _loggerAssembly = AssemblyLoadInfo.Create(assemblyName, assemblyFile);
            }
 
            _verbosity = (LoggerVerbosity)reader.ReadInt32();
            _loggerId = reader.ReadInt32();
        }
        #endregion
 
        #region INodePacketTranslatable Members
 
        void ITranslatable.Translate(ITranslator translator)
        {
            translator.Translate(ref _loggerClassName);
            translator.Translate(ref _loggerSwitchParameters);
            translator.Translate(ref _loggerAssembly, AssemblyLoadInfo.FactoryForTranslation);
            translator.TranslateEnum(ref _verbosity, (int)_verbosity);
            translator.Translate(ref _loggerId);
        }
 
        internal static LoggerDescription FactoryForTranslation(ITranslator translator)
        {
            LoggerDescription description = new LoggerDescription();
            ((ITranslatable)description).Translate(translator);
            return description;
        }
 
        #endregion
    }
}