Отображение трассировки стека из запущенного приложения Python


340

У меня есть это приложение Python, которое время от времени зависает, и я не могу найти где.

Есть ли какой-нибудь способ дать интерпретатору Python указание точного кода, который выполняется?

Какая-то трассировка стека на лету?

Смежные вопросы:



Ответы:


315

У меня есть модуль, который я использую для таких ситуаций - когда процесс будет работать долго, но иногда застревает по неизвестным и невоспроизводимым причинам. Это немного странно, и работает только на 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 ее использовали), и позволить ей работать. В любой момент отправьте процессу сигнал SIGUSR1, используя kill или в python:

    os.kill(pid, signal.SIGUSR1)

Это приведет к тому, что программа перейдет к консоли Python в точке, в которой она находится в данный момент, показывая трассировку стека и позволяя вам манипулировать переменными. Используйте control-d (EOF) для продолжения работы (хотя учтите, что вы, вероятно, прервете любой ввод-вывод и т. Д. В точке, о которой вы сигнализируете, чтобы он не был полностью ненавязчивым).

У меня есть другой скрипт, который делает то же самое, за исключением того, что он связывается с запущенным процессом по каналу (для отладки фоновых процессов и т. Д.). Это немного большой, чтобы разместить здесь, но я добавил его как рецепт кулинарной книги питона .


1
Спасибо! Это именно то, что я искал. Может быть, вы могли бы также опубликовать этот сценарий с поддержкой pipe на каком-нибудь сайте Python Snippets?
Себ

2
Сейчас я разместил его на сайте кулинарной книги python - ссылка добавлена.
Брайан

1
Мне нужно было добавить «import readline», чтобы включить функции истории.
miracle2k

2
Отличный совет! Это также работает для отправки сигнала всем процессам, содержащим слово «mypythonapp»: pkill -SIGUSR1 -f mypythonapp
Александр

10
Если приложение застряло, цикл интерпретатора Python может не работать для обработки сигнала. Используйте faulthandlerмодуль (и его обратный порт, найденный в PyPI) для обработчика сигналов уровня C, который будет печатать стек Python без необходимости реагирования цикла интерпретатора.
GPS

146

Предложение об установке обработчика сигналов хорошее, и я им часто пользуюсь. Например, bzr по умолчанию устанавливает обработчик SIGQUIT, который вызывает pdb.set_trace()немедленное перетаскивание вас в приглашение pdb . ( Точную информацию смотрите в источнике модуля bzrlib.breakin .) С помощью pdb вы можете не только получить текущую трассировку стека, но и проверить переменные и т. Д.

Однако иногда мне нужно отлаживать процесс, в котором у меня не было предвидения, чтобы установить обработчик сигналов. В Linux вы можете присоединить к процессу gdb и получить трассировку стека python с помощью некоторых макросов gdb. Поместите http://svn.python.org/projects/python/trunk/Misc/gdbinit в ~/.gdbinit, то:

  • Прикрепить GDB: gdb -p PID
  • Получить трассировку стека Python: pystack

К сожалению, это не совсем надежно, но работает большую часть времени.

Наконец, присоединение straceможет дать вам хорошее представление о том, что делает процесс.


2
Brilliant! Команда pystack иногда блокируется, но перед этим она дает мне полную трассировку стека процесса в строках кода Python без необходимости какой-либо подготовки.
muudscope

26
Незначительное обновление: этот метод gdb (и обновленный код) документирован на wiki.python.org/moin/DebuggingWithGdb. На этом фронте произошли некоторые разработки, документированные по этому URL, и, очевидно, gdb 7 имеет некоторую поддержку Python.
Нельсон

7
Насколько я могу судить, это действительно работает, только если у вас есть отладочные символы, скомпилированные в ваш двоичный файл python - например: вы запустили свою программу с python2-dbg (в Ubuntu это в отдельном пакете python-dbg). Без этих символов вы, похоже, не получите много полезной информации.
drevicko

1
в моем случае это возвращение Unable to locate python frameк каждой команде
seriyPS

6
GDB 7+ - с поддержкой Python обеспечивается python-gdb.py. Более подробная информация здесь: chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros
Лукас Кимон

71

Я почти всегда имею дело с несколькими потоками, и основной поток обычно не делает много, поэтому самое интересное - сбросить все стеки (что больше похоже на дамп Java). Вот реализация, основанная на этом блоге :

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)

53

Получение трассировки стека неподготовленной программы на 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, ответ на который содержал указатель на это, среди других инструментов.)


5
Это прекрасно сработало для меня, где dump_stacks.pyбыло простоimport traceback; traceback.print_stack()
Джон Леманн

2
traceback -lдает вам список предопределенных скриптов Python, которые вы можете использовать, и dump_stacks.pyявляется одним из них. Если вы используете свое собственное (например, для записи трассировки стека в файл), может быть целесообразно использовать другое имя.
Johndodo

12
Важный совет: запускайте apt-get install gdb python-dbg(или эквивалентный) перед запуском пиразита, иначе он молча потерпит неудачу. Работает как шарм в противном случае!
Johndodo

Последний выпуск пиразита был в 2012 году
Борис

35
>>> import traceback
>>> def x():
>>>    print traceback.extract_stack()

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

Вы также можете отформатировать трассировку стека, см. Документацию .

Редактировать : чтобы имитировать поведение Java, как предлагает @Douglas Leeder, добавьте это:

import signal
import traceback

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

к коду запуска в вашем приложении. Затем вы можете распечатать стек, отправив SIGUSR1запущенному процессу Python.


2
Это будет только печатать обратную трассировку основного потока. Мне еще предстоит найти решение для просмотра следов для всех потоков. Фактически, в python отсутствует API для извлечения стека из объекта Thread, хотя threading.enumerate () предоставляет доступ ко всем объектам Thread.
haridsv

Это прекрасно работает на Cygwin. Он печатает только три строки трассировки стека, но этого достаточно, чтобы получить подсказку
slashdottir

28

Модуль traceback имеет несколько приятных функций, среди которых: print_stack:

import traceback

traceback.print_stack()

1
Чтобы записать трассировку стека в файл, используйте: import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
GuruM

1
+1 к @gulgi за его простой в использовании ответ. Некоторые другие ответы выглядели очень сложными для моей простой задачи получения трассировки стека вызовов из функции скрипта.
ГуруМ

24

Вы можете попробовать модуль обработчика ошибок . Установите его используя pip install faulthandlerи добавьте:

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

в начале вашей программы. Затем отправьте SIGUSR1 вашему процессу (например kill -USR1 42:) для отображения трассировки Python всех потоков в стандартный вывод. Прочтите документацию для получения дополнительных опций (например: войти в файл) и других способов отображения трассировки.

Модуль теперь является частью Python 3.3. Для Python 2 см. Http://faulthandler.readthedocs.org/


20

Что мне действительно помогло, так это совет spiv (за который я бы проголосовал и прокомментировал бы, если бы у меня были очки репутации), чтобы получить трассировку стека из неподготовленного процесса Python. За исключением того, что он не работал, пока я не изменил скрипт gdbinit . Так:

  • загрузите http://svn.python.org/projects/python/trunk/Misc/gdbinit и вставьте его в~/.gdbinit

  • отредактируйте его, изменив PyEval_EvalFrameнаPyEval_EvalFrameEx[править: больше не нужно; связанный файл уже имеет это изменение по состоянию на 2010-01-14]

  • Прикрепить GDB: gdb -p PID

  • Получить трассировку стека Python: pystack


В gdbinit по указанному URL уже есть исправление, которое вы предлагаете. В моем случае, когда я набрал pystack, мой процессор просто завис. Не уверен почему.
Джесси Глик

2
Нет, это не так - мне было неясно, извините, потому что эта строка появляется в трех местах. Патч, на который я ссылался, показывает, какой я изменил, когда увидел эту работу.
Гуннлаугур Брим

2
Как и ответ @ spiv, для этого требуется, чтобы программа работала под python, скомпилированным с символами отладки. В противном случае вы просто получитеNo symbol "co" in current context.
Николай

12

Я бы добавил это в качестве комментария к ответу haridsv , но мне не хватает репутации, чтобы сделать это:

Некоторые из нас все еще привязаны к версии Python старше 2.6 (требуется для 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)

11

python -dv yourscript.py

Это заставит интерпретатор работать в режиме отладки и даст вам представление о том, что делает интерпретатор.

Если вы хотите интерактивно отлаживать код, вы должны запустить его так:

python -m pdb yourscript.py

Это говорит интерпретатору python запускать ваш скрипт с модулем "pdb", который является отладчиком python, если вы запустите его так, что интерпретатор будет выполняться в интерактивном режиме, очень похожем на GDB


Это не отвечает на вопрос. Вопрос был о уже запущенном процессе.
д.б.н.

11

Взгляните на faulthandlerмодуль, новый в Python 3.3. faulthandlerПортировать для использования в Python 2 доступно на PyPI.


2
Более поздний ответ @haypo описывает это более подробно. Я не уверен, как это обычно обрабатывается на SO, но кажется неправильным иметь два, по сути, дублирующих ответа ...
Николай

7

В 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.

2
Похоже, что есть программа Debian / Ubuntu, pstackкоторая делает то же самое
Рори

1
Кажется, что он дает только обратную трассировку в linux, а не Python traceback с именами файлов и строк.
Огрисел

6

Если вы работаете в системе Linux, используйте замечательные gdbрасширения Python для отладки (могут быть в виде пакета python-dbgили python-debuginfoпакета). Это также помогает с многопоточными приложениями, приложениями GUI и модулями C.

Запустите вашу программу с:

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

Это инструктирует gdbподготовить python <programname>.py <arguments>и снять rего.

Теперь, когда ваша программа зависает, переключитесь на gdbконсоль, нажмите Ctr+Cи выполните:

(gdb) thread apply all py-list

Смотрите пример сессии и больше информации здесь и здесь .


6

Некоторое время я искал решение для отладки своих потоков, и я нашел его здесь благодаря 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)

Для своих нужд я также фильтрую темы по имени.


3

Стоит взглянуть на Pydb , «расширенную версию отладчика Python, свободно основанную на наборе команд gdb». Он включает диспетчеры сигналов, которые могут позаботиться о запуске отладчика при отправке указанного сигнала.

Проект Summer of Code 2006 года рассматривал добавление функций удаленной отладки в pydb в модуле с именем mpdb .


Кажется, что он прошел два ( 1 ) переписывания ( 2 ) без добавления функции присоединения по PID, которую я искал ...
Николай

3

Я собрал воедино какой-то инструмент, который подключается к работающему процессу Python и внедряет некоторый код, чтобы получить оболочку Python.

Смотрите здесь: https://github.com/albertz/pydbattach


1
Примечание: не очевидно, как это построить. Спасибо за ссылки, которые вы поместили в README: pyrasiteотлично!
Николай

3

Это можно сделать с отличным пи-шпионом . Это профилировщик выборки для программ 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)  

2

pyringe - это отладчик, который может взаимодействовать с запущенными процессами Python, трассировкой стека печати, переменными и т. д. без какой-либо предварительной настройки.

Хотя в прошлом я часто использовал решение для обработки сигналов, в некоторых средах по-прежнему бывает трудно воспроизвести проблему.


3
По-видимому, он несовместим с некоторыми сборками GDB (например, с той, что я установил в Ubuntu): github.com/google/pyringe/issues/16 , требующей пересборки вручную. Еще один отладчик, pyrasiteработал как шарм для меня.
Николай

1

Нет способа подключиться к работающему процессу Python и получить разумные результаты. То, что я делаю, если процессы блокируются, это просто цепляется за попытки и пытается выяснить, что именно происходит.

К сожалению, часто страус является наблюдателем, который «фиксирует» условия гонки, так что результат там тоже бесполезен.


1
Да, это правда. Обидно, хотя thad pdb не поддерживает присоединение к запущенному процессу ...
Bartosz Radaczyński

Это неправда. Смотрите ответ "spiv" выше, который показывает, как подключить GDB и получить трассировку стека Python.
Андрей Кук

Это не то же самое - эти макросы GDB не являются надежными и не предоставляют полный интерфейс Power / знакомый PDB. Я часто хотел бы, чтобы кто-то написал небольшое приложение, которое использовало бы ptrace для вставки некоторого байт-кода Python в работающий процесс Python и запуска его 'import pdb; pdb.set_trace () ', возможно также после временного перенаправления sys.stdin / stdout.
Мариус Гедминас

Это больше не так, см. Другие ответы, указывающие на пириндж / пиразит.
Николай

1

Для этого вы можете использовать PuDB , отладчик Python с интерфейсом curses. Просто добавь

from pudb import set_interrupt_handler; set_interrupt_handler()

к вашему коду и используйте Ctrl-C, когда вы хотите сломать. Вы можете продолжить cи снова сломать несколько раз, если вы пропустите это и хотите повторить попытку.


Когда вы используете приведенную выше команду в django, не забудьте правильно запустить сервер, чтобы избежать сбоев: "manage.py runserver --noreload --nothreading"
potar

1

Я нахожусь в лагере GDB с расширениями Python. Следуйте https://wiki.python.org/moin/DebuggingWithGdb , что означает

  1. dnf install gdb python-debuginfo или sudo apt-get install gdb python2.7-dbg
  2. gdb python <pid of running process>
  3. py-bt

Также рассмотрим info threadsи thread apply all py-bt.


это нормально, чтобы получить ответ, как Traceback (most recent call first): Python Exception <class 'gdb.error'> No frame is currently selected.: Error occurred in Python command: No frame is currently selected.при запуске py-btв gdb?
Crookedleaf

1
неважно. это потому, что мое приложение работало как sudo. gdb pyton <pid>Мне тоже нужно было бежать как Судо.
Crookedleaf

1

Как отладить любую функцию в консоли :

Создайте функцию, в которой вы используете 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) 

Удачной отладки :)


0

Я не знаю ничего похожего на ответ java на SIGQUIT , поэтому вам, возможно, придется встроить его в свое приложение. Может быть, вы могли бы создать сервер в другом потоке, который мог бы получить трассировку стека в ответ на какое-либо сообщение?


0

используйте модуль проверки.

import inspect help (inspect.stack) Справка по стеку функций в модуле inspect:

stack (context = 1) Возвращает список записей для стека выше кадра вызывающего.

Я нахожу это действительно полезным.


0

В 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))

pdb стоит изучить, если вы потратите какое-то время на отладку Python. Интерфейс немного тупой, но должен быть знаком любому, кто использовал подобные инструменты, такие как gdb.


0

В случае, если вам нужно сделать это с помощью uWSGI, в него встроен Python Tracebacker, и достаточно просто включить его в конфигурации (номер присваивается имени каждому работнику):

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

Сделав это, вы можете распечатать трассировку, просто подключившись к сокету:

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

0

В тот момент, когда код запускается, вы можете вставить этот небольшой фрагмент, чтобы увидеть хорошо отформатированный отпечаток трассировки стека. Предполагается, что у вас есть папка logsв корневом каталоге вашего проекта.

# DEBUG: START DEBUG -->
import traceback

with open('logs/stack-trace.log', 'w') as file:
    traceback.print_stack(file=file)
# DEBUG: END DEBUG --!
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.