Source code for aspectlib.debug

import logging
import os
import string
import sys
from itertools import islice

from aspectlib import Aspect
from aspectlib import mimic

try:
    from types import InstanceType
except ImportError:
    InstanceType = type(None)

logger = logging.getLogger(__name__)


[docs]def frame_iterator(frame): """ Yields frames till there are no more. """ while frame: yield frame frame = frame.f_back
[docs]def format_stack(skip=0, length=6, _sep=os.path.sep): """ Returns a one-line string with the current callstack. """ return ' < '.join("%s:%s:%s" % ( '/'.join(f.f_code.co_filename.split(_sep)[-2:]), f.f_lineno, f.f_code.co_name ) for f in islice(frame_iterator(sys._getframe(1 + skip)), length))
PRINTABLE = string.digits + string.ascii_letters + string.punctuation + ' ' ASCII_ONLY = ''.join(i if i in PRINTABLE else '.' for i in (chr(c) for c in range(256)))
[docs]def strip_non_ascii(val): """ Convert to string (using `str`) and replace non-ascii characters with a dot (``.``). """ return str(val).translate(ASCII_ONLY)
[docs]def log(func=None, stacktrace=10, stacktrace_align=60, attributes=(), module=True, call=True, call_args=True, call_args_repr=repr, result=True, exception=True, exception_repr=repr, result_repr=strip_non_ascii, use_logging='CRITICAL', print_to=None): """ Decorates `func` to have logging. Args func (function): Function to decorate. If missing log returns a partial which you can use as a decorator. stacktrace (int): Number of frames to show. stacktrace_align (int): Column to align the framelist to. attributes (list): List of instance attributes to show, in case the function is a instance method. module (bool): Show the module. call (bool): If ``True``, then show calls. If ``False`` only show the call details on exceptions (if ``exception`` is enabled) (default: ``True``) call_args (bool): If ``True``, then show call arguments. (default: ``True``) call_args_repr (bool): Function to convert one argument to a string. (default: ``repr``) result (bool): If ``True``, then show result. (default: ``True``) exception (bool): If ``True``, then show exceptions. (default: ``True``) exception_repr (function): Function to convert an exception to a string. (default: ``repr``) result_repr (function): Function to convert the result object to a string. (default: ``strip_non_ascii`` - like ``str`` but nonascii characters are replaced with dots.) use_logging (string): Emit log messages with the given loglevel. (default: ``"CRITICAL"``) print_to (fileobject): File object to write to, in case you don't want to use logging module. (default: ``None`` - printing is disabled) Returns: A decorator or a wrapper. Example:: >>> @log(print_to=sys.stdout) ... def a(weird=False): ... if weird: ... raise RuntimeError('BOOM!') >>> a() a() <<< ... a => None >>> try: ... a(weird=True) ... except Exception: ... pass # naughty code! a(weird=True) <<< ... a ~ raised RuntimeError('BOOM!',) You can conveniently use this to logs just errors, or just results, example:: >>> import aspectlib >>> with aspectlib.weave(float, log(call=False, result=False, print_to=sys.stdout)): ... try: ... float('invalid') ... except Exception as e: ... pass # naughty code! float('invalid') <<< ... float ~ raised ValueError(...float...invalid...) This makes debugging naughty code easier. PS: Without the weaving it looks like this:: >>> try: ... log(call=False, result=False, print_to=sys.stdout)(float)('invalid') ... except Exception: ... pass # naughty code! float('invalid') <<< ... float ~ raised ValueError(...float...invalid...) .. versionchanged:: 0.5.0 Renamed `arguments` to `call_args`. Renamed `arguments_repr` to `call_args_repr`. Added `call` option. """ loglevel = use_logging and ( logging._levelNames if hasattr(logging, '_levelNames') else logging._nameToLevel ).get(use_logging, logging.CRITICAL) _missing = object() def dump(buf): try: if use_logging: logger._log(loglevel, buf, ()) if print_to: buf += '\n' print_to.write(buf) except Exception as exc: logger.critical('Failed to log a message: %s', exc, exc_info=True) class __logged__(Aspect): __slots__ = 'cutpoint_function', 'final_function', 'binding', '__name__', '__weakref__' bind = False def __init__(self, cutpoint_function, binding=None): mimic(self, cutpoint_function) self.cutpoint_function = cutpoint_function self.final_function = super(__logged__, self).__call__(cutpoint_function) self.binding = binding def __get__(self, instance, owner): return __logged__(self.cutpoint_function.__get__(instance, owner), instance) def __call__(self, *args, **kwargs): return self.final_function(*args, **kwargs) def advising_function(self, *args, **kwargs): name = self.cutpoint_function.__name__ instance = self.binding if instance is not None: if isinstance(instance, InstanceType): instance_type = instance.__class__ else: instance_type = type(instance) info = [] for key in attributes: if key.endswith('()'): callarg = key = key.rstrip('()') else: callarg = False val = getattr(instance, key, _missing) if val is not _missing and key != name: info.append(' %s=%s' % ( key, call_args_repr(val() if callarg else val) )) sig = buf = '{%s%s%s}.%s' % ( instance_type.__module__ + '.' if module else '', instance_type.__name__, ''.join(info), name ) else: sig = buf = name if call_args: buf += '(%s%s)' % ( ', '.join(repr(i) for i in (args if call_args is True else args[:call_args])), ((', ' if args else '') + ', '.join('%s=%r' % i for i in kwargs.items())) if kwargs and call_args is True else '', ) if stacktrace: buf = ("%%-%ds <<< %%s" % stacktrace_align) % (buf, format_stack(skip=1, length=stacktrace)) if call: dump(buf) try: res = yield except Exception as exc: if exception: if not call: dump(buf) dump('%s ~ raised %s' % (sig, exception_repr(exc))) raise if result: dump('%s => %s' % (sig, result_repr(res))) if func: return __logged__(func) else: return __logged__