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