5089c872773064f438db9e5fcc69dcfa6105236e
[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                 currentDomain.DomainUnload += new EventHandler(ExitOrUnloadEventHandler);
313                 currentDomain.ProcessExit += new EventHandler(ExitOrUnloadEventHandler);
314             }
315         }
316
317         void ExitOrUnloadEventHandler(object sender, EventArgs e)
318         {
319             ShutdownTracing();
320         }
321
322         protected abstract void OnUnhandledException(Exception exception);
323
324         protected void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
325         {
326             Exception e = (Exception)args.ExceptionObject;
327             OnUnhandledException(e);
328             ShutdownTracing();
329         }
330
331         protected static string CreateSourceString(object source)
332         {
333             var traceSourceStringProvider = source as ITraceSourceStringProvider;
334             if (traceSourceStringProvider != null)
335             {
336                 return traceSourceStringProvider.GetSourceString();
337             }
338
339             return CreateDefaultSourceString(source);
340         }
341
342         internal static string CreateDefaultSourceString(object source)
343         {
344             if (source == null)
345             {
346                 throw new ArgumentNullException("source");
347             }
348
349             return String.Format(CultureInfo.CurrentCulture, "{0}/{1}", source.GetType().ToString(), source.GetHashCode());
350         }
351
352         protected static void AddExceptionToTraceString(XmlWriter xml, Exception exception)
353         {
354             xml.WriteElementString(DiagnosticStrings.ExceptionTypeTag, XmlEncode(exception.GetType().AssemblyQualifiedName));
355             xml.WriteElementString(DiagnosticStrings.MessageTag, XmlEncode(exception.Message));
356             xml.WriteElementString(DiagnosticStrings.StackTraceTag, XmlEncode(StackTraceString(exception)));
357             xml.WriteElementString(DiagnosticStrings.ExceptionStringTag, XmlEncode(exception.ToString()));
358             Win32Exception win32Exception = exception as Win32Exception;
359             if (win32Exception != null)
360             {
361                 xml.WriteElementString(DiagnosticStrings.NativeErrorCodeTag, win32Exception.NativeErrorCode.ToString("X", CultureInfo.InvariantCulture));
362             }
363
364             if (exception.Data != null && exception.Data.Count > 0)
365             {
366                 xml.WriteStartElement(DiagnosticStrings.DataItemsTag);
367                 foreach (object dataItem in exception.Data.Keys)
368                 {
369                     xml.WriteStartElement(DiagnosticStrings.DataTag);
370                     xml.WriteElementString(DiagnosticStrings.KeyTag, XmlEncode(dataItem.ToString()));
371                     xml.WriteElementString(DiagnosticStrings.ValueTag, XmlEncode(exception.Data[dataItem].ToString()));
372                     xml.WriteEndElement();
373                 }
374                 xml.WriteEndElement();
375             }
376             if (exception.InnerException != null)
377             {
378                 xml.WriteStartElement(DiagnosticStrings.InnerExceptionTag);
379                 AddExceptionToTraceString(xml, exception.InnerException);
380                 xml.WriteEndElement();
381             }
382         }
383
384         protected static string StackTraceString(Exception exception)
385         {
386             string retval = exception.StackTrace;
387             if (string.IsNullOrEmpty(retval))
388             {
389                 // This means that the exception hasn't been thrown yet. We need to manufacture the stack then.
390                 StackTrace stackTrace = new StackTrace(false);
391                 // Figure out how many frames should be throw away
392                 System.Diagnostics.StackFrame[] stackFrames = stackTrace.GetFrames();
393
394                 int frameCount = 0;
395                 bool breakLoop = false;
396                 foreach (StackFrame frame in stackFrames)
397                 {
398                     string methodName = frame.GetMethod().Name;
399                     switch (methodName)
400                     {
401                         case "StackTraceString":
402                         case "AddExceptionToTraceString":
403                         case "BuildTrace":
404                         case "TraceEvent":
405                         case "TraceException":
406                         case "GetAdditionalPayload":
407                             ++frameCount;
408                             break;
409                         default:
410                             if (methodName.StartsWith("ThrowHelper", StringComparison.Ordinal))
411                             {
412                                 ++frameCount;
413                             }
414                             else
415                             {
416                                 breakLoop = true;
417                             }
418                             break;
419                     }
420                     if (breakLoop)
421                     {
422                         break;
423                     }
424                 }
425
426                 stackTrace = new StackTrace(frameCount, false);
427                 retval = stackTrace.ToString();
428             }
429             return retval;
430         }
431
432         //CSDMain:109153, Duplicate code from System.ServiceModel.Diagnostics
433         [Fx.Tag.SecurityNote(Critical = "Calls unsafe methods, UnsafeCreateEventLogger and UnsafeLogEvent.",
434             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.")]
435         [SecuritySafeCritical]
436         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.SecureAsserts,
437             Justification = "Should not demand permission that is asserted by the EtwProvider ctor.")]
438         protected void LogTraceFailure(string traceString, Exception exception)
439         {
440             const int FailureBlackoutDuration = 10;
441             TimeSpan FailureBlackout = TimeSpan.FromMinutes(FailureBlackoutDuration);
442             try
443             {
444                 lock (this.thisLock)
445                 {
446                     if (DateTime.UtcNow.Subtract(this.LastFailure) >= FailureBlackout)
447                     {
448                         this.LastFailure = DateTime.UtcNow;
449 #pragma warning disable 618
450                         EventLogger logger = EventLogger.UnsafeCreateEventLogger(this.eventSourceName, this);
451 #pragma warning restore 618
452                         if (exception == null)
453                         {
454                             logger.UnsafeLogEvent(TraceEventType.Error, TracingEventLogCategory, (uint)System.Runtime.Diagnostics.EventLogEventId.FailedToTraceEvent, false,
455                                 traceString);
456                         }
457                         else
458                         {
459                             logger.UnsafeLogEvent(TraceEventType.Error, TracingEventLogCategory, (uint)System.Runtime.Diagnostics.EventLogEventId.FailedToTraceEventWithException, false,
460                                 traceString, exception.ToString());
461                         }
462                     }
463                 }
464             }
465             catch (Exception eventLoggerException)
466             {
467                 if (Fx.IsFatal(eventLoggerException))
468                 {
469                     throw;
470                 }
471             }
472         }
473
474         protected abstract void OnShutdownTracing();
475
476         void ShutdownTracing()
477         {
478             if (!this.calledShutdown)
479             {
480                 this.calledShutdown = true;
481                 try
482                 {
483                     OnShutdownTracing();
484                 }
485 #pragma warning suppress 56500 //[....]; Taken care of by FxCop
486                 catch (Exception exception)
487                 {
488                     if (Fx.IsFatal(exception))
489                     {
490                         throw;
491                     }
492
493                     //log failure
494                     LogTraceFailure(null, exception);
495                 }
496             }
497         }
498
499         protected bool CalledShutdown
500         {
501             get
502             {
503                 return this.calledShutdown;
504             }
505         }
506
507         public static Guid ActivityId
508         {
509             [Fx.Tag.SecurityNote(Critical = "gets the CorrelationManager, which does a LinkDemand for UnmanagedCode",
510                 Safe = "only uses the CM to get the ActivityId, which is not protected data, doesn't leak the CM")]
511             [SecuritySafeCritical]
512             [SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
513                 Justification = "SecuritySafeCriticial method")]
514             get
515             {
516                 object id = Trace.CorrelationManager.ActivityId;
517                 return id == null ? Guid.Empty : (Guid)id;
518             }
519
520             [Fx.Tag.SecurityNote(Critical = "gets the CorrelationManager, which does a LinkDemand for UnmanagedCode",
521                 Safe = "only uses the CM to get the ActivityId, which is not protected data, doesn't leak the CM")]
522             [SecuritySafeCritical]
523             set
524             {
525                 Trace.CorrelationManager.ActivityId = value;
526             }
527         }
528
529 #pragma warning restore 56500
530
531         protected static string LookupSeverity(TraceEventType type)
532         {
533             string s;
534             switch (type)
535             {
536                 case TraceEventType.Critical:
537                     s = "Critical";
538                     break;
539                 case TraceEventType.Error:
540                     s = "Error";
541                     break;
542                 case TraceEventType.Warning:
543                     s = "Warning";
544                     break;
545                 case TraceEventType.Information:
546                     s = "Information";
547                     break;
548                 case TraceEventType.Verbose:
549                     s = "Verbose";
550                     break;
551                 case TraceEventType.Start:
552                     s = "Start";
553                     break;
554                 case TraceEventType.Stop:
555                     s = "Stop";
556                     break;
557                 case TraceEventType.Suspend:
558                     s = "Suspend";
559                     break;
560                 case TraceEventType.Transfer:
561                     s = "Transfer";
562                     break;
563                 default:
564                     s = type.ToString();
565                     break;
566             }
567
568 #pragma warning disable 618
569             Fx.Assert(s == type.ToString(), "Return value should equal the name of the enum");
570 #pragma warning restore 618
571             return s;
572         }
573
574         public abstract bool IsEnabled();
575         public abstract void TraceEventLogEvent(TraceEventType type, TraceRecord traceRecord);
576     }
577 }