//-----------------------------------------------------------------------------
//
// 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.
//
//-----------------------------------------------------------------------------
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
{
///
/// Defines default startegy for recording exception. By default class exeptions are marked as remote.
///
[Serializable]
public class DefaultExceptionSerializationStrategy : ExceptionSerializationStrategy
{
private static readonly Logger _logger = Logger.GetLogger(typeof(DefaultExceptionSerializationStrategy));
private static List _defaultExceptionClasses = new List() { typeof(AmazonServiceException)};
private List _remoteExceptionClasses = new List();
///
/// Default stack frame size for the recorded .
///
public const int DefaultStackFrameSize = 50;
///
/// The maximum stack frame size for the strategy.
///
public int MaxStackFrameSize { get; private set; } = DefaultStackFrameSize;
///
/// Initializes a new instance of the class.
///
public DefaultExceptionSerializationStrategy() : this(DefaultStackFrameSize)
{
}
///
/// Initializes instance with provided Stack frame size.
/// While setting number consider max trace size limit : https://aws.amazon.com/xray/pricing/
///
/// Integer value for stack frame size.
public DefaultExceptionSerializationStrategy(int stackFrameSize)
{
MaxStackFrameSize = GetValidStackFrameSize(stackFrameSize);
_remoteExceptionClasses.AddRange(_defaultExceptionClasses);
}
///
/// Initializes 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/
///
/// Stack frame size for the recorded exception.
/// List of for which exceptions should be marked as remote.
public DefaultExceptionSerializationStrategy(int stackFrameSize, List types)
{
MaxStackFrameSize = GetValidStackFrameSize(stackFrameSize);
_remoteExceptionClasses.AddRange(types);
_remoteExceptionClasses.AddRange(_defaultExceptionClasses);
}
///
/// Initializes instance with provided
/// list of types for which exceptions should be marked as remote.
///
/// List of for which exceptions should be marked as remote.
public DefaultExceptionSerializationStrategy(List types)
{
MaxStackFrameSize = DefaultStackFrameSize;
_remoteExceptionClasses.AddRange(types);
_remoteExceptionClasses.AddRange(_defaultExceptionClasses);
}
///
/// Validates and returns valid max stack frame size.
///
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;
}
///
/// Checks whether the exception should be marked as remote.
///
/// Instance of .
/// True if the exception is of type present in , else false.
private bool IsRemoteException(Exception e)
{
foreach (Type t in _remoteExceptionClasses)
{
Type exceptionType = e.GetType();
if (exceptionType == t || exceptionType.IsSubclassOf(t))
{
return true;
}
}
return false;
}
///
/// 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 .
///
/// The exception to be added
/// The subsegments to search for existing exception descriptor.
/// List of
public List DescribeException(Exception e, IEnumerable subsegments)
{
List result = new List();
// First check if the exception has been described in subsegment
ExceptionDescriptor ex = new ExceptionDescriptor();
IEnumerable 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;
}
}
}