【问题标题】:Trace Bug which happends only sometimes in CI跟踪仅在 CI 中偶尔发生的错误
【发布时间】:2016-01-19 09:25:01
【问题描述】:

我在 python 代码中有一个奇怪的错误,它有时只会在 CI 中发生。

我们无法复制它。

测试代码在哪里:

response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, response)

有时我们会收到 302,这是在表单保存后发生的。

我的调试思路:

with some_magic_trace.trace() as trace:
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, trace)

跟踪应包含解释器执行的 python 行(文件名、行偏移量、行作为字符串)。

some_magic_trace.trace()如何实现?

【问题讨论】:

  • python 的标准跟踪模块的programmatic interface 还不够完成这项任务吗?为什么解释器正常回溯不足?
  • @StefanoM 编程接口可以提供帮助。谢谢你的提示。对于下一个问题,为什么普通的解释器回溯是不够的:I 方法返回一个我不期望的值。这就是我目前所知道的。我想知道在返回值之前发生了什么。我在回溯中看不到这一点。在这种情况下,通常您会使用调试器。但是错误只出现在持续集成环境中。不在别处:-(

标签: python debugging trace


【解决方案1】:

trace 模块为您提供了一个非常简单的解决方案(与您的要求不同,但足够简单,可以试一试。)

from trace import Trace

tracer = Trace()
response = tracer.runfunc(self.admin_client.post, url, post)
self.assertEqual(200, response.status_code, response)

需要创建一个上下文管理器来保存跟踪并仅在异常时打印它的更复杂的解决方案需要使用sys.settrace。 只是您自己实现的模板可以是:

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

那么你可以:

with MyTracer():
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, response)

这个想法是 MyTracer 实例有一个跟踪器方法newscope,它将一些有用的信息保存在self.trace 中。在从上下文异常退出时,调用pprint 方法;在正常退出时,跟踪信息将被丢弃。

大部分工作都必须在追踪方法newscope 中完成。 跟踪函数的一些具体例子可以在here找到。

【讨论】:

  • 改为使用with MyTracer() as tracer:,因为您的实现仅在出现错误时打印数据,但问题中的问题不会引发错误,将其保留为tracer,然后您可以访问@ 987654333@ 并获得有关它的更好信息。
  • @TadhgMcDonald-Jensen 我的理解是,只有在self.assertEqual(200, response.status_code, response) 引发异常时才需要跟踪,这是一种相当不寻常的情况。这个想法是“缓冲”MyTracer 对象中的跟踪信息,并使用异常作为触发器来打印收集到的数据。在大多数情况下,您只需丢弃它。当然,您可以灵活选择哪些异常会触发打印收集的数据。
  • 我只指示例使用,即使使用 with MyTracer() as tracer 从未调用过printff,程序仍然可以访问tracer.trace 中记录的帧,更多地指向使用你的任何人执行。非常典型的答案!
【解决方案2】:

注意:Stefano M 的解决方案是正确答案,因为它完全符合 StackOverflow 标准

对于那些不熟悉使用 Frame/traceback 对象的人,我提供了一个可用的 TrackerStefano MMyTracer 模板构建

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

那么它可以像这样用于将输出显示到标准输出:

with Tracker():
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, trace)

或将输出发送到文件,您可以这样做:

with open("log.txt","w") as f, Tracker(f):
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, trace)

希望能给您带来立竿见影的效果,但您最好还是自己实现 Stefano M 模板,因为您要查找的信息可能与我想要显示的不同。

【讨论】:

    【解决方案3】:

    要将trace.Tracewith 上下文一起使用,而不是Trace.runfunc,您可以使用:

    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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-22
      • 2020-05-26
      • 2011-05-01
      • 1970-01-01
      • 2010-12-24
      • 2012-05-09
      • 2015-02-07
      • 1970-01-01
      相关资源
      最近更新 更多