Отображение трассировки стека из запущенного приложения 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.