1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Reflection; 6 using System.Text; 7 using System.IO; 8 9 using Microsoft.Build.Framework; 10 using Microsoft.Build.BuildEngine.Shared; 11 12 namespace Microsoft.Build.BuildEngine 13 { 14 /// <summary> 15 /// This class is used to contain information about a logger as a collection of values that 16 /// can be used to instantiate the logger and can be serialized to be passed between different 17 /// processes. 18 /// </summary> 19 public class LoggerDescription 20 { 21 #region Constructor 22 LoggerDescription()23 internal LoggerDescription() 24 { 25 } 26 27 /// <summary> 28 /// Creates a logger description from given data 29 /// </summary> LoggerDescription( string loggerClassName, string loggerAssemblyName, string loggerAssemblyFile, string loggerSwitchParameters, LoggerVerbosity verbosity )30 public LoggerDescription 31 ( 32 string loggerClassName, 33 string loggerAssemblyName, 34 string loggerAssemblyFile, 35 string loggerSwitchParameters, 36 LoggerVerbosity verbosity 37 ) 38 { 39 this.loggerClassName = loggerClassName; 40 this.loggerAssembly = new AssemblyLoadInfo(loggerAssemblyName, loggerAssemblyFile); 41 this.loggerSwitchParameters = loggerSwitchParameters; 42 this.verbosity = verbosity; 43 } 44 45 #endregion 46 47 #region Properties 48 49 /// <summary> 50 /// This property exposes the logger id which identifies each distributed logger uniquiely 51 /// </summary> 52 internal int LoggerId 53 { 54 get 55 { 56 return this.loggerId; 57 } 58 set 59 { 60 this.loggerId = value; 61 } 62 } 63 64 /// <summary> 65 /// This property generates the logger name by appending together the class name and assembly name 66 /// </summary> 67 internal string Name 68 { 69 get 70 { 71 if (!string.IsNullOrEmpty(this.loggerClassName) && 72 !string.IsNullOrEmpty(this.loggerAssembly.AssemblyFile)) 73 { 74 return this.loggerClassName + ":" + this.loggerAssembly.AssemblyFile; 75 } 76 else if ( !string.IsNullOrEmpty(this.loggerClassName) ) 77 { 78 return this.loggerClassName; 79 } 80 else 81 { 82 return this.loggerAssembly.AssemblyFile; 83 } 84 } 85 } 86 87 /// <summary> 88 /// Returns the string of logger parameters, null if there are none 89 /// </summary> 90 public string LoggerSwitchParameters 91 { 92 get 93 { 94 return loggerSwitchParameters; 95 } 96 } 97 98 /// <summary> 99 /// Return the verbosity for this logger (from command line all loggers get same verbosity) 100 /// </summary> 101 public LoggerVerbosity Verbosity 102 { 103 get 104 { 105 return this.verbosity; 106 } 107 } 108 109 #endregion 110 111 #region Methods 112 113 /// <summary> 114 /// Create an IForwardingLogger out of the data in this description. This method may throw a variety of 115 /// reflection exceptions if the data is invalid. It is the resposibility of the caller to handle these 116 /// exceptions if desired. 117 /// </summary> 118 /// <returns></returns> CreateForwardingLogger()119 internal IForwardingLogger CreateForwardingLogger() 120 { 121 return (IForwardingLogger)CreateLogger(true); 122 } 123 124 /// <summary> 125 /// Create an ILogger out of the data in this description. This method may throw a variety of 126 /// reflection exceptions if the data is invalid. It is the resposibility of the caller to handle these 127 /// exceptions if desired. 128 /// </summary> 129 /// <returns></returns> CreateLogger()130 internal ILogger CreateLogger() 131 { 132 return CreateLogger(false); 133 } 134 135 /// <summary> 136 /// Loads a logger from its assembly, instantiates it, and handles errors. 137 /// </summary> 138 /// <returns>Instantiated logger.</returns> CreateLogger(bool forwardingLogger)139 private ILogger CreateLogger(bool forwardingLogger) 140 { 141 ILogger logger = null; 142 143 try 144 { 145 if (forwardingLogger) 146 { 147 // load the logger from its assembly 148 LoadedType loggerClass = (new TypeLoader(forwardingLoggerClassFilter)).Load(loggerClassName, loggerAssembly); 149 150 if (loggerClass != null) 151 { 152 // instantiate the logger 153 logger = (IForwardingLogger)Activator.CreateInstance(loggerClass.Type); 154 } 155 } 156 else 157 { 158 // load the logger from its assembly 159 LoadedType loggerClass = (new TypeLoader(loggerClassFilter)).Load(loggerClassName, loggerAssembly); 160 161 if (loggerClass != null) 162 { 163 // instantiate the logger 164 logger = (ILogger)Activator.CreateInstance(loggerClass.Type); 165 } 166 } 167 } 168 catch (TargetInvocationException e) 169 { 170 // At this point, the interesting stack is the internal exception; 171 // the outer exception is System.Reflection stuff that says nothing 172 // about the nature of the logger failure. 173 Exception innerException = e.InnerException; 174 175 if (innerException is LoggerException) 176 { 177 // Logger failed politely during construction. In order to preserve 178 // the stack trace at which the error occured we wrap the original 179 // exception instead of throwing. 180 LoggerException l = ((LoggerException)innerException); 181 throw new LoggerException(l.Message, innerException, l.ErrorCode, l.HelpKeyword); 182 } 183 else 184 { 185 throw; 186 } 187 } 188 189 return logger; 190 } 191 192 /// <summary> 193 /// Used for finding loggers when reflecting through assemblies. 194 /// </summary> 195 private static readonly TypeFilter forwardingLoggerClassFilter = new TypeFilter(IsForwardingLoggerClass); 196 197 /// <summary> 198 /// Used for finding loggers when reflecting through assemblies. 199 /// </summary> 200 private static readonly TypeFilter loggerClassFilter = new TypeFilter(IsLoggerClass); 201 202 /// <summary> 203 /// Checks if the given type is a logger class. 204 /// </summary> 205 /// <remarks>This method is used as a TypeFilter delegate.</remarks> 206 /// <returns>true, if specified type is a logger</returns> IsForwardingLoggerClass(Type type, object unused)207 private static bool IsForwardingLoggerClass(Type type, object unused) 208 { 209 return (type.IsClass && 210 !type.IsAbstract && 211 (type.GetInterface("IForwardingLogger") != null)); 212 } 213 214 /// <summary> 215 /// Checks if the given type is a logger class. 216 /// </summary> 217 /// <remarks>This method is used as a TypeFilter delegate.</remarks> 218 /// <returns>true, if specified type is a logger</returns> IsLoggerClass(Type type, object unused)219 private static bool IsLoggerClass(Type type, object unused) 220 { 221 return (type.IsClass && 222 !type.IsAbstract && 223 (type.GetInterface("ILogger") != null)); 224 } 225 226 /// <summary> 227 /// Converts the path to the logger assembly to a full path 228 /// </summary> ConvertPathsToFullPaths()229 internal void ConvertPathsToFullPaths() 230 { 231 if (loggerAssembly.AssemblyFile != null) 232 { 233 loggerAssembly = 234 new AssemblyLoadInfo(loggerAssembly.AssemblyName, Path.GetFullPath(loggerAssembly.AssemblyFile)); 235 } 236 } 237 238 #endregion 239 240 #region Data 241 private string loggerClassName; 242 private string loggerSwitchParameters; 243 private AssemblyLoadInfo loggerAssembly; 244 private LoggerVerbosity verbosity; 245 private int loggerId; 246 #endregion 247 248 #region CustomSerializationToStream WriteToStream(BinaryWriter writer)249 internal void WriteToStream(BinaryWriter writer) 250 { 251 #region LoggerClassName 252 if (loggerClassName == null) 253 { 254 writer.Write((byte)0); 255 } 256 else 257 { 258 writer.Write((byte)1); 259 writer.Write(loggerClassName); 260 } 261 #endregion 262 #region LoggerSwitchParameters 263 if (loggerSwitchParameters == null) 264 { 265 writer.Write((byte)0); 266 } 267 else 268 { 269 writer.Write((byte)1); 270 writer.Write(loggerSwitchParameters); 271 } 272 #endregion 273 #region LoggerAssembly 274 if (loggerAssembly == null) 275 { 276 writer.Write((byte)0); 277 } 278 else 279 { 280 writer.Write((byte)1); 281 if (loggerAssembly.AssemblyFile == null) 282 { 283 writer.Write((byte)0); 284 } 285 else 286 { 287 writer.Write((byte)1); 288 writer.Write(loggerAssembly.AssemblyFile); 289 } 290 291 if (loggerAssembly.AssemblyName == null) 292 { 293 writer.Write((byte)0); 294 } 295 else 296 { 297 writer.Write((byte)1); 298 writer.Write(loggerAssembly.AssemblyName); 299 } 300 } 301 #endregion 302 writer.Write((Int32)verbosity); 303 writer.Write((Int32)loggerId); 304 } 305 CreateFromStream(BinaryReader reader)306 internal void CreateFromStream(BinaryReader reader) 307 { 308 #region LoggerClassName 309 if (reader.ReadByte() ==0) 310 { 311 loggerClassName = null; 312 } 313 else 314 { 315 loggerClassName = reader.ReadString(); 316 } 317 #endregion 318 #region LoggerSwitchParameters 319 if (reader.ReadByte() == 0) 320 { 321 loggerSwitchParameters = null; 322 } 323 else 324 { 325 loggerSwitchParameters = reader.ReadString(); 326 } 327 #endregion 328 #region LoggerAssembly 329 if (reader.ReadByte() == 0) 330 { 331 loggerAssembly = null; 332 } 333 else 334 { 335 336 string assemblyName = null; 337 string assemblyFile = null; 338 339 if (reader.ReadByte() != 0) 340 { 341 assemblyFile = reader.ReadString(); 342 } 343 344 if (reader.ReadByte() != 0) 345 { 346 assemblyName = reader.ReadString(); 347 } 348 349 loggerAssembly = new AssemblyLoadInfo(assemblyName, assemblyFile); 350 } 351 #endregion 352 verbosity = (LoggerVerbosity)reader.ReadInt32(); 353 loggerId = reader.ReadInt32(); 354 } 355 #endregion 356 } 357 } 358