1

I want to be able to put an attribute on a method and have that method inspect the method parameters and the return value. Pseudo code.

[AttributeUsage(AttributeTargets.Method)]
class MyAttribute: Attribute {
   public void MethodEnter(MethodInfo info) {
      foreach (var param in info.MethodParameters {
        Console.WriteLine(param.ToString());   
      }
   }

   public void MethodLeave(MethodInfo info) {
      Console.WriteLine(info.ReturnValue);   
   }
}

public class MyClass {
  [MyAttribute]
  public SomeType foo(Order order, List<OrderLine> orderLines) {
     ...
  }
}

Is this possible?

Basically, I want to be able to log everything that comes in and leaves the function. I've done this previously with PostSharp, but it seems like an overkill to use it for logging.

AngryHacker
  • 59,598
  • 102
  • 325
  • 594

2 Answers2

1

You basicaly looking for AOP (Aspect Oriented Programming) solution. PostSharp uses IL injection in your binaries after compilation and in my opinion, this is worst framework I worked with.

Instead of looking into some very complex solution I recommend you to decorate your classes. For example:

public MyClass : IMyClass
{
    public object[] MyMethod(object[] args){...}
}

public LoggerDecoratedMyClass : IMyClass
{
    private readonly IMyClass _inner;
    private readonly ILogger _logger;
    public LoggerDecoratedMyClass(IMyClass inner, ILogger logger)
    {
        _inner = inner;
        _logger = logger;
    } 

    public object[] MyMethod(object[] args)
    {
        try
        {
            var result = _inner.MyMethod(args);
            _logger.LogSuccess(...);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(..., ex);
            throw;
        }
    }
}

This looks a lot better than attribute bindings, and provide you with ability to control your Aspect dependecies. Also, it forces you to write interface oriented code.

UPDATE

Also, I often use scope logging:

internal struct ScopeLogger : IDisposable
{
    private readonly string _name;
    public ScopeLogger(ILogger logger, string scopeName, object[] args)
    {
        _name = scopeName;
        _logger = logger;
        _logger.LogInfo("Begin {name}: {args}", _name, args);
    }
    public void Dispose()
    {
        _logger.LogInfo("End {name}",_name);
    }
}

public static IDisposable LogScope(this ILogger logger, string name, params object[] args)
{
    return new ScopeLogger(logger, name, args);
}

And simply use it like this:

public LoggerDecoratedMyClass : IMyClass
{
    private readonly IMyClass _inner;
    private readonly ILogger _logger;
    public LoggerDecoratedMyClass(IMyClass inner, ILogger logger)
    {
        _inner = inner;
        _logger = logger;
    } 

    public object[] MyMethod(object[] args)
    {
        using(_logger.LogScope(nameof(MyMethod), args))
        {
            return _inner.MyMethod(args);
        }
    }
}

Decorators this way looks a lot better and shorter.

eocron
  • 6,885
  • 1
  • 21
  • 50
  • I agree that PostSharp is not the easiest to work with (particularly in the way it integrates with Visual Studio - I heard its better now), but the approach of "set it and forget it" appeals to me. I just decorate the class or method and don't have to worry about it anymore. The case that u present gives you more control, sure, but then you pollute your methods with Log.Success and Log.Fail and all that fun stuff. – AngryHacker Jul 04 '17 at 18:59
  • Well, I can't say you are not right. It is easy yes, but it doesn't guarantie you will not pollute your code, and it will come with price - you will be forced to instantiate your dependencies in static. Which at some point in time can cause you a lot of trouble. For example, configuring test environment. – eocron Jul 04 '17 at 19:07
1

You could create an aspect by making your class derive from ContextBoundObject and your attribute from ContextAttribute. By reading this article I made the following sample that meet your requirements:

First, our Foo class derived from ContextBoundObject, decorated with our custom class attribute:

 [Inpectable]
    internal class Foo : ContextBoundObject
    {
        [InspectableProperty]
        public int DoSomething(int a)
        {
            Console.WriteLine("I am doing something");
            a += 1;
            return a;
        }

        public void IamNotLogged()
        {
            Console.WriteLine("Lol");
        }

        [InspectableProperty]
        public string IamLoggedToo()
        {
            var msg = "Lol too";
            Console.WriteLine(msg);
            return msg;
        }
    }

Now the Inspectable attribute

[AttributeUsage(AttributeTargets.Class)]
    public class Inpectable : ContextAttribute
    {
        public Inpectable() : base("Inspectable")
        {
        }

        public override void GetPropertiesForNewContext(IConstructionCallMessage ccm)
        {
            ccm.ContextProperties.Add(new InspectorProperty());
        }
    }

The InspectablePropertyAttribute which will only be used to identify those methods that should be logged:

[AttributeUsage(AttributeTargets.Method)]
    public class InspectableProperty : Attribute
    {
    }

The InspectorProperty, which will capture the context of the instance and intercept the messages passing them to our aspect

public class InspectorProperty : IContextProperty,
    IContributeObjectSink
{
    public bool IsNewContextOK(Context newCtx) => true;

    public void Freeze(Context newContext)
    {
    }

    public string Name { get; } = "LOL";

    public IMessageSink GetObjectSink(MarshalByRefObject o,IMessageSink next) => new InspectorAspect(next);
}

And where the magic works, the implementation of our InspectorAspect:

internal class InspectorAspect : IMessageSink
    {
        internal InspectorAspect(IMessageSink next)
        {
            NextSink = next;
        }

        public IMessageSink NextSink { get; }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            if (!(msg is IMethodMessage)) return NextSink.SyncProcessMessage(msg);

            var call = (IMethodMessage) msg;
            var type = Type.GetType(call.TypeName);
            if (type == null) return NextSink.SyncProcessMessage(msg);

            var methodInfo = type.GetMethod(call.MethodName);
            if (!Attribute.IsDefined(methodInfo, typeof (InspectableProperty)))
                return NextSink.SyncProcessMessage(msg);
            Console.WriteLine($"Entering method: {call.MethodName}. Args being:");
            foreach (var arg in call.Args)
                Console.WriteLine(arg);
            var returnMethod = NextSink.SyncProcessMessage(msg) as IMethodReturnMessage;

            Console.WriteLine($"Method {call.MethodName} returned: {returnMethod?.ReturnValue}");
            Console.WriteLine();
            return returnMethod;
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            throw new InvalidOperationException();
        }
    }

Inside the SyncProcessMessage theInspectorAspect receives all the messages regarding the context (Foo), so we just filter those that are instances of IMethodMessage and then only those belonging to methods decorated with InspectableProperty attribute.

This may not be a ready-to-production solution, but I think that puts you in the right path to research for more info.

Finally testing:

 private static void Main(string[] args)
        {
            var foo = new Foo();
            foo.DoSomething(1);
            foo.IamNotLogged();
            foo.IamLoggedToo();
            Console.ReadLine();
        }

The output:

enter image description here

EDIT>>>

These are the namespaces needed:

  • using System.Runtime.Remoting.Activation;
  • using System.Runtime.Remoting.Contexts;
  • using System.Runtime.Remoting.Messaging;
taquion
  • 2,667
  • 2
  • 18
  • 29
  • This definitely gets me started. Thank you. Any idea if there is a significant performance hit? – AngryHacker Jul 05 '17 at 02:11
  • I would think that for this particular case there should not be a significant performance hit, since you are not using MarshalByRefObject to perform RPC or to be used over the wire. Since now all your methods (at least those decorated) are proxied, they are gonna take a little more time to execute, not so much. Nevertheless I would perform a performance check just to be sure. Check https://stackoverflow.com/questions/6503380/how-expensive-is-using-marshalbyrefobject-compared-to-serialization – taquion Jul 05 '17 at 02:50