Add feature define for multiple appdomains.
[mono.git] / mcs / class / referencesource / System.ServiceModel.Internals / System / Runtime / Diagnostics / DiagnosticTraceBase.cs
1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4
5 namespace System.Runtime.Diagnostics
6 {
7     using System;
8     using System.Collections;
9     using System.ComponentModel;
10     using System.Diagnostics;
11     using System.Globalization;
12     using System.IO;
13     using System.Security;
14     using System.Text;
15     using System.Xml;
16     using System.Xml.XPath;
17     using System.Diagnostics.CodeAnalysis;
18     using System.Security.Permissions;
19
20     abstract class DiagnosticTraceBase
21     {
22         //Diagnostics trace
23         protected const string DefaultTraceListenerName = "Default";
24         protected const string TraceRecordVersion = "http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord";
25
26         protected static string AppDomainFriendlyName = AppDomain.CurrentDomain.FriendlyName;
27         const ushort TracingEventLogCategory = 4;
28
29         object thisLock;
30         bool tracingEnabled = true;
31         bool calledShutdown;
32         bool haveListeners;
33         SourceLevels level;
34         protected string TraceSourceName;
35         TraceSource traceSource;
36         [Fx.Tag.SecurityNote(Critical = "This determines the event source name.")]
37         [SecurityCritical]
38         string eventSourceName;
39
40         public DiagnosticTraceBase(string traceSourceName)
41         {
42             this.thisLock = new object();
43             this.TraceSourceName = traceSourceName;
44             this.LastFailure = DateTime.MinValue;
45         }
46
47         protected DateTime LastFailure { get; set; }
48
49         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
50                 Justification = "SecurityCritical method. Does not expose critical resources returned by methods with Link Demands")]
51         [Fx.Tag.SecurityNote(Critical = "Critical because we are invoking TraceSource.Listeners which has a Link Demand for UnmanagedCode permission.",
52             Miscellaneous = "Asserting Unmanaged Code causes traceSource.Listeners to be successfully initiated and cached. But the Listeners property has a LinkDemand for UnmanagedCode, so it can't be read by partially trusted assemblies in heterogeneous appdomains")]
53         [SecurityCritical]
54         [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
55         static void UnsafeRemoveDefaultTraceListener(TraceSource traceSource)
56         {
57             traceSource.Listeners.Remove(DiagnosticTraceBase.DefaultTraceListenerName);
58         }
59
60         public TraceSource TraceSource
61         {
62             get
63             {
64                 return this.traceSource;
65             }
66
67             set
68             {
69                 SetTraceSource(value);
70             }
71         }
72
73         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
74                 Justification = "Does not expose critical resources returned by methods with Link Demands")]
75         [Fx.Tag.SecurityNote(Critical = "Critical because we are invoking TraceSource.Listeners which has a Link Demand for UnmanagedCode permission.",
76             Safe = "Safe because are only retrieving the count of listeners and removing the default trace listener - we aren't leaking any critical resources.")]
77         [SecuritySafeCritical]
78         protected void SetTraceSource(TraceSource traceSource)
79         {
80             if (traceSource != null)
81             {
82                 UnsafeRemoveDefaultTraceListener(traceSource);
83                 this.traceSource = traceSource;
84                 this.haveListeners = this.traceSource.Listeners.Count > 0;
85             }
86         }
87
88         public bool HaveListeners
89         {
90             get
91             {
92                 return this.haveListeners;
93             }
94         }
95
96         SourceLevels FixLevel(SourceLevels level)
97         {
98             //the bit fixing below is meant to keep the trace level legal even if somebody uses numbers in config
99             if (((level & ~SourceLevels.Information) & SourceLevels.Verbose) != 0)
100             {
101                 level |= SourceLevels.Verbose;
102             }
103             else if (((level & ~SourceLevels.Warning) & SourceLevels.Information) != 0)
104             {
105                 level |= SourceLevels.Information;
106             }
107             else if (((level & ~SourceLevels.Error) & SourceLevels.Warning) != 0)
108             {
109                 level |= SourceLevels.Warning;
110             }
111             if (((level & ~SourceLevels.Critical) & SourceLevels.Error) != 0)
112             {
113                 level |= SourceLevels.Error;
114             }
115             if ((level & SourceLevels.Critical) != 0)
116             {
117                 level |= SourceLevels.Critical;
118             }
119
120             // If only the ActivityTracing flag is set, then
121             // we really have Off. Do not do ActivityTracing then.
122             if (level == SourceLevels.ActivityTracing)
123             {
124                 level = SourceLevels.Off;
125             }
126
127             return level;
128         }
129
130         protected virtual void OnSetLevel(SourceLevels level)
131         {
132         }
133
134         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
135                 Justification = "Does not expose critical resources returned by methods with Link Demands")]
136         [Fx.Tag.SecurityNote(Critical = "Critical because we are invoking TraceSource.Listeners and SourceSwitch.Level which have Link Demands for UnmanagedCode permission.")]
137         [SecurityCritical]
138         void SetLevel(SourceLevels level)
139         {
140             SourceLevels fixedLevel = FixLevel(level);
141             this.level = fixedLevel;
142
143             if (this.TraceSource != null)
144             {
145                 // Need this for setup from places like TransactionBridge.
146                 this.haveListeners = this.TraceSource.Listeners.Count > 0;
147                 OnSetLevel(level);
148
149 #pragma warning disable 618
150                 this.tracingEnabled = this.HaveListeners && (level != SourceLevels.Off);
151 #pragma warning restore 618
152                 this.TraceSource.Switch.Level = level;
153             }
154         }
155
156         [Fx.Tag.SecurityNote(Critical = "Critical because we are invoking SetLevel.")]
157         [SecurityCritical]
158         void SetLevelThreadSafe(SourceLevels level)
159         {
160             lock (this.thisLock)
161             {
162                 SetLevel(level);
163             }
164         }
165
166         public SourceLevels Level
167         {
168             get
169             {
170                 if (this.TraceSource != null && (this.TraceSource.Switch.Level != this.level))
171                 {
172                     this.level = this.TraceSource.Switch.Level;
173                 }
174
175                 return this.level;
176             }
177
178             [Fx.Tag.SecurityNote(Critical = "Critical because we are invoking SetLevelTheadSafe.")]
179             [SecurityCritical]
180             set
181             {
182                 SetLevelThreadSafe(value);
183             }
184         }
185
186         protected string EventSourceName
187         {
188             [Fx.Tag.SecurityNote(Critical = "Access critical eventSourceName field",
189                 Safe = "Doesn't leak info\\resources")]
190             [SecuritySafeCritical]
191             get
192             {
193                 return this.eventSourceName;
194             }
195
196             [Fx.Tag.SecurityNote(Critical = "This determines the event source name.")]
197             [SecurityCritical]
198             set
199             {
200                 this.eventSourceName = value;
201             }
202         }
203
204         public bool TracingEnabled
205         {
206             get
207             {
208                 return this.tracingEnabled && this.traceSource != null;
209             }
210         }
211
212         protected static string ProcessName
213         {
214             [Fx.Tag.SecurityNote(Critical = "Satisfies a LinkDemand for 'PermissionSetAttribute' on type 'Process' when calling method GetCurrentProcess",
215             Safe = "Does not leak any resource and has been reviewed")]
216             [SecuritySafeCritical]
217             get
218             {
219                 string retval = null;
220                 using (Process process = Process.GetCurrentProcess())
221                 {
222                     retval = process.ProcessName;
223                 }
224                 return retval;
225             }
226         }
227
228         protected static int ProcessId
229         {
230             [Fx.Tag.SecurityNote(Critical = "Satisfies a LinkDemand for 'PermissionSetAttribute' on type 'Process' when calling method GetCurrentProcess",
231             Safe = "Does not leak any resource and has been reviewed")]
232             [SecuritySafeCritical]
233             get
234             {
235                 int retval = -1;
236                 using (Process process = Process.GetCurrentProcess())
237                 {
238                     retval = process.Id;
239                 }
240                 return retval;
241             }
242         }
243
244         public virtual bool ShouldTrace(TraceEventLevel level)
245         {
246             return ShouldTraceToTraceSource(level);
247         }
248
249         public bool ShouldTrace(TraceEventType type)
250         {
251             return this.TracingEnabled && this.HaveListeners &&
252                 (this.TraceSource != null) &&
253                 0 != ((int)type & (int)this.Level);
254         }
255
256         public bool ShouldTraceToTraceSource(TraceEventLevel level)
257         {
258             return ShouldTrace(TraceLevelHelper.GetTraceEventType(level));
259         }
260
261         //only used for exceptions, perf is not important
262         public static string XmlEncode(string text)
263         {
264             if (string.IsNullOrEmpty(text))
265             {
266                 return text;
267             }
268
269             int len = text.Length;
270             StringBuilder encodedText = new StringBuilder(len + 8); //perf optimization, expecting no more than 2 > characters
271
272             for (int i = 0; i < len; ++i)
273             {
274                 char ch = text[i];
275                 switch (ch)
276                 {
277                     case '<':
278                         encodedText.Append("&lt;");
279                         break;
280                     case '>':
281                         encodedText.Append("&gt;");
282                         break;
283                     case '&':
284                         encodedText.Append("&amp;");
285                         break;
286                     default:
287                         encodedText.Append(ch);
288                         break;
289                 }
290             }
291             return encodedText.ToString();
292         }
293
294         [Fx.Tag.SecurityNote(Critical = "Sets global event handlers for the AppDomain",
295             Safe = "Doesn't leak resources\\Information")]
296         [SecuritySafeCritical]
297         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
298                 Justification = "SecuritySafeCritical method, Does not expose critical resources returned by methods with Link Demands")]
299         protected void AddDomainEventHandlersForCleanup()
300         {
301             AppDomain currentDomain = AppDomain.CurrentDomain;
302             if (this.TraceSource != null)
303             {
304                 this.haveListeners = this.TraceSource.Listeners.Count > 0;
305             }
306
307             this.tracingEnabled = this.haveListeners;
308             if (this.TracingEnabled)
309             {
310                 currentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
311                 this.SetLevel(this.TraceSource.Switch.Level);
312 #if MONO_FEATURE_MULTIPLE_APPDOMAINS
313                 currentDomain.DomainUnload += new EventHandler(ExitOrUnloadEventHandler);
314 #endif
315                 currentDomain.ProcessExit += new EventHandler(ExitOrUnloadEventHandler);
316             }
317         }
318
319         void ExitOrUnloadEventHandler(object sender, EventArgs e)
320         {
321             ShutdownTracing();
322         }
323
324         protected abstract void OnUnhandledException(Exception exception);
325
326         protected void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
327         {
328             Exception e = (Exception)args.ExceptionObject;
329             OnUnhandledException(e);
330             ShutdownTracing();
331         }
332
333         protected static string CreateSourceString(object source)
334         {
335             var traceSourceStringProvider = source as ITraceSourceStringProvider;
336             if (traceSourceStringProvider != null)
337             {
338                 return traceSourceStringProvider.GetSourceString();
339             }
340
341             return CreateDefaultSourceString(source);
342         }
343
344         internal static string CreateDefaultSourceString(object source)
345         {
346             if (source == null)
347             {
348                 throw new ArgumentNullException("source");
349             }
350
351             return String.Format(CultureInfo.CurrentCulture, "{0}/{1}", source.GetType().ToString(), source.GetHashCode());
352         }
353
354         protected static void AddExceptionToTraceString(XmlWriter xml, Exception exception)
355         {
356             xml.WriteElementString(DiagnosticStrings.ExceptionTypeTag, XmlEncode(exception.GetType().AssemblyQualifiedName));
357             xml.WriteElementString(DiagnosticStrings.MessageTag, XmlEncode(exception.Message));
358             xml.WriteElementString(DiagnosticStrings.StackTraceTag, XmlEncode(StackTraceString(exception)));
359             xml.WriteElementString(DiagnosticStrings.ExceptionStringTag, XmlEncode(exception.ToString()));
360             Win32Exception win32Exception = exception as Win32Exception;
361             if (win32Exception != null)
362             {
363                 xml.WriteElementString(DiagnosticStrings.NativeErrorCodeTag, win32Exception.NativeErrorCode.ToString("X", CultureInfo.InvariantCulture));
364             }
365
366             if (exception.Data != null && exception.Data.Count > 0)
367             {
368                 xml.WriteStartElement(DiagnosticStrings.DataItemsTag);
369                 foreach (object dataItem in exception.Data.Keys)
370                 {
371                     xml.WriteStartElement(DiagnosticStrings.DataTag);
372                     xml.WriteElementString(DiagnosticStrings.KeyTag, XmlEncode(dataItem.ToString()));
373                     xml.WriteElementString(DiagnosticStrings.ValueTag, XmlEncode(exception.Data[dataItem].ToString()));
374                     xml.WriteEndElement();
375                 }
376                 xml.WriteEndElement();
377             }
378             if (exception.InnerException != null)
379             {
380                 xml.WriteStartElement(DiagnosticStrings.InnerExceptionTag);
381                 AddExceptionToTraceString(xml, exception.InnerException);
382                 xml.WriteEndElement();
383             }
384         }
385
386         protected static string StackTraceString(Exception exception)
387         {
388             string retval = exception.StackTrace;
389             if (string.IsNullOrEmpty(retval))
390             {
391                 // This means that the exception hasn't been thrown yet. We need to manufacture the stack then.
392                 StackTrace stackTrace = new StackTrace(false);
393                 // Figure out how many frames should be throw away
394                 System.Diagnostics.StackFrame[] stackFrames = stackTrace.GetFrames();
395
396                 int frameCount = 0;
397                 bool breakLoop = false;
398                 foreach (StackFrame frame in stackFrames)
399                 {
400                     string methodName = frame.GetMethod().Name;
401                     switch (methodName)
402                     {
403                         case "StackTraceString":
404                         case "AddExceptionToTraceString":
405                         case "BuildTrace":
406                         case "TraceEvent":
407                         case "TraceException":
408                         case "GetAdditionalPayload":
409                             ++frameCount;
410                             break;
411                         default:
412                             if (methodName.StartsWith("ThrowHelper", StringComparison.Ordinal))
413                             {
414                                 ++frameCount;
415                             }
416                             else
417                             {
418                                 breakLoop = true;
419                             }
420                             break;
421                     }
422                     if (breakLoop)
423                     {
424                         break;
425                     }
426                 }
427
428                 stackTrace = new StackTrace(frameCount, false);
429                 retval = stackTrace.ToString();
430             }
431             return retval;
432         }
433
434         //CSDMain:109153, Duplicate code from System.ServiceModel.Diagnostics
435         [Fx.Tag.SecurityNote(Critical = "Calls unsafe methods, UnsafeCreateEventLogger and UnsafeLogEvent.",
436             Safe = "Event identities cannot be spoofed as they are constants determined inside the method, Demands the same permission that is asserted by the unsafe method.")]
437         [SecuritySafeCritical]
438         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.SecureAsserts,
439             Justification = "Should not demand permission that is asserted by the EtwProvider ctor.")]
440         protected void LogTraceFailure(string traceString, Exception exception)
441         {
442             const int FailureBlackoutDuration = 10;
443             TimeSpan FailureBlackout = TimeSpan.FromMinutes(FailureBlackoutDuration);
444             try
445             {
446                 lock (this.thisLock)
447                 {
448                     if (DateTime.UtcNow.Subtract(this.LastFailure) >= FailureBlackout)
449                     {
450                         this.LastFailure = DateTime.UtcNow;
451 #pragma warning disable 618
452                         EventLogger logger = EventLogger.UnsafeCreateEventLogger(this.eventSourceName, this);
453 #pragma warning restore 618
454                         if (exception == null)
455                         {
456                             logger.UnsafeLogEvent(TraceEventType.Error, TracingEventLogCategory, (uint)System.Runtime.Diagnostics.EventLogEventId.FailedToTraceEvent, false,
457                                 traceString);
458                         }
459                         else
460                         {
461                             logger.UnsafeLogEvent(TraceEventType.Error, TracingEventLogCategory, (uint)System.Runtime.Diagnostics.EventLogEventId.FailedToTraceEventWithException, false,
462                                 traceString, exception.ToString());
463                         }
464                     }
465                 }
466             }
467             catch (Exception eventLoggerException)
468             {
469                 if (Fx.IsFatal(eventLoggerException))
470                 {
471                     throw;
472                 }
473             }
474         }
475
476         protected abstract void OnShutdownTracing();
477
478         void ShutdownTracing()
479         {
480             if (!this.calledShutdown)
481             {
482                 this.calledShutdown = true;
483                 try
484                 {
485                     OnShutdownTracing();
486                 }
487 #pragma warning suppress 56500 //[....]; Taken care of by FxCop
488                 catch (Exception exception)
489                 {
490                     if (Fx.IsFatal(exception))
491                     {
492                         throw;
493                     }
494
495                     //log failure
496                     LogTraceFailure(null, exception);
497                 }
498             }
499         }
500
501         protected bool CalledShutdown
502         {
503             get
504             {
505                 return this.calledShutdown;
506             }
507         }
508
509         public static Guid ActivityId
510         {
511             [Fx.Tag.SecurityNote(Critical = "gets the CorrelationManager, which does a LinkDemand for UnmanagedCode",
512                 Safe = "only uses the CM to get the ActivityId, which is not protected data, doesn't leak the CM")]
513             [SecuritySafeCritical]
514             [SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
515                 Justification = "SecuritySafeCriticial method")]
516             get
517             {
518                 object id = Trace.CorrelationManager.ActivityId;
519                 return id == null ? Guid.Empty : (Guid)id;
520             }
521
522             [Fx.Tag.SecurityNote(Critical = "gets the CorrelationManager, which does a LinkDemand for UnmanagedCode",
523                 Safe = "only uses the CM to get the ActivityId, which is not protected data, doesn't leak the CM")]
524             [SecuritySafeCritical]
525             set
526             {
527                 Trace.CorrelationManager.ActivityId = value;
528             }
529         }
530
531 #pragma warning restore 56500
532
533         protected static string LookupSeverity(TraceEventType type)
534         {
535             string s;
536             switch (type)
537             {
538                 case TraceEventType.Critical:
539                     s = "Critical";
540                     break;
541                 case TraceEventType.Error:
542                     s = "Error";
543                     break;
544                 case TraceEventType.Warning:
545                     s = "Warning";
546                     break;
547                 case TraceEventType.Information:
548                     s = "Information";
549                     break;
550                 case TraceEventType.Verbose:
551                     s = "Verbose";
552                     break;
553                 case TraceEventType.Start:
554                     s = "Start";
555                     break;
556                 case TraceEventType.Stop:
557                     s = "Stop";
558                     break;
559                 case TraceEventType.Suspend:
560                     s = "Suspend";
561                     break;
562                 case TraceEventType.Transfer:
563                     s = "Transfer";
564                     break;
565                 default:
566                     s = type.ToString();
567                     break;
568             }
569
570 #pragma warning disable 618
571             Fx.Assert(s == type.ToString(), "Return value should equal the name of the enum");
572 #pragma warning restore 618
573             return s;
574         }
575
576         public abstract bool IsEnabled();
577         public abstract void TraceEventLogEvent(TraceEventType type, TraceRecord traceRecord);
578     }
579 }