59

I'm implementing a RESTful web service in python and would like to add some QOS logging functionality by intercepting function calls and logging their execution time and so on.

Basically i thought of a class from which all other services can inherit, that automatically overrides the default method implementations and wraps them in a logger function. What's the best way to achieve this?

2 Answers2

74

Something like this? This implictly adds a decorator to your method (you can also make an explicit decorator based on this if you prefer that):

class Foo(object):
    def __getattribute__(self,name):
        attr = object.__getattribute__(self, name)
        if hasattr(attr, '__call__'):
            def newfunc(*args, **kwargs):
                print('before calling %s' %attr.__name__)
                result = attr(*args, **kwargs)
                print('done calling %s' %attr.__name__)
                return result
            return newfunc
        else:
            return attr

when you now try something like:

class Bar(Foo):
    def myFunc(self, data):
        print("myFunc: %s"% data)

bar = Bar()
bar.myFunc(5)

You'll get:

before calling myFunc
myFunc:  5
done calling myFunc
KillianDS
  • 16,936
  • 4
  • 61
  • 70
  • This code is kind of odd in that if `attr` doesn't have a `__call__` attribute (which, incidentally, is slightly different than not being callable), this gives a `NameError` rather than returning the attribute. Also, `attr.__call__(*args, **kwargs)` is usually spelled `attr(*args, **kwargs)`. – Mike Graham Apr 24 '10 at 13:28
  • perfect, just what i need. i had done something similar, but in the init method and somehow messed it up but your approach worked like a charm. thanks. –  Apr 24 '10 at 13:34
  • @Mike: indeed, if forgot the else clause. I was mixing python2.6 and 3 btw, hence not using callable built-in. In 3, I do not really know a more straightforward way to check this. – KillianDS Apr 24 '10 at 13:38
  • calling it would be more accurate but, if there are side effects, problematic. – Mike Graham Apr 24 '10 at 15:09
  • 2
    Shouldn't it be retval = attr(*args, **kwargs) ... return retval in case the method returns something? – Dustin Boswell Aug 02 '11 at 22:50
  • @Dustin: Correct and edited, I am actually surprised this answer got upvotes for more then a year and this got only noticed now, thank you. – KillianDS Aug 03 '11 at 06:13
  • 2
    If `Foo` does not directly inherit from `object` then you'll probably want to call `super(Foo, self).__getattribute__` instead of `object.__getattribute__` so that the inheritance chain is respected. – Florian Brucker Nov 14 '12 at 12:48
  • 2
    @FlorianBrucker: the use of `super` and understanding MRO correctly is tricky at best, I prefer providing the explicit base class, certainly in examples as this. – KillianDS Nov 14 '12 at 20:09
  • 1
    @KillianDS I am firmly with FlorianBrucker on this one, object.__getattribute__ is not pythonic and increases confusion. – Jay Taylor Feb 07 '13 at 21:47
  • 2
    @Jay what is unpythonic about it? What about 'explicit is better than implicit'? – KillianDS Feb 18 '13 at 17:53
  • I believe that the pythonic way is the clearest way -- and jumping around the class's model to directly invoke a static method in the `object` class is certainly confusing since it undermines the standard super(clazz, self). call (and this works fine in the above example). Ultimately it comes down to a matter of opinion, experience, and taste, so there is no truly definitive answer here. – Jay Taylor Feb 20 '13 at 05:09
  • Using `hasattr(x, '__call__')` to test if `x` is callable is unreliable. If `x` implements custom `__getattr__` or its `__call__` is itself not callable, you might get misleading results. Use `callable(x)` for consistent results. (from bandit) – Sepehr GH Nov 21 '22 at 08:40
  • What if `attr` has attributes of its own, such as if it is itself a class? Or a method decorated with `lru_cache`, that should have a `cache_clear` attribute. Those will be missing on `new_func`, if I'm not mistaken. – LoneCodeRanger Jul 24 '23 at 18:51
  • Actually this might be fixable by decorating `new_func` with `functools.wraps`, but [it won't by default copy all the attributes from the original function](https://docs.python.org/3/library/functools.html#functools.update_wrapper). I think the general idea is there but the fine details of how to do it might be out of scope for this question. – LoneCodeRanger Jul 24 '23 at 19:07
5

What if you write a decorator on each functions ? Here is an example on python's wiki.

Do you use any web framework for doing your webservice ? Or are you doing everything by hand ?

dzen
  • 6,923
  • 5
  • 28
  • 31
  • 3
    It's an example, to understand how it works. An Example by definition is not something magical – dzen Apr 24 '10 at 17:08