verbose_proxy.py 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import functools
  4. import logging
  5. import pprint
  6. from itertools import chain
  7. import six
  8. def format_call(args, kwargs):
  9. args = (repr(a) for a in args)
  10. kwargs = ("{0!s}={1!r}".format(*item) for item in six.iteritems(kwargs))
  11. return "({0})".format(", ".join(chain(args, kwargs)))
  12. def format_return(result, max_lines):
  13. if isinstance(result, (list, tuple, set)):
  14. return "({0} with {1} items)".format(type(result).__name__, len(result))
  15. if result:
  16. lines = pprint.pformat(result).split('\n')
  17. extra = '\n...' if len(lines) > max_lines else ''
  18. return '\n'.join(lines[:max_lines]) + extra
  19. return result
  20. class VerboseProxy(object):
  21. """Proxy all function calls to another class and log method name, arguments
  22. and return values for each call.
  23. """
  24. def __init__(self, obj_name, obj, log_name=None, max_lines=10):
  25. self.obj_name = obj_name
  26. self.obj = obj
  27. self.max_lines = max_lines
  28. self.log = logging.getLogger(log_name or __name__)
  29. def __getattr__(self, name):
  30. attr = getattr(self.obj, name)
  31. if not six.callable(attr):
  32. return attr
  33. return functools.partial(self.proxy_callable, name)
  34. def proxy_callable(self, call_name, *args, **kwargs):
  35. self.log.info("%s %s <- %s",
  36. self.obj_name,
  37. call_name,
  38. format_call(args, kwargs))
  39. result = getattr(self.obj, call_name)(*args, **kwargs)
  40. self.log.info("%s %s -> %s",
  41. self.obj_name,
  42. call_name,
  43. format_return(result, self.max_lines))
  44. return result