Just import, call and log your selected call/return/exception/etc. events.
You can use a standard Python logger from the
logging
module.Quite flexible tool and about 50 efective SLOC only (excluding the example script).
For debugging rather than production environments (programs noticeably slow down).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | #!/usr/bin/env python
# Copyright (c) 2010-2011 Jan Kaliszewski (zuo). All rights reserved.
# Licensed under the MIT License. Python 2.6/2.7-compatibile.
import os.path
import sys
from contextlib import contextmanager
from repr import repr as default_reprfunc
__all__ = 'trace_logging_on',
default_refdir = os.path.dirname(sys.modules['__main__'].__file__)
def trace_logging_on(refdir=default_refdir, # reference-point directory path
negprefix=('..', '<'), # negative filtering path prefixes
filterfunc=(lambda s: True), # custom filtering function
filterarg='', # .format()-able filter argument pattern
# e.g. '{event}&{path}&{frame.f_lineno}'
events2log = { # mapping events to .format()-able log patt
'call': '[C ] {path}: {name}{callargs}',
'return': '[ R] {path}: {name} -> {argrepr}',
'exception': '[ E ] {path}, in {name}:\n{traceback}',
}, # [.format()-able patterns refer to tracer()'s locals]
logger='', # logger name or instance
loggermethod='debug', # logger method name
reprfunc=default_reprfunc): # repr()-replacement function
"""
Enable logging of Python call/return/exception events (handily filtered).
Usage variants:
* simply-turn-on call: trace_logging_on(...)
* context manager syntax: with trace_logging_on(...): ...
* context manager with 'as': with trace_logging_on(...) as tracefunc: ...
"""
from inspect import getargvalues, formatargvalues
from traceback import format_exception
if isinstance(logger, basestring):
from logging import getLogger
logger = getLogger(logger)
log = getattr(logger, loggermethod)
relpath = os.path.relpath
formatvalue = (lambda s: '=' + reprfunc(s))
filterarg_format = filterarg.format
def tracer(frame, event, arg):
if event not in events2log: # initial event filtering
return tracer
path = relpath(frame.f_code.co_filename, refdir) # relative to refdir
# path-prefix-based scope filtering (negative, i.e. False lets by)
if path.startswith(negprefix):
return None # (<- None to discontinue tracing in sub-scopes)
# adding some locals (to be used to format filtering/logging arguments)
name = frame.f_code.co_name
argrepr = reprfunc(arg)
if event == 'call':
argvalues = (getargvalues(frame) if name != '<genexpr>'
else ([],) + getargvalues(frame)[1:])
callargs = formatargvalues(*argvalues, formatvalue=formatvalue)
elif event == 'exception':
traceback = ''.join(format_exception(*arg))
pattern_fields = locals()
# callback-based individual filtering (positive, i.e. True lets by)
if not filterfunc(filterarg_format(**pattern_fields)):
return tracer
# event-specific logging
log(events2log[event].format(**pattern_fields))
return tracer
with_support = [_with_statement_support(tracer, previous=sys.gettrace())]
sys.settrace(tracer)
return with_support.pop() # (we don't like circular references...)
@contextmanager
def _with_statement_support(tracer, previous):
yield tracer
sys.settrace(previous)
if __name__ == '__main__':
## Example script ##
import logging
from operator import methodcaller
def robin(a, b, c):
return c, b, a
def lancelot(x, y="Let's not bicker and argue", z="about who killed who."):
return x, y, z
def rabbit():
return 1 / 0
def arthur(nee):
lancelot('Camelot!')
robin('Oh, shut up', 'and go', 'and change your armor!')
robin(*lancelot("Why doesn't Lancelot go?"))
try:
rabbit()
except:
lancelot(*robin('spam', 'spam', 'spam'))
return nee
# enjoy your stderr :)
logging.basicConfig(level=logging.DEBUG)
arthur(0) # (<- not logged)
with trace_logging_on(logger='tim.the.enchanter',
reprfunc='.:{0}:.'.format):
arthur(1)
with trace_logging_on(refdir='/usr', negprefix=(), # any prefix is ok
filterarg='{event}', filterfunc='call'.__eq__):
arthur('2 sheds')
arthur(3)
arthur(4) # (<- not logged)
trace_logging_on(filterarg='{name}',
filterfunc=methodcaller('startswith', 'r'),
logger='comfy.chair', loggermethod='info', reprfunc=repr)
arthur(5)
|
If you change the line
to
then the code works in python 3.x
Thanx Robin for your comment.
I'd rather write:
To problem is that with Py3k (3.1) I got also some other errors -- related to Python 3 exception chaining (message mentioned
__cause__
attribute etc.) -- and I haven't had time to investigate what's wrong (probably I will when I have some spare time).2011-04-29: GenExp-related problem fixed.