Search code examples
c#.netunity-interception

How can I tracing method call following the ordering?


I would like to tracing the method calls following the order. Because the logging will be applied on the production so I do not want to modify my code so much.

So far, I would like to apply an interceptor on Unity to tracing the method call, I almost done. However, the log printing out is not what I am expecting. All of I need are:

From peusedo code as below:

  void method1()
  {
     call method2();
  }

The tracing log should be format as below:

Entering Method call 1/ Entering Method call 2

Leaving Method call 2 / Leaving Method call 1

BTW, my application running on .Net framework version 4.

The program as below:

    static void Main(string[] args)
    {
        var container = new UnityContainer();
        container.RegisterType<IInterface1, Implementation1>();
        container.RegisterType<IInterface2, Implementation2>();
        container.RegisterInstance(typeof (IUnityContainer), container);
        container.AddNewExtension<Interception>();

        container.Configure<Interception>()
        .SetInterceptorFor<IInterface1>(new InterfaceInterceptor());

        container.Configure<Interception>()
        .SetInterceptorFor<IInterface2>(new InterfaceInterceptor());

        var service = container.Resolve<IInterface1>();
        var results = service.GetListCustomerIdByName("abc");

        Console.ReadLine();
     }

public interface IInterface1
    {
        [Trace]
        IEnumerable<Guid> GetListCustomerIdByName(string name);
    }

    public class Implementation1 : IInterface1
    {
        private readonly IInterface2 _impl;

        public Implementation1(IUnityContainer container)
        {
            _impl = container.Resolve<IInterface2>();
        }

        public IEnumerable<Guid> GetListCustomerIdByName(string name)
        {
            return _impl.GetListCustomerIdByName(name);
        }
    }

public interface IInterface2
{
    [Trace]
    IEnumerable<Guid> GetListCustomerIdByName(string name);
}

public class Implementation2 : IInterface2
{
    public IEnumerable<Guid> GetListCustomerIdByName(string name)
    {
        yield return Guid.NewGuid();
    }
}


public class TraceAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return new TraceCallHandler();
        }
    }

public class TraceCallHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input, 
   GetNextHandlerDelegate getNext)
    {
        Console.WriteLine("Entering " + input.MethodBase.Name +"()");

        InvokeHandlerDelegate next = getNext();

        IMethodReturn result = next(input, getNext);

        string returnValueStr = 
        result.ReturnValue == null ? "(void)" : result.ReturnValue.ToString();

        if (result.Exception != null)
        {
            Console.WriteLine("Exception: {0}", result.Exception);
        }

        Console.WriteLine("Leaving 
        " +input.MethodBase.Name + "() Return Value: [" + returnValueStr + "]");

        return result;
    }

    public int Order { get; set; }
}

Sorry for my code. It is quite long.


Solution

  • Here is a class provides that functionality:

    class Scope : IDisposable
    {
        Action<string> _logger;
        string _memberName;
    
        public Scope(Action<string> logger, [CallerMemberName] string memberName = "N/A")
        {
            if (logger == null) throw new ArgumentNullException();
    
            _logger = logger;
            _memberName = memberName;
    
            _logger(string.Format("Entered {0}", _memberName));
        }
    
        public void Dispose()
        {
            _logger(string.Format("Exited {0}", _memberName));
        }
    }
    

    And to use it:

    static void MyMethod()
    {
        using (new Scope(s => Console.WriteLine(s)))
        {
            // boddy
        }
    }
    

    Note: If you use previous versions of C# that does not support [CallerMemberName] attribute, you have to provide memberName explicitly.

    Also you can use this helper class that I use myself in version prior to .NET 4.5:

    public static class Here
    {
        public static string Spot(HereOptions options)
        {
            return MakeMessageInternals(options, 3);
        }
    
        public static string Type
        {
            get
            {
                return MakeMessageInternals(HereOptions.Type, 3);
            }
        }
    
        public static string Member
        {
            get
            {
                return MakeMessageInternals(HereOptions.Member, 3);
            }
        }
    
        public static string FileLine
        {
            get
            {
                return MakeMessageInternals(HereOptions.FileLine, 3);
            }
        }
    
        public static string FileName
        {
            get
            {
                return MakeMessageInternals(HereOptions.FileName, 3);
            }
        }
    
        static StackFrame GetCaller(int index) { return new StackTrace(true).GetFrame(index); }
    
        static string MakeMessageInternals(HereOptions options, int index)
        {
            StackFrame first = null;
            var _FileName = new StringBuilder();
            var _FileLine = new StringBuilder();
            var _Member = new StringBuilder();
            var _Type = new StringBuilder();
    
            if ((options & HereOptions.FileName) == HereOptions.FileName)
            {
                first = GetCaller(index);
                if (first != null)
                {
                    var fn = first.GetFileName();
                    if (!string.IsNullOrEmpty(fn))
                        _FileName.Append(" " + Path.GetFileName(fn));
                }
            }
    
            if ((options & HereOptions.FileLine) == HereOptions.FileLine)
            {
                if (first == null)
                    first = GetCaller(index);
                if (first != null)
                {
                    var ln = first.GetFileLineNumber();
                    _FileLine.Append(" #" + ln);
                }
            }
    
            if ((options & HereOptions.Member) == HereOptions.Member)
            {
                if (first == null)
                    first = GetCaller(index);
                if (first != null)
                {
                    var mn = first.GetMethod().Name;
                    _Member.Append(" " + mn);
                }
            }
    
            if ((options & HereOptions.Type) == HereOptions.Type)
            {
                if (first == null)
                    first = GetCaller(index);
                if (first != null)
                {
                    var dt = first.GetMethod().DeclaringType;
                    _Type.Append(string.Format(" ({0})", dt));
                }
            }
    
            var messageInternal = string.Format("{0}{1}{2}{3}",
                _FileName,
                _FileLine,
                _Member,
                _Type).Trim();
    
            return messageInternal;
        }
    }
    
    [Flags]
    public enum HereOptions : byte
    {
        None = 0,
        FileName = 1,
        FileLine = 2,
        Member = 4,
        Type = 8,
        All = 15
    }