显示正在运行的 Python 应用程序的堆栈跟踪

Posted

技术标签:

【中文标题】显示正在运行的 Python 应用程序的堆栈跟踪【英文标题】:Showing the stack trace from a running Python application 【发布时间】:2010-09-13 00:31:44 【问题描述】:

我有这个 Python 应用程序时常卡住,我不知道在哪里。

有什么方法可以让 Python 解释器向您显示正在运行的确切代码吗?

某种即时堆栈跟踪?

相关问题:

Print current call stack from a method in Python code Check what a running process is doing: print stack trace of an uninstrumented Python program

【问题讨论】:

相关:***.com/q/4163964/1449460 相关wiki.python.org/moin/DebuggingWithGdb 这个问题debugging - Get stacktrace from stuck python process - Stack Overflow 询问信号不起作用的情况(尽管下面的一些其他答案也解决了这种情况) 【参考方案1】:

python -dv yourscript.py

这将使解释器在调试模式下运行,并为您提供解释器正在做什么的跟踪。

如果你想交互式地调试代码,你应该像这样运行它:

python -m pdb yourscript.py

这告诉python解释器使用模块“pdb”运行你的脚本,它是python调试器,如果你运行它,解释器将以交互模式执行,就像GDB一样

【讨论】:

这没有回答问题。问题是关于一个已经在运行的进程。【参考方案2】:

traceback 模块有一些不错的功能,其中:print_stack:

import traceback

traceback.print_stack()

【讨论】:

要将堆栈跟踪写入文件,请使用:import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close() +1 到@gulgi 以获得易于使用的答案。对于我从脚本函数获取调用堆栈跟踪的简单任务,其他一些答案看起来非常复杂。【参考方案3】:
>>> import traceback
>>> def x():
>>>    print traceback.extract_stack()

>>> x()
[('<stdin>', 1, '<module>', None), ('<stdin>', 2, 'x', None)]

您还可以很好地格式化堆栈跟踪,请参阅docs。

编辑:按照@Douglas Leeder 的建议,模拟Java 的行为,添加以下内容:

import signal
import traceback

signal.signal(signal.SIGUSR1, lambda sig, stack: traceback.print_stack(stack))

到应用程序中的启动代码。然后,您可以通过将SIGUSR1 发送到正在运行的 Python 进程来打印堆栈。

【讨论】:

这只会打印主线程的回溯。我还没有找到查看所有线程跟踪的解决方案。事实上,python 似乎缺少从 Thread 对象中检索堆栈的 API,尽管 threading.enumerate() 提供了对所有 Thread 对象的访问权限。 这在 cygwin 上效果很好。虽然它只打印了三行堆栈跟踪,但这足以获得线索【参考方案4】:

我不知道类似java's response to SIGQUIT 的任何东西,因此您可能必须将其构建到您的应用程序中。也许您可以在另一个线程中创建一个服务器,以便在响应某种消息时获取堆栈跟踪?

【讨论】:

【参考方案5】:

没有办法挂钩到正在运行的 python 进程并获得合理的结果。如果进程被锁定,我会做些什么来连接 strace 并试图弄清楚到底发生了什么。

不幸的是,strace 通常是“修复”竞争条件的观察者,因此输出在那里也毫无用处。

【讨论】:

是的,这是真的。可惜 pdb 不支持附加到正在运行的进程... 这不是真的。参见上面“spiv”的回答,它展示了如何连接 gdb 并获取 Python 堆栈跟踪。 不一样——那些 gdb 宏不可靠,也不提供 pdb 的完整功能/熟悉的接口。我经常希望有人编写一个小应用程序,它会使用 ptrace 将一些 Python 字节码注入到正在运行的 Python 进程中并让它执行'import pdb; pdb.set_trace()',也可能在临时重定向 sys.stdin/stdout 之后。 这不再是真的,请参阅其他指向 pyringe/pyrasite 的答案。【参考方案6】:

我有用于这种情况的模块 - 一个进程将运行很长时间,但有时会因为未知和不可重现的原因而卡住。它有点 hacky,只适用于 unix(需要信号):

import code, traceback, signal

def debug(sig, frame):
    """Interrupt running process, and provide a python prompt for
    interactive debugging."""
    d='_frame':frame         # Allow access to frame object.
    d.update(frame.f_globals)  # Unless shadowed by global
    d.update(frame.f_locals)

    i = code.InteractiveConsole(d)
    message  = "Signal received : entering python shell.\nTraceback:\n"
    message += ''.join(traceback.format_stack(frame))
    i.interact(message)

def listen():
    signal.signal(signal.SIGUSR1, debug)  # Register handler

要使用它,只需在程序启动时调用listen() 函数(您甚至可以将它粘贴在site.py 中以让所有python 程序都使用它),然后让它运行。在任何时候,使用 kill 或在 python 中向进程发送 SIGUSR1 信号:

    os.kill(pid, signal.SIGUSR1)

这将导致程序在当前位置中断到 python 控制台,向您显示堆栈跟踪,并让您操作变量。使用 control-d (EOF) 继续运行(但请注意,您可能会在发出信号时中断任何 I/O 等,因此它不是完全非侵入性的。

我有另一个脚本做同样的事情,除了它通过管道与正在运行的进程通信(以允许调试后台进程等)。在这里发布有点大,但我已将其添加为python cookbook recipe。

【讨论】:

谢谢!这正是我一直在寻找的。也许你也可以在一些 Python sn-ps 网站上发布带有管道支持的脚本? 我现在已经在 python 食谱网站上发布了它 - 添加了链接。 我需要添加“import readline”来启用历史记录功能。 很棒的提示!这也可以将信号发送到包含单词“mypythonapp”的所有进程: pkill -SIGUSR1 -f mypythonapp 如果应用程序卡住了,Python解释器循环可能无法运行来处理信号。将 faulthandler 模块(及其在 PyPI 上找到的 backport)用于 C 级信号处理程序,该处理程序将打印 Python 堆栈,而无需解释器循环响应。【参考方案7】:

安装信号处理程序的建议很好,我经常使用它。例如,bzr 默认安装一个 SIGQUIT 处理程序,该处理程序调用pdb.set_trace() 以立即将您放入pdb 提示符。 (有关详细信息,请参阅bzrlib.breakin 模块的源代码。)使用 pdb,您不仅可以获取当前堆栈跟踪(使用 (w)here 命令),还可以检查变量等。

但是,有时我需要调试一个我没有远见的进程来安装信号处理程序。在 linux 上,您可以将 gdb 附加到进程并使用一些 gdb 宏获取 python 堆栈跟踪。将http://svn.python.org/projects/python/trunk/Misc/gdbinit 放入~/.gdbinit,然后:

附加gdb:gdb -p PID 获取python堆栈跟踪:pystack

不幸的是,它并不完全可靠,但它大部分时间都有效。

最后,附加strace 通常可以让您很好地了解进程在做什么。

【讨论】:

太棒了! pystack 命令有时会锁定,但在它锁定之前,它会给我一个完整的进程堆栈跟踪,在 python 代码行中,不需要做任何准备。 次要更新:这个 gdb 技术(和更新的代码)记录在 wiki.python.org/moin/DebuggingWithGdb 这方面有一些发展,记录在那个 URL 上,显然 gdb 7 有一些 Python 支持。 据我所知,这只有在您将调试符号编译到您的 python 二进制文件中时才真正有效 - 例如:您使用 python2-dbg 运行程序(在 Ubuntu 上,这是在一个单独的包中python-dbg)。如果没有这些符号,您似乎无法获得太多有用的信息。 在我的情况下,这会将 Unable to locate python frame 返回到每个命令 gdb 7+ --with-python 支持由 python-gdb.py 提供。更多细节在这里:chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros【参考方案8】:

值得一看 Pydb,“Python 调试器的扩展版本,松散地基于 gdb 命令集”。它包括信号管理器,可以在发送指定信号时负责启动调试器。

2006 Summer of Code 项目着眼于在名为 mpdb 的模块中向 pydb 添加远程调试功能。

【讨论】:

似乎它经历了两次 (1) 重写 (2) without adding the attach-by-PID 我正在寻找的功能...【参考方案9】:

真正帮助我的是spiv's tip(如果我有声誉点,我会投票并评论)从未准备 Python 进程中获取堆栈跟踪。除非它直到我modified the gdbinit script 才起作用。所以:

下载http://svn.python.org/projects/python/trunk/Misc/gdbinit并放入~/.gdbinit

编辑它,将PyEval_EvalFrame 更改为PyEval_EvalFrameEx [编辑:不再需要;截至 2010 年 1 月 14 日,链接文件已经有此更改]

附加gdb:gdb -p PID

获取 python 堆栈跟踪:pystack

【讨论】:

上述 URL 上的 gdbinit 似乎已经包含您建议的补丁。就我而言,当我输入 pystack 时,我的 CPU 刚刚挂起。不知道为什么。 不,它没有——我不清楚,抱歉,因为那条线出现在三个地方。我链接到的补丁显示了当我看到这个作品时我改变了哪一个。 就像@spiv 的回答一样,这需要程序在使用调试符号编译的python 下运行。否则你只会得到No symbol "co" in current context.【参考方案10】:

使用检查模块。

进口检查 帮助(检查堆栈) 模块检查中函数堆栈的帮助:

堆栈(上下文=1) 返回调用者框架上方堆栈的记录列表。

我觉得它确实很有帮助。

【讨论】:

【参考方案11】:

我几乎总是在处理多个线程,而主线程通常不会做太多事情,所以最有趣的是转储所有堆栈(这更像是 Java 的转储)。这是一个基于this blog的实现:

import threading, sys, traceback

def dumpstacks(signal, frame):
    id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
    code = []
    for threadId, stack in sys._current_frames().items():
        code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
        for filename, lineno, name, line in traceback.extract_stack(stack):
            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
            if line:
                code.append("  %s" % (line.strip()))
    print("\n".join(code))

import signal
signal.signal(signal.SIGQUIT, dumpstacks)

【讨论】:

python 3 有一个更简单的解决方案——调用traceback.print_stack()【参考方案12】:

我会将此作为评论添加到haridsv's response,但我缺乏这样做的声誉:

我们中的一些人仍然坚持使用早于 2.6 的 Python 版本(Thread.ident 需要),所以我得到了在 Python 2.5 中工作的代码(尽管没有显示线程名称):

import traceback
import sys
def dumpstacks(signal, frame):
    code = []
    for threadId, stack in sys._current_frames().items():
            code.append("\n# Thread: %d" % (threadId))
        for filename, lineno, name, line in traceback.extract_stack(stack):
            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
            if line:
                code.append("  %s" % (line.strip()))
    print "\n".join(code)

import signal
signal.signal(signal.SIGQUIT, dumpstacks)

【讨论】:

【参考方案13】:

在 Solaris 上,您可以使用 pstack(1) 无需更改 python 代码。例如。

# pstack 16000 | grep : | head
16000: /usr/bin/python2.6 /usr/lib/pkg.depotd --cfg svc:/application/pkg/serv
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:282 (_wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:295 (wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:242 (block) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/_init_.py:249 (quickstart) ]
[ /usr/lib/pkg.depotd:890 (<module>) ]
[ /usr/lib/python2.6/threading.py:256 (wait) ]
[ /usr/lib/python2.6/Queue.py:177 (get) ]
[ /usr/lib/python2.6/vendor-packages/pkg/server/depot.py:2142 (run) ]
[ /usr/lib/python2.6/threading.py:477 (run)
etc.

【讨论】:

似乎有一个 Debian/Ubuntu 程序 pstack 做同样的事情 好像只给出linux下的回溯,而不是Python的带有文件名和行号的回溯。【参考方案14】:

看看faulthandler 模块,Python 3.3 中的新模块。 PyPI 上提供了用于 Python 2 的faulthandler backport。

【讨论】:

@haypo 最近的回答更详细地介绍了这一点。我不确定这通常是如何在 SO 上处理的,但是有两个基本上重复的答案感觉不对...【参考方案15】:

我拼凑了一些工具,该工具附加到正在运行的 Python 进程中并注入一些代码以获取 Python shell。

请看这里:https://github.com/albertz/pydbattach

【讨论】:

注意:如何构建它并不明显。感谢您在自述文件中提供的链接:pyrasite 工作得很好!【参考方案16】:

我一直在寻找调试线程的解决方案,多亏了haridsv,我在这里找到了它。我使用了稍微简化的版本,使用了 traceback.print_stack():

import sys, traceback, signal
import threading
import os

def dumpstacks(signal, frame):
  id2name = dict((th.ident, th.name) for th in threading.enumerate())
  for threadId, stack in sys._current_frames().items():
    print(id2name[threadId])
    traceback.print_stack(f=stack)

signal.signal(signal.SIGQUIT, dumpstacks)

os.killpg(os.getpgid(0), signal.SIGQUIT)

根据我的需要,我还按名称过滤线程。

【讨论】:

【参考方案17】:

你可以试试faulthandler module。使用pip install faulthandler 安装并添加:

import faulthandler, signal
faulthandler.register(signal.SIGUSR1)

在程序的开头。然后将 SIGUSR1 发送到您的进程(例如:kill -USR1 42)以将所有线程的 Python 回溯显示到标准输出。 Read the documentation 获取更多选项(例如:登录文件)和其他显示回溯的方式。

该模块现在是 Python 3.3 的一部分。对于 Python 2,请参阅http://faulthandler.readthedocs.org/

【讨论】:

【参考方案18】:

您可以使用PuDB,这是一个带有curses 接口的Python 调试器来执行此操作。只需添加

from pudb import set_interrupt_handler; set_interrupt_handler()

到您的代码并在您想要中断时使用 Ctrl-C。如果您错过了它并想再试一次,您可以继续使用c 并再次中断多次。

【讨论】:

在django中使用上述命令时,不要忘记正确运行服务器以防止出现故障:“manage.py runserver --noreload --nothreading”【参考方案19】:

如果您使用的是 Linux 系统,请使用 gdb 的强大功能和 Python 调试扩展(可以在 python-dbgpython-debuginfo 包中)。它还有助于多线程应用程序、GUI 应用程序和 C 模块。

运行你的程序:

$ gdb -ex r --args python <programname>.py [arguments]

这指示gdb 准备python &lt;programname&gt;.py &lt;arguments&gt;run 它。

现在当你程序挂起时,切换到gdb控制台,按Ctr+C并执行:

(gdb) thread apply all py-list

请参阅 example session 和更多信息 here 和 here。

【讨论】:

【参考方案20】:

pyringe 是一个调试器,无需任何先验设置即可与正在运行的 python 进程交互、打印堆栈跟踪、变量等。

虽然我过去经常使用信号处理程序解决方案,但在某些环境中重现问题通常仍然很困难。

【讨论】:

显然它与某些 gdb 构建不兼容(例如我在 ubuntu 上安装的那个):github.com/google/pyringe/issues/16,需要手动重建。另一个调试器 pyrasite 对我来说就像一个魅力。【参考方案21】:

在 Python 3 中,pdb 将在您第一次在调试器中使用 c(ont(inue)) 时自动安装信号处理程序。之后按 Control-C 会让你回到那里。在 Python 2 中,这是一个单行代码,即使在相对较旧的版本中也应该可以工作(在 2.7 中测试,但我将 Python 源代码检查回 2.4 并且看起来还不错):

import pdb, signal
signal.signal(signal.SIGINT, lambda sig, frame: pdb.Pdb().set_trace(frame))

如果你花时间调试 Python,pdb 是值得学习的。界面有点生硬,但使用过类似工具(如 gdb)的人应该很熟悉。

【讨论】:

【参考方案22】:

获取未准备 python 程序的堆栈跟踪,在普通 python 中运行没有调试符号可以使用pyrasite 完成。在 Ubuntu Trusty 上对我来说就像一个魅力:

$ sudo pip install pyrasite
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
$ sudo pyrasite 16262 dump_stacks.py # dumps stacks to stdout/stderr of the python program

(对@Albert 的提示,他的回答包含指向此的指针,以及其他工具。)

【讨论】:

这对我很有用,dump_stacks.py 只是 import traceback; traceback.print_stack() traceback -l 为您提供了可以使用的预定义 python 脚本列表,dump_stacks.py 就是其中之一。如果您使用自己的名称(例如将堆栈跟踪写入文件),使用不同的名称可能是明智的。 重要提示:在运行 pyrasite 之前运行apt-get install gdb python-dbg(或等效项),否则它将静默失败。否则就像一个魅力! 最后发布的pyrasite是in 2012 (免责声明:我的包)我有一个 fork pyrasite-ng 可以修复各种报告的错误。【参考方案23】:

如果您需要使用 uWSGI 执行此操作,它内置了 Python Tracebacker,只需在配置中启用它(编号附在每个工作人员的名称上):

py-tracebacker=/var/run/uwsgi/pytrace

完成此操作后,您只需连接到套接字即可打印回溯:

uwsgi --connect-and-read /var/run/uwsgi/pytrace1

【讨论】:

【参考方案24】:

我在 GDB 阵营中使用 python 扩展。关注https://wiki.python.org/moin/DebuggingWithGdb,意思是

    dnf install gdb python-debuginfosudo apt-get install gdb python2.7-dbg gdb python &lt;pid of running process&gt; py-bt

同时考虑info threadsthread apply all py-bt

【讨论】:

gdb 中运行py-bt 时得到类似Traceback (most recent call first): Python Exception &lt;class 'gdb.error'&gt; No frame is currently selected.: Error occurred in Python command: No frame is currently selected. 的响应是否正常? 没关系。这是因为我的应用程序以sudo 运行。我也需要以 sudo 的身份运行 gdb pyton &lt;pid&gt;【参考方案25】:

如何在控制台中调试任何功能

在您使用 pdb.set_trace() 的地方创建函数,然后是要调试的函数。

>>> import pdb
>>> import my_function

>>> def f():
...     pdb.set_trace()
...     my_function()
... 

然后调用创建的函数:

>>> f()
> <stdin>(3)f()
(Pdb) s
--Call--
> <stdin>(1)my_function()
(Pdb) 

调试愉快:)

【讨论】:

【参考方案26】:

优秀的py-spy可以做到。它是Python 程序的采样分析器,因此它的工作是附加到 Python 进程并对其调用堆栈进行采样。因此,py-spy dump --pid $SOME_PID 是您在$SOME_PID 进程中转储所有线程的调用堆栈所需要做的所有事情。通常它需要升级权限(读取目标进程的内存)。

这是一个线程化 Python 应用程序的示例。

$ sudo py-spy dump --pid 31080
Process 31080: python3.7 -m chronologer -e production serve -u www-data -m
Python v3.7.1 (/usr/local/bin/python3.7)

Thread 0x7FEF5E410400 (active): "MainThread"
    _wait (cherrypy/process/wspbus.py:370)
    wait (cherrypy/process/wspbus.py:384)
    block (cherrypy/process/wspbus.py:321)
    start (cherrypy/daemon.py:72)
    serve (chronologer/cli.py:27)
    main (chronologer/cli.py:84)
    <module> (chronologer/__main__.py:5)
    _run_code (runpy.py:85)
    _run_module_as_main (runpy.py:193)
Thread 0x7FEF55636700 (active): "_TimeoutMonitor"
    run (cherrypy/process/plugins.py:518)
    _bootstrap_inner (threading.py:917)
    _bootstrap (threading.py:885)
Thread 0x7FEF54B35700 (active): "HTTPServer Thread-2"
    accept (socket.py:212)
    tick (cherrypy/wsgiserver/__init__.py:2075)
    start (cherrypy/wsgiserver/__init__.py:2021)
    _start_http_thread (cherrypy/process/servers.py:217)
    run (threading.py:865)
    _bootstrap_inner (threading.py:917)
    _bootstrap (threading.py:885)
...
Thread 0x7FEF2BFFF700 (idle): "CP Server Thread-10"
    wait (threading.py:296)
    get (queue.py:170)
    run (cherrypy/wsgiserver/__init__.py:1586)
    _bootstrap_inner (threading.py:917)
    _bootstrap (threading.py:885)  

【讨论】:

这行得通,但是来自@kmaork 的回答中的hypno 会产生一个Python 回溯,这对于找出问题所在更有用。【参考方案27】:

在代码运行时,您可以插入这个小 sn-p 以查看格式良好的打印堆栈跟踪。它假定您在项目的根目录中有一个名为 logs 的文件夹。

# DEBUG: START DEBUG -->
import traceback

with open('logs/stack-trace.log', 'w') as file:
    traceback.print_stack(file=file)
# DEBUG: END DEBUG --!

【讨论】:

【参考方案28】:

您可以使用hypno 包,如下所示:

hypno <pid> "import traceback; traceback.print_stack()"

这会将堆栈跟踪打印到程序的标准输出中。

或者,如果您不想将任何内容打印到标准输出,或者您无权访问它(例如守护程序),您可以使用 madbg 包,它是一个允许您的 python 调试器附加到正在运行的 python 程序并在当前终端中对其进行调试。它类似于pyrasitepyringe,但更新,不需要gdb,并使用IPython 作为调试器(这意味着颜色和自动完成)。

要查看正在运行的程序的堆栈跟踪,您可以运行:

madbg attach <pid>

然后在调试器外壳中,输入: bt

免责声明 - 我写了两个包

【讨论】:

FTW! sudo $(which hypno) $(pgrep pytest) "import traceback; traceback.print_stack()"(没有 sudo 就无法工作,尽管两个进程都属于同一个用户。) 很高兴听到!如果你在你的机器上允许 ptrace 范围,它可以在没有 sudo 的情况下工作,更多细节在这里:***.com/q/19215177/2907819【参考方案29】:

如果您想查看正在运行的 Python 应用程序以查看类似 top 的 fashon 中的“实时”调用堆栈,您可以使用 austin-tui (https://github.com/p403n1x87/austin-tui)。您可以从 PyPI 安装它,例如

pipx install austin-tui

请注意,它需要 austin 二进制文件才能工作 (https://github.com/p403n1x87/austin),但是您可以使用

附加到正在运行的 Python 进程
austin-tui -p <pid>

【讨论】:

以上是关于显示正在运行的 Python 应用程序的堆栈跟踪的主要内容,如果未能解决你的问题,请参考以下文章

堆栈跟踪作为字符串

AWS Lambda在Python 3.8中不显示原因 异常堆栈跟踪

详细的堆栈跟踪:错误:找不到模块“条带”

从堆栈跟踪中获取更多详细信息

Kotlin 代码堆栈跟踪显示 Java 行号

Rails应用程序在测试运行时是否应加载其.env文件?