iothub/device/src/Common/ExceptionTrace.cs (139 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Azure.Devices.Client.Extensions;
#if NET451
using System.Globalization;
using System.Runtime.Versioning;
using System.Threading;
#endif
namespace Microsoft.Azure.Devices.Client
{
internal class ExceptionTrace
{
private readonly string _eventSourceName;
public ExceptionTrace(string eventSourceName)
{
_eventSourceName = eventSourceName;
}
public Exception AsError(Exception exception)
{
return TraceException<Exception>(exception, TraceEventType.Error);
}
public Exception AsInformation(Exception exception)
{
return TraceException<Exception>(exception, TraceEventType.Information);
}
public Exception AsWarning(Exception exception)
{
return TraceException<Exception>(exception, TraceEventType.Warning);
}
public Exception AsVerbose(Exception exception)
{
return TraceException<Exception>(exception, TraceEventType.Verbose);
}
public ArgumentException Argument(string paramName, string message)
{
return TraceException<ArgumentException>(new ArgumentException(message, paramName), TraceEventType.Error);
}
public ArgumentNullException ArgumentNull(string paramName)
{
return TraceException<ArgumentNullException>(new ArgumentNullException(paramName), TraceEventType.Error);
}
public ArgumentNullException ArgumentNull(string paramName, string message)
{
return TraceException<ArgumentNullException>(new ArgumentNullException(paramName, message), TraceEventType.Error);
}
public ArgumentOutOfRangeException ArgumentOutOfRange(string paramName, object actualValue, string message)
{
return TraceException(new ArgumentOutOfRangeException(paramName, actualValue, message), TraceEventType.Error);
}
// When throwing ObjectDisposedException, it is highly recommended that you use this ctor
// [C#]
// public ObjectDisposedException(string objectName, string message);
// And provide null for objectName but meaningful and relevant message for message.
// It is recommended because end user really does not care or can do anything on the disposed object, commonly an internal or private object.
public ObjectDisposedException ObjectDisposed(string message)
{
// pass in null, not disposedObject.GetType().FullName as per the above guideline
return TraceException<ObjectDisposedException>(new ObjectDisposedException(null, message), TraceEventType.Error);
}
[SuppressMessage("Usage", "CA1801:Review unused parameters", Justification = "Unused parameter catchLocation is inside of the DEBUG compilation flag.")]
public void TraceHandled(Exception exception, string catchLocation)
{
#if NET451 && DEBUG
Trace.WriteLine(string.Format(
CultureInfo.InvariantCulture,
"IotHub/TraceHandled ThreadID=\"{0}\" catchLocation=\"{1}\" exceptionType=\"{2}\" exception=\"{3}\"",
Thread.CurrentThread.ManagedThreadId,
catchLocation,
exception.GetType(),
exception.ToStringSlim()));
#endif
BreakOnException(exception);
}
#if NET451
[ResourceConsumption(ResourceScope.Process)]
#endif
[Fx.Tag.SecurityNote(Critical = "Calls 'System.Runtime.Interop.UnsafeNativeMethods.IsDebuggerPresent()' which is a P/Invoke method",
Safe = "Does not leak any resource, needed for debugging")]
public TException TraceException<TException>(TException exception, TraceEventType level)
where TException : Exception
{
if (!exception.Data.Contains(_eventSourceName))
{
// Only trace if this is the first time an exception is thrown by this ExceptionTrace/EventSource.
exception.Data[_eventSourceName] = _eventSourceName;
#if NET451
switch (level)
{
case TraceEventType.Critical:
case TraceEventType.Error:
Trace.TraceError($"An Exception is being thrown: {GetDetailsForThrownException(exception)}");
break;
case TraceEventType.Warning:
Trace.TraceWarning($"An Exception is being thrown: {GetDetailsForThrownException(exception)}");
break;
}
#endif
}
BreakOnException(exception);
return exception;
}
public static string GetDetailsForThrownException(Exception e)
{
string details = e.GetType().ToString();
#if NET451
const int maxStackFrames = 10;
// Include the current callstack (this ensures we see the Stack in case exception is not output when caught)
var stackTrace = new StackTrace();
string stackTraceString = stackTrace.ToString();
if (stackTrace.FrameCount > maxStackFrames)
{
string[] frames = stackTraceString.Split(new[] { Environment.NewLine }, maxStackFrames + 1, StringSplitOptions.RemoveEmptyEntries);
stackTraceString = string.Join(Environment.NewLine, frames, 0, maxStackFrames) + "...";
}
details += Environment.NewLine + stackTraceString;
#endif
details += Environment.NewLine + "Exception ToString:" + Environment.NewLine;
details += e.ToStringSlim();
return details;
}
[SuppressMessage("Usage", "CA1801:Review unused parameters", Justification = "Unused parameters are inside of the NET451 compilation flag.")]
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.MarkMembersAsStatic, Justification = "CSDMain #183668")]
[Fx.Tag.SecurityNote(Critical = "Calls into critical method UnsafeNativeMethods.IsDebuggerPresent and UnsafeNativeMethods.DebugBreak",
Safe = "Safe because it's a no-op in retail builds.")]
internal void BreakOnException(Exception exception)
{
#if DEBUG
if (Fx.BreakOnExceptionTypes != null)
{
foreach (Type breakType in Fx.BreakOnExceptionTypes)
{
#if NET451
if (breakType.IsAssignableFrom(exception.GetType()))
{
// This is intended to "crash" the process so that a debugger can be attached. If a managed
// debugger is already attached, it will already be able to hook these exceptions. We don't
// want to simulate an unmanaged crash (DebugBreak) in that case.
if (!Debugger.IsAttached && !UnsafeNativeMethods.IsDebuggerPresent())
{
Debugger.Launch();
}
}
#endif
}
}
#endif
}
}
}