问题
I have a strange bug in python code which only happens sometimes in CI.
We can't reproduce it.
Where is the test code:
response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, response)
Sometimes we get a 302 which happens since the form gets saved.
My idea to debug this:
with some_magic_trace.trace() as trace:
response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, trace)
The trace should contain the python lines (filename, line offset, line as string) executed by the interpreter.
How to implement some_magic_trace.trace()
?
回答1:
The trace module gives you a very simple solution (different from what you are asking for, but simple enough to have a try.)
from trace import Trace
tracer = Trace()
response = tracer.runfunc(self.admin_client.post, url, post)
self.assertEqual(200, response.status_code, response)
A more complex solution that entails creating a context manager that saves the trace and prints it only on exceptions, requires the use of sys.settrace. Just a template for your own implementation could be:
class MyTracer():
def __init__(self):
self.trace = None
def newscope(self, frame, event, arg):
## real work should be done here, just minimal example
self.trace.append((frame, event, arg))
return None
def pprint(self):
## real pretty printing of trace info should be done here
print(self.trace)
def __enter__(self):
self.trace = []
sys.settrace(self.newscope)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
sys.settrace(None)
if exc_type is not None:
self.pprint()
## print some info gathered from exc_type, exc_val, exc_tb
Then you can:
with MyTracer():
response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, response)
The idea is that a MyTracer instance has a tracer method newscope
that saves some useful info in self.trace
. On an abnormal exit from the context the pprint
method is called; on a normal exit the trace info is discarded.
Most of the work has to be done in the tracing method newscope
.
Some concrete examples of tracing functions can be found here.
回答2:
Note: Stefano M
's solution is the correct answer since fits perfectly with StackOverflow standards
For those unfamiliar with working with Frame/traceback objects I have provided a working Tracker
class built from Stefano M
's MyTracer
template:
import sys,traceback
def printf(*stuff,sep=" ",end="\n",file=sys.stdout):
file.write(sep.join(stuff)+end)#for backward compatability with python2
class Tracker:
def __init__(self,out_file = sys.stdout):
assert out_file.writable(),"need to open a file for writing"
self.out = out_file
self.last_frame = None
def __enter__(self):
self.old_trace = sys.gettrace()
sys.settrace(self.newscope)
def __exit__(self,etype,value,tb):
sys.settrace(self.old_trace)
self.finish_previous()
if tb:
traceback.printf_exception(etype,value,tb,file=self.out)
else:
printf("exit status normal",file=self.out)
def newscope(self,f,e,ar):
self.finish_previous(f)
global_vars = f.f_globals
for name,module in sys.modules.items():
if global_vars is vars(module):
file_name = name
break
else:
file_name = "??"
module = None
printf(self._format_module(file_name,f.f_lineno),file=self.out)
if module:
printf(self._read_line_of_file(module,f.f_lineno),file=self.out)
@staticmethod
def _format_module(file_name,line):
return "{}, line:{}".format(file_name,line)
@staticmethod
def _read_line_of_file(module,line):
try:
with open(module.__file__,"r") as FILE:
line = list(FILE)[line]
return line
except Exception as e:
return "ERROR WHEN READING FILE: %r"%e
@staticmethod
def _local_vars(frame):
return "locals:\n "+"\n ".join("{} = {!r}".format(*pair) for pair in frame.f_locals.items())
def finish_previous(self,new_frame=None):
if self.last_frame:
printf(self._local_vars(self.last_frame),end="\n\n\n",file=self.out)
self.last_frame = new_frame
Then it can be used like this for displaying output to standard output:
with Tracker():
response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, trace)
or to send output to a file you can do this:
with open("log.txt","w") as f, Tracker(f):
response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, trace)
hope that can give you immediate results but you would still be much better off to implement the template by Stefano M yourself since the information you are looking for is likely different then what I would want displayed.
回答3:
To use trace.Trace
with the with
context instead of Trace.runfunc
you can use:
import trace
class ContextTrace(trace.Trace):
def __enter__(self):
if not self.donothing:
sys.settrace(self.globaltrace)
return self
def __exit__(self,*others):
if not self.donothing:
sys.settrace(None)
来源:https://stackoverflow.com/questions/34872918/trace-bug-which-happends-only-sometimes-in-ci