importloggingimportosimportstringimportsysfromitertoolsimportislicefromaspectlibimportAspectfromaspectlibimportmimictry:fromtypesimportInstanceTypeexceptImportError:InstanceType=type(None)logger=logging.getLogger(__name__)[docs]defframe_iterator(frame):""" Yields frames till there are no more. """whileframe:yieldframeframe=frame.f_back [docs]defformat_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)forfinislice(frame_iterator(sys._getframe(1+skip)),length)) PRINTABLE=string.digits+string.ascii_letters+string.punctuation+' 'ASCII_ONLY=''.join(iifiinPRINTABLEelse'.'foriin(chr(c)forcinrange(256)))[docs]defstrip_non_ascii(val):""" Convert to string (using `str`) and replace non-ascii characters with a dot (``.``). """returnstr(val).translate(ASCII_ONLY) [docs]deflog(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_loggingand(logging._levelNamesifhasattr(logging,'_levelNames')elselogging._nameToLevel).get(use_logging,logging.CRITICAL)_missing=object()defdump(buf):try:ifuse_logging:logger._log(loglevel,buf,())ifprint_to:buf+='\n'print_to.write(buf)exceptExceptionasexc:logger.critical('Failed to log a message:%s',exc,exc_info=True)class__logged__(Aspect):__slots__='cutpoint_function','final_function','binding','__name__','__weakref__'bind=Falsedef__init__(self,cutpoint_function,binding=None):mimic(self,cutpoint_function)self.cutpoint_function=cutpoint_functionself.final_function=super(__logged__,self).__call__(cutpoint_function)self.binding=bindingdef__get__(self,instance,owner):return__logged__(self.cutpoint_function.__get__(instance,owner),instance)def__call__(self,*args,**kwargs):returnself.final_function(*args,**kwargs)defadvising_function(self,*args,**kwargs):name=self.cutpoint_function.__name__instance=self.bindingifinstanceisnotNone:ifisinstance(instance,InstanceType):instance_type=instance.__class__else:instance_type=type(instance)info=[]forkeyinattributes:ifkey.endswith('()'):callarg=key=key.rstrip('()')else:callarg=Falseval=getattr(instance,key,_missing)ifvalisnot_missingandkey!=name:info.append('%s=%s'%(key,call_args_repr(val()ifcallargelseval)))sig=buf='{%s%s%s}.%s'%(instance_type.__module__+'.'ifmoduleelse'',instance_type.__name__,''.join(info),name)else:sig=buf=nameifcall_args:buf+='(%s%s)'%(', '.join(repr(i)foriin(argsifcall_argsisTrueelseargs[:call_args])),((', 'ifargselse'')+', '.join('%s=%r'%iforiinkwargs.items()))ifkwargsandcall_argsisTrueelse'',)ifstacktrace:buf=("%%-%ds <<<%%s"%stacktrace_align)%(buf,format_stack(skip=1,length=stacktrace))ifcall:dump(buf)try:res=yieldexceptExceptionasexc:ifexception:ifnotcall:dump(buf)dump('%s ~ raised%s'%(sig,exception_repr(exc)))raiseifresult:dump('%s =>%s'%(sig,result_repr(res)))iffunc:return__logged__(func)else:return__logged__