Showing posts with label Debugging StackTrace StackFrame C# Devops. Show all posts
Showing posts with label Debugging StackTrace StackFrame C# Devops. Show all posts

Friday 3 February 2023

Looking into a stacktrace to find a given method name

When you log exceptions, it is sometimes interesting to look after a method name in the stackTrace (the 'callstack' where the application or system is running) An example usage of helper method I wrote to find the StackFrame containing the method of name to search is shown below.
 
     StackFrameExtensions.MethodStackFrameInfo methodStackFrame = stackTrace.FindStackFrameWithMethod("service", 2, nameof(OnEntry).ToLower(), nameof(LogUnauthorizedAccessDetails).ToLower());
 
This specifies that we want to look in the stack trace after a method which is containing 'service' (case insensitive), with a given minimum frame count of at least 2 from the place you retrieve the stackTrace and ignoring the method name "OnEntry" if it is found (Case insensitive). I have used this inside Postsharp aspects. It can be helpful in code where you either use AOP or some code where you expect a method call exist with a method name containing some string with a given distance (frame count) from the location in code you retrieve the stack frames. Note that you can always obtain a StackTrace by just instantiating a new StackTrace, e.g :
 
  var stackTrace = new StackTrace(); 
 
The stack trace helper extension method then looks like this:
 
 using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace DebuggingExtensionsLib
{
    public static class StackFrameExtensions
    {
        /// <summary>
        /// Retrieves the first stack frame of interest matching having a method with a name containing the <paramref name="methodNameContaining"/>. Case-insensitive search.
        /// </summary>
        /// <param name="stackTrace"></param>
        /// <param name="methodNameContaining">Pass in the method name to search for (case insensitive match)</param>
        /// <param name="minimumFrameCount">The minimum stack frame count. Defaults to 2.</param>
        /// <param name="ignoreMethodNamesContaining">Pass in one or more method names to ignore</param>
        /// <returns></returns>
        public static MethodStackFrameInfo FindStackFrameWithMethod(this StackTrace stackTrace, string methodNameContaining, int minimumFrameCount = 2, params string[] ignoreMethodNamesContaining)
        {
            try
            {
                var stackFrames = stackTrace.GetFrames();

                if (stackFrames == null || stackFrames.Length < minimumFrameCount)
                {
                    return null;
                }

                for (int i = 0; i < stackFrames.Length; i++)
                {
                    MethodBase mi = stackFrames[i].GetMethod();
                    if (mi.ReflectedType == null)
                    {
                        continue;
                    }
                    if (ignoreMethodNamesContaining != null && ignoreMethodNamesContaining.Any())
                    {
                        if (ignoreMethodNamesContaining.Contains(mi.Name, StringComparer.CurrentCultureIgnoreCase))
                        {
                            continue;
                        }
                    }
                    // Looks like the parameter value is not possible to obtain
                    string fullMethodName = $"{mi.ReflectedType.Name}.{mi.Name}";

                    if (!fullMethodName.Contains(methodNameContaining, StringComparison.CurrentCultureIgnoreCase))
                    {
                        continue;
                    }
                   
                    var parameterDictionary = mi.GetParameters().Select(mp => new
                    {
                        ParameterName = mp.Name,
                        ParameterType = mp.ParameterType.Name
                    }).ToDictionary(x => x.ParameterName, x => x.ParameterType);

                    var stackFrameInfo = new MethodStackFrameInfo
                    {
                        MethodName = fullMethodName,
                        MethodParameters = parameterDictionary
                    };
                    return stackFrameInfo;
                }

                return null;
            }
            catch (Exception err)
            {
                Debug.WriteLine(err);
                return null;
            }
        }

        public class MethodStackFrameInfo
        {
            public string MethodName { get; set; }

            public Dictionary<string, string> MethodParameters { get; set; } = new Dictionary<string, string>();

            public override string ToString()
            {
                return $"{MethodName}({string.Join(",", MethodParameters.Select(x => x.Value + " " + x.Key).ToArray())})";
            }
        }
    }
}
 
We return here an instance of MethodStackFrameInfo, where we get the method name and also the parameters given into the method with the value and the key, this means in this context the data type and the parameter name given as method parameter. It will return text like SomeClass.SomeMethod(Int32 somearg1, System.String somearg2). Hence you can log this information in a logger to understand which method was called, up the stack with a known name. This can be practical also in systems where you have some wrapping code and the code you want to inspect if was called is some stack frames up the stack.