import logging
import os
import string
import sys
from itertools import islice
from wrapt import decorator
logger = logging.getLogger(__name__)
[docs]def frame_iterator(frame):
"""
Yields frames till there are no more.
"""
while frame:
yield frame
frame = frame.f_back
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.
:param function func:
Function to decorate. If missing log returns a partial which you can use as a decorator.
:param int stacktrace:
Number of frames to show.
:param int stacktrace_align:
Column to align the framelist to.
:param list attributes:
List of instance attributes to show, in case the function is a instance method.
:param bool module:
Show the module.
:param bool call:
If ``True``, then show calls. If ``False`` only show the call details on exceptions (if ``exception`` is
enabled) (default: ``True``)
:param bool call_args:
If ``True``, then show call arguments. (default: ``True``)
:param bool call_args_repr:
Function to convert one argument to a string. (default: ``repr``)
:param bool result:
If ``True``, then show result. (default: ``True``)
:param bool exception:
If ``True``, then show exceptions. (default: ``True``)
:param function exception_repr:
Function to convert an exception to a string. (default: ``repr``)
:param function result_repr:
Function to convert the result object to a string. (default: ``strip_non_ascii`` - like ``str`` but nonascii
characters are replaced with dots.)
:param string use_logging:
Emit log messages with the given loglevel. (default: ``"CRITICAL"``)
:param fileobject print_to:
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(int, log(call=False, result=False, print_to=sys.stdout)):
... try:
... int('invalid')
... except Exception:
... pass # naughty code !
int('invalid') <<< ...
int ~ raised ValueError("invalid literal for int() with base 10: 'invalid'",)
This makes debugging naughty code easier.
"""
loglevel = use_logging and logging._levelNames.get(use_logging, logging.CRITICAL)
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)
@decorator
def logged(func, instance, args, kwargs, _missing=object()):
name = func.__name__
if instance:
instance_type = instance.__class__ if hasattr(instance, '__class__') else 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 = func.__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 = func(*args, **kwargs)
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)))
return res
if func:
return logged(func)
else:
return logged