sdk/src/Core/Strategies/DefaultExceptionSerializationStrategy.cs (125 lines of code) (raw):
//-----------------------------------------------------------------------------
// <copyright file="DefaultExceptionSerializationStrategy.cs" company="Amazon.com">
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
// </copyright>
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.XRay.Recorder.Core.Internal.Entities;
namespace Amazon.XRay.Recorder.Core.Strategies
{
/// <summary>
/// Defines default startegy for recording exception. By default <see cref="AmazonServiceException"/> class exeptions are marked as remote.
/// </summary>
[Serializable]
public class DefaultExceptionSerializationStrategy : ExceptionSerializationStrategy
{
private static readonly Logger _logger = Logger.GetLogger(typeof(DefaultExceptionSerializationStrategy));
private static List<Type> _defaultExceptionClasses = new List<Type>() { typeof(AmazonServiceException)};
private List<Type> _remoteExceptionClasses = new List<Type>();
/// <summary>
/// Default stack frame size for the recorded <see cref="Exception"/>.
/// </summary>
public const int DefaultStackFrameSize = 50;
/// <summary>
/// The maximum stack frame size for the strategy.
/// </summary>
public int MaxStackFrameSize { get; private set; } = DefaultStackFrameSize;
/// <summary>
/// Initializes a new instance of the <see cref="_defaultExceptionClasses"/> class.
/// </summary>
public DefaultExceptionSerializationStrategy() : this(DefaultStackFrameSize)
{
}
/// <summary>
/// Initializes <see cref="DefaultExceptionSerializationStrategy"/> instance with provided Stack frame size.
/// While setting number consider max trace size limit : https://aws.amazon.com/xray/pricing/
/// </summary>
/// <param name="stackFrameSize">Integer value for stack frame size.</param>
public DefaultExceptionSerializationStrategy(int stackFrameSize)
{
MaxStackFrameSize = GetValidStackFrameSize(stackFrameSize);
_remoteExceptionClasses.AddRange(_defaultExceptionClasses);
}
/// <summary>
/// Initializes <see cref="DefaultExceptionSerializationStrategy"/> instance with provided Stack frame size and
/// list of types for which exceptions should be marked as remote.
/// While setting number consider max trace size limit : https://aws.amazon.com/xray/pricing/
/// </summary>
/// <param name="stackFrameSize">Stack frame size for the recorded exception.</param>
/// <param name="types">List of <see cref="Type"/> for which exceptions should be marked as remote.</param>
public DefaultExceptionSerializationStrategy(int stackFrameSize, List<Type> types)
{
MaxStackFrameSize = GetValidStackFrameSize(stackFrameSize);
_remoteExceptionClasses.AddRange(types);
_remoteExceptionClasses.AddRange(_defaultExceptionClasses);
}
/// <summary>
/// Initializes <see cref="DefaultExceptionSerializationStrategy"/> instance with provided
/// list of types for which exceptions should be marked as remote.
/// </summary>
/// <param name="types">List of <see cref="Type"/> for which exceptions should be marked as remote.</param>
public DefaultExceptionSerializationStrategy(List<Type> types)
{
MaxStackFrameSize = DefaultStackFrameSize;
_remoteExceptionClasses.AddRange(types);
_remoteExceptionClasses.AddRange(_defaultExceptionClasses);
}
/// <summary>
/// Validates and returns valid max stack frame size.
/// </summary>
public static int GetValidStackFrameSize(int stackFrameSize)
{
if (stackFrameSize < 0)
{
_logger.DebugFormat("Provided Stack frame size should be non-negative. Setting max stack frame size : {0}", DefaultStackFrameSize);
return DefaultStackFrameSize;
}
_logger.DebugFormat("Setting max stack frame size : {0}", stackFrameSize);
return stackFrameSize;
}
/// <summary>
/// Checks whether the exception should be marked as remote.
/// </summary>
/// <param name="e">Instance of <see cref="Exception"/>.</param>
/// <returns>True if the exception is of type present in <see cref="_remoteExceptionClasses"/>, else false.</returns>
private bool IsRemoteException(Exception e)
{
foreach (Type t in _remoteExceptionClasses)
{
Type exceptionType = e.GetType();
if (exceptionType == t || exceptionType.IsSubclassOf(t))
{
return true;
}
}
return false;
}
/// <summary>
/// Visit each node in the cause chain. For each node:
/// Determine if it has already been described in one of the child subsegments' causes. If so, link there.
/// Otherwise, describe it and add it to the Cause and returns the list of <see cref="ExceptionDescriptor"/>.
/// </summary>
/// <param name="e">The exception to be added</param>
/// <param name="subsegments">The subsegments to search for existing exception descriptor.</param>
/// <returns> List of <see cref="ExceptionDescriptor"/></returns>
public List<ExceptionDescriptor> DescribeException(Exception e, IEnumerable<Subsegment> subsegments)
{
List<ExceptionDescriptor> result = new List<ExceptionDescriptor>();
// First check if the exception has been described in subsegment
ExceptionDescriptor ex = new ExceptionDescriptor();
IEnumerable<ExceptionDescriptor> existingExceptionDescriptors = null;
if (subsegments != null)
{
existingExceptionDescriptors = subsegments.Where(subsegment => subsegment.Cause != null && subsegment.Cause.IsExceptionAdded).SelectMany(subsegment => subsegment.Cause.ExceptionDescriptors);
}
ExceptionDescriptor existingDescriptor = null;
if (existingExceptionDescriptors != null)
{
existingDescriptor = existingExceptionDescriptors.FirstOrDefault(descriptor => e.Equals(descriptor.Exception));
}
// While referencing exception from child, record id if exists or cause and return.
if (existingDescriptor != null)
{
ex.Cause = existingDescriptor.Id != null ? existingDescriptor.Id : existingDescriptor.Cause;
ex.Exception = existingDescriptor.Exception; // pass the exception of the cause so that this reference can be found if the same exception is thrown again
ex.Id = null; // setting this to null since, cause is already populated with reference to downstream exception
result.Add(ex);
return result;
}
// The exception is not described. Start describe it.
ExceptionDescriptor curDescriptor = new ExceptionDescriptor();
while (e != null)
{
curDescriptor.Exception = e;
curDescriptor.Message = e.Message;
curDescriptor.Type = e.GetType().Name;
StackFrame[] frames = new StackTrace(e, true).GetFrames();
if (frames != null && frames.Length > MaxStackFrameSize)
{
curDescriptor.Truncated = frames.Length - MaxStackFrameSize;
curDescriptor.Stack = new StackFrame[MaxStackFrameSize];
Array.Copy(frames, curDescriptor.Stack, MaxStackFrameSize);
}
else
{
curDescriptor.Stack = frames;
}
if (IsRemoteException(e))
{
curDescriptor.Remote = true;
}
result.Add(curDescriptor);
e = e.InnerException;
if (e != null)
{
// Inner exception alreay described
ExceptionDescriptor innerExceptionDescriptor = existingExceptionDescriptors != null ? existingExceptionDescriptors.FirstOrDefault(d => d.Exception.Equals(e)) : null;
if (innerExceptionDescriptor != null)
{
curDescriptor.Cause = innerExceptionDescriptor.Id;
e = null;
}
else
{
var newDescriptor = new ExceptionDescriptor();
curDescriptor.Cause = newDescriptor.Id;
curDescriptor = newDescriptor;
}
}
}
return result;
}
}
}