From 58431e7019478dcd1a89e9a6ed415a56cbab56da Mon Sep 17 00:00:00 2001 Message-Id: <58431e7019478dcd1a89e9a6ed415a56cbab56da.1542120990.git.hahn@univention.de> From: Philipp Hahn Date: Fri, 9 Nov 2018 16:24:46 +0100 Subject: [PATCH] Bug #43422 debug: logging function decorator Organization: Univention GmbH, Bremen, Germany --- base/univention-debug/python/debug.py | 96 ++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/base/univention-debug/python/debug.py b/base/univention-debug/python/debug.py index d50731dc2c..17afd63fea 100644 --- a/base/univention-debug/python/debug.py +++ b/base/univention-debug/python/debug.py @@ -34,25 +34,50 @@ example: ->>> import univention.debug as ud ->>> ud.init("stdout", ud.NO_FLUSH, ud.FUNCTION) #doctest: +ELLIPSIS +>>> f = init('stdout', NO_FLUSH, FUNCTION) #doctest: +ELLIPSIS ... ... DEBUG_INIT ->>> ud.set_level(ud.LISTENER, ud.ERROR) ->>> ud.debug(ud.LISTENER, ud.ERROR, 'Fatal error: var=%s' % 42) #doctest: +ELLIPSIS -... ... LISTENER ( ERROR ) : Fatal error: var=42 +>>> set_level(LISTENER, ERROR) """ -import _debug -from _debug import * +from __future__ import absolute_import +from sys import exc_info +from functools import wraps +from itertools import chain +from warnings import warn +from univention import _debug +from univention._debug import * # noqa F403 def debug(id, level, ustring, utf8=True): + """ + Log message 'ustring' of severity 'level' to facility 'id'. + + :param int id: ID of the category, e.g. MAIN, LDAP, USERS, ... + :param int level: Level of logging, e.g. ERROR, WARN, PROCESS, INFO, ALL + :param str ustring: The message to log. + :param bool utf8: Assume the message is UTF-8 encoded. + + >>> debug(LISTENER, ERROR, 'Fatal error: var=%s' % 42) #doctest: +ELLIPSIS + ... ... LISTENER ( ERROR ) : Fatal error: var=42 + """ _debug.debug(id, level, ustring) class function: + """ + Log function call begin and end. + + Deprecated in favor of :py:class:`trace`. + + >>> def my_func(agr1, agr2=None): + ... _d = function('my_func(...)') + ... return 'yes' + >>> my_func(42) + 'yes' + """ def __init__(self, text, utf8=True): + warn('univention.debug.function is deprecated and will be removed with UCS-5', PendingDeprecationWarning) self.text = text _debug.begin(self.text) @@ -60,6 +85,63 @@ class function: _debug.end(self.text) +def trace(with_args=True, with_return=False, repr=object.__repr__): + """ + Log function call, optional with arguments and result. + + :param bool with_args: Log function arguments. + :param bool with_return: Log function result. + :param repr: Function accepting a single object and returing a string representation for the given object. Defaults to :py:func:`object.__repr__`, alternative :py:func:`repr`. + + >>> @trace(with_args=True, with_return=True) + ... def my_func(arg1, arg2=None): + ... return 'yes' + >>> my_func(42) + 'yes' + >>> class MyClass(object): + ... @trace(with_args=True, with_return=True, repr=repr) + ... def my_meth(self, arg1, arg2=None): + ... return 'yes' + >>> MyClass().my_meth(42) + 'yes' + >>> @trace() + ... def my_bug(): + ... 1 / 0 + >>> my_bug() + Traceback (most recent call last): + ... + ZeroDivisionError: integer division or modulo by zero + """ + def decorator(f): + @wraps(f) + def wrapper(*args, **kwargs): + fname = '%s.%s' % (f.__module__, f.__name__) + _args = ', '.join( + chain( + (repr(arg) for arg in args), + ('%s=%s' % (k, repr(v)) for (k, v) in kwargs.iteritems()), + ) + ) if with_args else '...' + + _debug.begin('%s(%s): ...' % (fname, _args)) + try: + ret = f(*args, **kwargs) + except: + try: + (exctype, value) = exc_info()[:2] + _debug.end('%s(...): %s(%s)' % (fname, exctype, value)) + finally: + exctype = value = None + raise + else: + _debug.end('%s(...): %s' % (fname, repr(ret) if with_return else '...')) + return ret + + return wrapper + + return decorator + + if __name__ == '__main__': import doctest doctest.testmod() -- 2.11.0