536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
580 lines
22 KiB
C#
580 lines
22 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.Runtime.Diagnostics
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using System.Xml.XPath;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Security.Permissions;
|
|
|
|
abstract class DiagnosticTraceBase
|
|
{
|
|
//Diagnostics trace
|
|
protected const string DefaultTraceListenerName = "Default";
|
|
protected const string TraceRecordVersion = "http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord";
|
|
|
|
protected static string AppDomainFriendlyName = AppDomain.CurrentDomain.FriendlyName;
|
|
const ushort TracingEventLogCategory = 4;
|
|
|
|
object thisLock;
|
|
bool tracingEnabled = true;
|
|
bool calledShutdown;
|
|
bool haveListeners;
|
|
SourceLevels level;
|
|
protected string TraceSourceName;
|
|
TraceSource traceSource;
|
|
[Fx.Tag.SecurityNote(Critical = "This determines the event source name.")]
|
|
[SecurityCritical]
|
|
string eventSourceName;
|
|
|
|
public DiagnosticTraceBase(string traceSourceName)
|
|
{
|
|
this.thisLock = new object();
|
|
this.TraceSourceName = traceSourceName;
|
|
this.LastFailure = DateTime.MinValue;
|
|
}
|
|
|
|
protected DateTime LastFailure { get; set; }
|
|
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
|
|
Justification = "SecurityCritical method. Does not expose critical resources returned by methods with Link Demands")]
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because we are invoking TraceSource.Listeners which has a Link Demand for UnmanagedCode permission.",
|
|
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")]
|
|
[SecurityCritical]
|
|
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
|
static void UnsafeRemoveDefaultTraceListener(TraceSource traceSource)
|
|
{
|
|
traceSource.Listeners.Remove(DiagnosticTraceBase.DefaultTraceListenerName);
|
|
}
|
|
|
|
public TraceSource TraceSource
|
|
{
|
|
get
|
|
{
|
|
return this.traceSource;
|
|
}
|
|
|
|
set
|
|
{
|
|
SetTraceSource(value);
|
|
}
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
|
|
Justification = "Does not expose critical resources returned by methods with Link Demands")]
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because we are invoking TraceSource.Listeners which has a Link Demand for UnmanagedCode permission.",
|
|
Safe = "Safe because are only retrieving the count of listeners and removing the default trace listener - we aren't leaking any critical resources.")]
|
|
[SecuritySafeCritical]
|
|
protected void SetTraceSource(TraceSource traceSource)
|
|
{
|
|
if (traceSource != null)
|
|
{
|
|
UnsafeRemoveDefaultTraceListener(traceSource);
|
|
this.traceSource = traceSource;
|
|
this.haveListeners = this.traceSource.Listeners.Count > 0;
|
|
}
|
|
}
|
|
|
|
public bool HaveListeners
|
|
{
|
|
get
|
|
{
|
|
return this.haveListeners;
|
|
}
|
|
}
|
|
|
|
SourceLevels FixLevel(SourceLevels level)
|
|
{
|
|
//the bit fixing below is meant to keep the trace level legal even if somebody uses numbers in config
|
|
if (((level & ~SourceLevels.Information) & SourceLevels.Verbose) != 0)
|
|
{
|
|
level |= SourceLevels.Verbose;
|
|
}
|
|
else if (((level & ~SourceLevels.Warning) & SourceLevels.Information) != 0)
|
|
{
|
|
level |= SourceLevels.Information;
|
|
}
|
|
else if (((level & ~SourceLevels.Error) & SourceLevels.Warning) != 0)
|
|
{
|
|
level |= SourceLevels.Warning;
|
|
}
|
|
if (((level & ~SourceLevels.Critical) & SourceLevels.Error) != 0)
|
|
{
|
|
level |= SourceLevels.Error;
|
|
}
|
|
if ((level & SourceLevels.Critical) != 0)
|
|
{
|
|
level |= SourceLevels.Critical;
|
|
}
|
|
|
|
// If only the ActivityTracing flag is set, then
|
|
// we really have Off. Do not do ActivityTracing then.
|
|
if (level == SourceLevels.ActivityTracing)
|
|
{
|
|
level = SourceLevels.Off;
|
|
}
|
|
|
|
return level;
|
|
}
|
|
|
|
protected virtual void OnSetLevel(SourceLevels level)
|
|
{
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
|
|
Justification = "Does not expose critical resources returned by methods with Link Demands")]
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because we are invoking TraceSource.Listeners and SourceSwitch.Level which have Link Demands for UnmanagedCode permission.")]
|
|
[SecurityCritical]
|
|
void SetLevel(SourceLevels level)
|
|
{
|
|
SourceLevels fixedLevel = FixLevel(level);
|
|
this.level = fixedLevel;
|
|
|
|
if (this.TraceSource != null)
|
|
{
|
|
// Need this for setup from places like TransactionBridge.
|
|
this.haveListeners = this.TraceSource.Listeners.Count > 0;
|
|
OnSetLevel(level);
|
|
|
|
#pragma warning disable 618
|
|
this.tracingEnabled = this.HaveListeners && (level != SourceLevels.Off);
|
|
#pragma warning restore 618
|
|
this.TraceSource.Switch.Level = level;
|
|
}
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because we are invoking SetLevel.")]
|
|
[SecurityCritical]
|
|
void SetLevelThreadSafe(SourceLevels level)
|
|
{
|
|
lock (this.thisLock)
|
|
{
|
|
SetLevel(level);
|
|
}
|
|
}
|
|
|
|
public SourceLevels Level
|
|
{
|
|
get
|
|
{
|
|
if (this.TraceSource != null && (this.TraceSource.Switch.Level != this.level))
|
|
{
|
|
this.level = this.TraceSource.Switch.Level;
|
|
}
|
|
|
|
return this.level;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because we are invoking SetLevelTheadSafe.")]
|
|
[SecurityCritical]
|
|
set
|
|
{
|
|
SetLevelThreadSafe(value);
|
|
}
|
|
}
|
|
|
|
protected string EventSourceName
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Access critical eventSourceName field",
|
|
Safe = "Doesn't leak info\\resources")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
return this.eventSourceName;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "This determines the event source name.")]
|
|
[SecurityCritical]
|
|
set
|
|
{
|
|
this.eventSourceName = value;
|
|
}
|
|
}
|
|
|
|
public bool TracingEnabled
|
|
{
|
|
get
|
|
{
|
|
return this.tracingEnabled && this.traceSource != null;
|
|
}
|
|
}
|
|
|
|
protected static string ProcessName
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Satisfies a LinkDemand for 'PermissionSetAttribute' on type 'Process' when calling method GetCurrentProcess",
|
|
Safe = "Does not leak any resource and has been reviewed")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
string retval = null;
|
|
using (Process process = Process.GetCurrentProcess())
|
|
{
|
|
retval = process.ProcessName;
|
|
}
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
protected static int ProcessId
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Satisfies a LinkDemand for 'PermissionSetAttribute' on type 'Process' when calling method GetCurrentProcess",
|
|
Safe = "Does not leak any resource and has been reviewed")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
int retval = -1;
|
|
using (Process process = Process.GetCurrentProcess())
|
|
{
|
|
retval = process.Id;
|
|
}
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
public virtual bool ShouldTrace(TraceEventLevel level)
|
|
{
|
|
return ShouldTraceToTraceSource(level);
|
|
}
|
|
|
|
public bool ShouldTrace(TraceEventType type)
|
|
{
|
|
return this.TracingEnabled && this.HaveListeners &&
|
|
(this.TraceSource != null) &&
|
|
0 != ((int)type & (int)this.Level);
|
|
}
|
|
|
|
public bool ShouldTraceToTraceSource(TraceEventLevel level)
|
|
{
|
|
return ShouldTrace(TraceLevelHelper.GetTraceEventType(level));
|
|
}
|
|
|
|
//only used for exceptions, perf is not important
|
|
public static string XmlEncode(string text)
|
|
{
|
|
if (string.IsNullOrEmpty(text))
|
|
{
|
|
return text;
|
|
}
|
|
|
|
int len = text.Length;
|
|
StringBuilder encodedText = new StringBuilder(len + 8); //perf optimization, expecting no more than 2 > characters
|
|
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
char ch = text[i];
|
|
switch (ch)
|
|
{
|
|
case '<':
|
|
encodedText.Append("<");
|
|
break;
|
|
case '>':
|
|
encodedText.Append(">");
|
|
break;
|
|
case '&':
|
|
encodedText.Append("&");
|
|
break;
|
|
default:
|
|
encodedText.Append(ch);
|
|
break;
|
|
}
|
|
}
|
|
return encodedText.ToString();
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Sets global event handlers for the AppDomain",
|
|
Safe = "Doesn't leak resources\\Information")]
|
|
[SecuritySafeCritical]
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
|
|
Justification = "SecuritySafeCritical method, Does not expose critical resources returned by methods with Link Demands")]
|
|
protected void AddDomainEventHandlersForCleanup()
|
|
{
|
|
AppDomain currentDomain = AppDomain.CurrentDomain;
|
|
if (this.TraceSource != null)
|
|
{
|
|
this.haveListeners = this.TraceSource.Listeners.Count > 0;
|
|
}
|
|
|
|
this.tracingEnabled = this.haveListeners;
|
|
if (this.TracingEnabled)
|
|
{
|
|
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
|
|
this.SetLevel(this.TraceSource.Switch.Level);
|
|
#if MONO_FEATURE_MULTIPLE_APPDOMAINS
|
|
currentDomain.DomainUnload += new EventHandler(ExitOrUnloadEventHandler);
|
|
#endif
|
|
currentDomain.ProcessExit += new EventHandler(ExitOrUnloadEventHandler);
|
|
}
|
|
}
|
|
|
|
void ExitOrUnloadEventHandler(object sender, EventArgs e)
|
|
{
|
|
ShutdownTracing();
|
|
}
|
|
|
|
protected abstract void OnUnhandledException(Exception exception);
|
|
|
|
protected void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
|
|
{
|
|
Exception e = (Exception)args.ExceptionObject;
|
|
OnUnhandledException(e);
|
|
ShutdownTracing();
|
|
}
|
|
|
|
protected static string CreateSourceString(object source)
|
|
{
|
|
var traceSourceStringProvider = source as ITraceSourceStringProvider;
|
|
if (traceSourceStringProvider != null)
|
|
{
|
|
return traceSourceStringProvider.GetSourceString();
|
|
}
|
|
|
|
return CreateDefaultSourceString(source);
|
|
}
|
|
|
|
internal static string CreateDefaultSourceString(object source)
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException("source");
|
|
}
|
|
|
|
return String.Format(CultureInfo.CurrentCulture, "{0}/{1}", source.GetType().ToString(), source.GetHashCode());
|
|
}
|
|
|
|
protected static void AddExceptionToTraceString(XmlWriter xml, Exception exception)
|
|
{
|
|
xml.WriteElementString(DiagnosticStrings.ExceptionTypeTag, XmlEncode(exception.GetType().AssemblyQualifiedName));
|
|
xml.WriteElementString(DiagnosticStrings.MessageTag, XmlEncode(exception.Message));
|
|
xml.WriteElementString(DiagnosticStrings.StackTraceTag, XmlEncode(StackTraceString(exception)));
|
|
xml.WriteElementString(DiagnosticStrings.ExceptionStringTag, XmlEncode(exception.ToString()));
|
|
Win32Exception win32Exception = exception as Win32Exception;
|
|
if (win32Exception != null)
|
|
{
|
|
xml.WriteElementString(DiagnosticStrings.NativeErrorCodeTag, win32Exception.NativeErrorCode.ToString("X", CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
if (exception.Data != null && exception.Data.Count > 0)
|
|
{
|
|
xml.WriteStartElement(DiagnosticStrings.DataItemsTag);
|
|
foreach (object dataItem in exception.Data.Keys)
|
|
{
|
|
xml.WriteStartElement(DiagnosticStrings.DataTag);
|
|
xml.WriteElementString(DiagnosticStrings.KeyTag, XmlEncode(dataItem.ToString()));
|
|
xml.WriteElementString(DiagnosticStrings.ValueTag, XmlEncode(exception.Data[dataItem].ToString()));
|
|
xml.WriteEndElement();
|
|
}
|
|
xml.WriteEndElement();
|
|
}
|
|
if (exception.InnerException != null)
|
|
{
|
|
xml.WriteStartElement(DiagnosticStrings.InnerExceptionTag);
|
|
AddExceptionToTraceString(xml, exception.InnerException);
|
|
xml.WriteEndElement();
|
|
}
|
|
}
|
|
|
|
protected static string StackTraceString(Exception exception)
|
|
{
|
|
string retval = exception.StackTrace;
|
|
if (string.IsNullOrEmpty(retval))
|
|
{
|
|
// This means that the exception hasn't been thrown yet. We need to manufacture the stack then.
|
|
StackTrace stackTrace = new StackTrace(false);
|
|
// Figure out how many frames should be throw away
|
|
System.Diagnostics.StackFrame[] stackFrames = stackTrace.GetFrames();
|
|
|
|
int frameCount = 0;
|
|
bool breakLoop = false;
|
|
foreach (StackFrame frame in stackFrames)
|
|
{
|
|
string methodName = frame.GetMethod().Name;
|
|
switch (methodName)
|
|
{
|
|
case "StackTraceString":
|
|
case "AddExceptionToTraceString":
|
|
case "BuildTrace":
|
|
case "TraceEvent":
|
|
case "TraceException":
|
|
case "GetAdditionalPayload":
|
|
++frameCount;
|
|
break;
|
|
default:
|
|
if (methodName.StartsWith("ThrowHelper", StringComparison.Ordinal))
|
|
{
|
|
++frameCount;
|
|
}
|
|
else
|
|
{
|
|
breakLoop = true;
|
|
}
|
|
break;
|
|
}
|
|
if (breakLoop)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
stackTrace = new StackTrace(frameCount, false);
|
|
retval = stackTrace.ToString();
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
//CSDMain:109153, Duplicate code from System.ServiceModel.Diagnostics
|
|
[Fx.Tag.SecurityNote(Critical = "Calls unsafe methods, UnsafeCreateEventLogger and UnsafeLogEvent.",
|
|
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.")]
|
|
[SecuritySafeCritical]
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.SecureAsserts,
|
|
Justification = "Should not demand permission that is asserted by the EtwProvider ctor.")]
|
|
protected void LogTraceFailure(string traceString, Exception exception)
|
|
{
|
|
const int FailureBlackoutDuration = 10;
|
|
TimeSpan FailureBlackout = TimeSpan.FromMinutes(FailureBlackoutDuration);
|
|
try
|
|
{
|
|
lock (this.thisLock)
|
|
{
|
|
if (DateTime.UtcNow.Subtract(this.LastFailure) >= FailureBlackout)
|
|
{
|
|
this.LastFailure = DateTime.UtcNow;
|
|
#pragma warning disable 618
|
|
EventLogger logger = EventLogger.UnsafeCreateEventLogger(this.eventSourceName, this);
|
|
#pragma warning restore 618
|
|
if (exception == null)
|
|
{
|
|
logger.UnsafeLogEvent(TraceEventType.Error, TracingEventLogCategory, (uint)System.Runtime.Diagnostics.EventLogEventId.FailedToTraceEvent, false,
|
|
traceString);
|
|
}
|
|
else
|
|
{
|
|
logger.UnsafeLogEvent(TraceEventType.Error, TracingEventLogCategory, (uint)System.Runtime.Diagnostics.EventLogEventId.FailedToTraceEventWithException, false,
|
|
traceString, exception.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception eventLoggerException)
|
|
{
|
|
if (Fx.IsFatal(eventLoggerException))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected abstract void OnShutdownTracing();
|
|
|
|
void ShutdownTracing()
|
|
{
|
|
if (!this.calledShutdown)
|
|
{
|
|
this.calledShutdown = true;
|
|
try
|
|
{
|
|
OnShutdownTracing();
|
|
}
|
|
#pragma warning suppress 56500 //Microsoft; Taken care of by FxCop
|
|
catch (Exception exception)
|
|
{
|
|
if (Fx.IsFatal(exception))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
//log failure
|
|
LogTraceFailure(null, exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected bool CalledShutdown
|
|
{
|
|
get
|
|
{
|
|
return this.calledShutdown;
|
|
}
|
|
}
|
|
|
|
public static Guid ActivityId
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "gets the CorrelationManager, which does a LinkDemand for UnmanagedCode",
|
|
Safe = "only uses the CM to get the ActivityId, which is not protected data, doesn't leak the CM")]
|
|
[SecuritySafeCritical]
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands,
|
|
Justification = "SecuritySafeCriticial method")]
|
|
get
|
|
{
|
|
object id = Trace.CorrelationManager.ActivityId;
|
|
return id == null ? Guid.Empty : (Guid)id;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "gets the CorrelationManager, which does a LinkDemand for UnmanagedCode",
|
|
Safe = "only uses the CM to get the ActivityId, which is not protected data, doesn't leak the CM")]
|
|
[SecuritySafeCritical]
|
|
set
|
|
{
|
|
Trace.CorrelationManager.ActivityId = value;
|
|
}
|
|
}
|
|
|
|
#pragma warning restore 56500
|
|
|
|
protected static string LookupSeverity(TraceEventType type)
|
|
{
|
|
string s;
|
|
switch (type)
|
|
{
|
|
case TraceEventType.Critical:
|
|
s = "Critical";
|
|
break;
|
|
case TraceEventType.Error:
|
|
s = "Error";
|
|
break;
|
|
case TraceEventType.Warning:
|
|
s = "Warning";
|
|
break;
|
|
case TraceEventType.Information:
|
|
s = "Information";
|
|
break;
|
|
case TraceEventType.Verbose:
|
|
s = "Verbose";
|
|
break;
|
|
case TraceEventType.Start:
|
|
s = "Start";
|
|
break;
|
|
case TraceEventType.Stop:
|
|
s = "Stop";
|
|
break;
|
|
case TraceEventType.Suspend:
|
|
s = "Suspend";
|
|
break;
|
|
case TraceEventType.Transfer:
|
|
s = "Transfer";
|
|
break;
|
|
default:
|
|
s = type.ToString();
|
|
break;
|
|
}
|
|
|
|
#pragma warning disable 618
|
|
Fx.Assert(s == type.ToString(), "Return value should equal the name of the enum");
|
|
#pragma warning restore 618
|
|
return s;
|
|
}
|
|
|
|
public abstract bool IsEnabled();
|
|
public abstract void TraceEventLogEvent(TraceEventType type, TraceRecord traceRecord);
|
|
}
|
|
}
|