|
//-----------------------------------------------------------------------------
// 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);
currentDomain.DomainUnload += new EventHandler(ExitOrUnloadEventHandler);
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);
}
}
|