Create Aspect for Async Methods

avistokar's Avatar

avistokar

10 Aug, 2017 02:59 PM

I would like to be able to create an aspect that would construct and log the call stack for Async methods when an exception is thrown on the background thread.
If I add CallerMemberName parameter to my Task xAsync or Task<> xAsync method, can this be referenced in the OnEntry method of the BoundryAspect.

  1. Support Staff 1 Posted by PostSharp Techn... on 11 Aug, 2017 03:11 PM

    PostSharp Technologies's Avatar

    Hello,

    You can find the parameter marked with [CallerMemberName] during the aspect's compile time initialization and store the parameter's index in the aspect's field. You can then use this index in OnEntry to access the value of that argument. The example below demonstrates this approach:

    [PSerializable]
    public class MyAspect : OnMethodBoundaryAspect
    {
        private int callerMemberNameIndex;
    
        public override bool CompileTimeValidate( MethodBase method )
        {
            ParameterInfo callerMemberNameParameter = method.GetParameters()
                .FirstOrDefault(p => p.GetCustomAttributes(typeof(CallerMemberNameAttribute)).Any());
    
            if (callerMemberNameParameter == null)
            {
                Message.Write(method, SeverityType.Error, "USR001", "Method {0}.{1} must have a parameter marked with [CallerMemberName].", method.DeclaringType, method );
                return false;
            }
    
            this.callerMemberNameIndex = callerMemberNameParameter.Position;
            return true;
        }
    
    
        public override void OnEntry(MethodExecutionArgs args)
        {
            string callerMemberName = (string) args.Arguments.GetArgument(callerMemberNameIndex);
    
            Console.WriteLine( "CallerMemberName = {0}", callerMemberName );
        }
    }
    

    You can store the current call stack in the logical call context and add and remove items as you enter and exit each method. This approach, but without using PostSharp, is described by Stephen Cleary in his blog post: http://blog.stephencleary.com/2013/04/implicit-async-context-asyncl...

    I would also suggest that adding CallerMemberName parameter to all your methods may be inconvenient. You can try to access the current method's name in the OnEntry and push it into the context. This may be enough for the purpose of exception logging.

    -alex

  2. 2 Posted by avistokar on 18 Aug, 2017 05:42 PM

    avistokar's Avatar

    very cool - using the Steven Cleary example I created this LogStack class:

        public class LogStack {
            public static string CurrentStack => string.Join(" ", CurrentContext.Reverse());
            private static readonly string name = Guid.NewGuid().ToString("N");
            private sealed class Wrapper : MarshalByRefObject {
                public ImmutableStack<string> Value { get; set; }
            }
            private static ImmutableStack<string> CurrentContext {
                get {
                    var ret = CallContext.LogicalGetData(name) as Wrapper;
                    return ret == null ? ImmutableStack.Create<string>() : ret.Value;
                }
                set => CallContext.LogicalSetData(name, new Wrapper {Value = value});
            }
            public static IDisposable Push([CallerMemberName] string context = "") {
                CurrentContext = CurrentContext.Push(context);
                return new popWhenDisposed();
            }

            public static void Pop() => CurrentContext = CurrentContext.Pop();
            private sealed class popWhenDisposed : IDisposable {
                private bool disposed;
                public void Dispose() {
                    if (disposed) return;
                    Pop();
                    disposed = true;
                }
            }
        }

    and this aspect:
        public class StackLogger : OnMethodBoundaryAspect
        {
            public override void OnEntry(MethodExecutionArgs args)
            {
                if (args.Method is MethodInfo methodInfo) {
                    var returnType = methodInfo.ReturnType;
                    if (returnType == typeof(Task) || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))) { LogStack.Push($"{methodInfo.DeclaringType?.Name} {args.Method.Name}"); }
                }
                base.OnEntry(args);
            }

            public override void OnExit(MethodExecutionArgs args) {
                if (args.Method is MethodInfo methodInfo)
                {
                    var returnType = methodInfo.ReturnType;
                    if (returnType == typeof(Task) || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))) { LogStack.Pop(); }
                }

                base.OnExit(args);
            }

            public override void OnException(MethodExecutionArgs args)
            {
                Log.Error(args.Exception, "CallStack in {methodName}", args.Method.Name, LogStack.CurrentStack);
                base.OnException(args);
            }
        }
    }

    I would like to limit this to only Async methods which I am doing at runtime using reflection. Is there a way to get this to apply to only async methods using a compile time check and/or attribute in the GlobalAspects.cs?

  3. Support Staff 3 Posted by PostSharp Techn... on 22 Aug, 2017 10:01 AM

    PostSharp Technologies's Avatar

    Hello,

    the best way is to use ReflectionExtensions.GetStateMachineKind method in the CompileTimeValidate method and return false if the result is not equal to StateMachineKind.Async. This would remove the aspect (after it was multicasted) from non-async methods.

    Best regards,
    Daniel

  4. Support Staff 4 Posted by PostSharp Techn... on 05 Sep, 2017 02:52 PM

    PostSharp Technologies's Avatar

    Hello,

    We are going to close this request as we believe it was solved. Please feel free to reopen the discussion if you need more help.

    Best regards,
    PostSharp Team

  5. PostSharp Technologies closed this discussion on 05 Sep, 2017 02:52 PM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac