{"id":13501575,"url":"https://github.com/ionelmc/python-manhole","last_synced_at":"2025-05-15T20:03:27.194Z","repository":{"id":9178724,"uuid":"10980048","full_name":"ionelmc/python-manhole","owner":"ionelmc","description":"Debugging manhole for python applications.","archived":false,"fork":false,"pushed_at":"2024-07-04T16:25:32.000Z","size":545,"stargazers_count":383,"open_issues_count":7,"forks_count":24,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-09T10:17:48.410Z","etag":null,"topics":["debugging","python"],"latest_commit_sha":null,"homepage":"https://pypi.python.org/pypi/manhole","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ionelmc.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.rst","contributing":"CONTRIBUTING.rst","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.rst","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-06-26T21:49:34.000Z","updated_at":"2025-04-25T13:43:34.000Z","dependencies_parsed_at":"2024-06-18T15:32:42.531Z","dependency_job_id":"b37c874c-ded6-490d-a232-53b7cedb16ca","html_url":"https://github.com/ionelmc/python-manhole","commit_stats":{"total_commits":471,"total_committers":12,"mean_commits":39.25,"dds":0.09341825902335454,"last_synced_commit":"db92fad359aec2296b96d9bc490e731a3bc70aea"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionelmc%2Fpython-manhole","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionelmc%2Fpython-manhole/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionelmc%2Fpython-manhole/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionelmc%2Fpython-manhole/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ionelmc","download_url":"https://codeload.github.com/ionelmc/python-manhole/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414493,"owners_count":22067271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debugging","python"],"created_at":"2024-07-31T22:01:42.094Z","updated_at":"2025-05-15T20:03:25.297Z","avatar_url":"https://github.com/ionelmc.png","language":"Python","readme":"========\nOverview\n========\n\n.. start-badges\n\n.. list-table::\n    :stub-columns: 1\n\n    * - docs\n      - |docs|\n    * - tests\n      - |github-actions| |coveralls| |codecov|\n    * - package\n      - |version| |wheel| |supported-versions| |supported-implementations| |commits-since|\n.. |docs| image:: https://readthedocs.org/projects/python-manhole/badge/?style=flat\n    :target: https://readthedocs.org/projects/python-manhole/\n    :alt: Documentation Status\n\n.. |github-actions| image:: https://github.com/ionelmc/python-manhole/actions/workflows/github-actions.yml/badge.svg\n    :alt: GitHub Actions Build Status\n    :target: https://github.com/ionelmc/python-manhole/actions\n\n.. |coveralls| image:: https://coveralls.io/repos/github/ionelmc/python-manhole/badge.svg?branch=master\n    :alt: Coverage Status\n    :target: https://coveralls.io/github/ionelmc/python-manhole?branch=master\n\n.. |codecov| image:: https://codecov.io/gh/ionelmc/python-manhole/branch/master/graphs/badge.svg?branch=master\n    :alt: Coverage Status\n    :target: https://app.codecov.io/github/ionelmc/python-manhole\n\n.. |version| image:: https://img.shields.io/pypi/v/manhole.svg\n    :alt: PyPI Package latest release\n    :target: https://pypi.org/project/manhole\n\n.. |wheel| image:: https://img.shields.io/pypi/wheel/manhole.svg\n    :alt: PyPI Wheel\n    :target: https://pypi.org/project/manhole\n\n.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/manhole.svg\n    :alt: Supported versions\n    :target: https://pypi.org/project/manhole\n\n.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/manhole.svg\n    :alt: Supported implementations\n    :target: https://pypi.org/project/manhole\n\n.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-manhole/v1.8.1.svg\n    :alt: Commits since latest release\n    :target: https://github.com/ionelmc/python-manhole/compare/v1.8.1...master\n\n\n\n.. end-badges\n\nManhole is in-process service that will accept unix domain socket connections and present the\nstacktraces for all threads and an interactive prompt. It can either work as a python daemon\nthread waiting for connections at all times *or* a signal handler (stopping your application and\nwaiting for a connection).\n\nAccess to the socket is restricted to the application's effective user id or root.\n\nThis is just like Twisted's `manhole \u003chttp://twistedmatrix.com/documents/current/api/twisted.conch.manhole.html\u003e`__.\nIt's simpler (no dependencies), it only runs on Unix domain sockets (in contrast to Twisted's manhole which\ncan run on telnet or ssh) and it integrates well with various types of applications.\n\n:Documentation: http://python-manhole.readthedocs.org/en/latest/\n\nUsage\n=====\n\nInstall it::\n\n    pip install manhole\n\nYou can put this in your django settings, wsgi app file, some module that's always imported early etc:\n\n.. code-block:: python\n\n    import manhole\n    manhole.install() # this will start the daemon thread\n\n    # and now you start your app, eg: server.serve_forever()\n\nNow in a shell you can do either of these::\n\n    netcat -U /tmp/manhole-1234\n    socat - unix-connect:/tmp/manhole-1234\n    socat readline unix-connect:/tmp/manhole-1234\n\nSocat with readline is best (history, editing etc).\nIf your socat doesn't have readline try `this \u003chttps://launchpad.net/~ionel-mc/+archive/ubuntu/socat\u003e`_.\n\nSample output::\n\n    $ nc -U /tmp/manhole-1234\n\n    Python 2.7.3 (default, Apr 10 2013, 06:20:15)\n    [GCC 4.6.3] on linux2\n    Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n    (InteractiveConsole)\n    \u003e\u003e\u003e dir()\n    ['__builtins__', 'dump_stacktraces', 'os', 'socket', 'sys', 'traceback']\n    \u003e\u003e\u003e print 'foobar'\n    foobar\n\nAlternative client\n------------------\n\nThere's a new experimental ``manhole-cli`` bin since 1.1.0, that emulates ``socat``::\n\n    usage: manhole-cli [-h] [-t TIMEOUT] [-1 | -2 | -s SIGNAL] PID\n\n    Connect to a manhole.\n\n    positional arguments:\n      PID                   A numerical process id, or a path in the form:\n                            /tmp/manhole-1234\n\n    optional arguments:\n      -h, --help            show this help message and exit\n      -t TIMEOUT, --timeout TIMEOUT\n                            Timeout to use. Default: 1 seconds.\n      -1, -USR1             Send USR1 (10) to the process before connecting.\n      -2, -USR2             Send USR2 (12) to the process before connecting.\n      -s SIGNAL, --signal SIGNAL\n                            Send the given SIGNAL to the process before\n                            connecting.\n\n.. end-badges\n\n\nFeatures\n========\n\n* Uses unix domain sockets, only root or same effective user can connect.\n* Can run the connection in a thread or in a signal handler (see ``oneshot_on`` option).\n* Can start the thread listening for connections from a signal handler (see ``activate_on`` option)\n* Compatible with apps that fork, reinstalls the Manhole thread after fork - had to monkeypatch os.fork/os.forkpty for\n  this.\n* Compatible with gevent and eventlet with some limitations - you need to either:\n\n  * Use ``oneshot_on``, *or*\n  * Disable thread monkeypatching (eg: ``gevent.monkey.patch_all(thread=False)``, ``eventlet.monkey_patch(thread=False)``\n\n  Note: on eventlet `you might \u003chttps://github.com/eventlet/eventlet/issues/401\u003e`_ need to setup the hub first to prevent\n  circular import problems:\n\n  .. sourcecode:: python\n\n    import eventlet\n    eventlet.hubs.get_hub()  # do this first\n    eventlet.monkey_patch(thread=False)\n\n* The thread is compatible with apps that use signalfd (will mask all signals for the Manhole threads).\n\nOptions\n-------\n\n.. code-block:: python\n\n    manhole.install(\n        verbose=True,\n        verbose_destination=2,\n        patch_fork=True,\n        activate_on=None,\n        oneshot_on=None,\n        sigmask=manhole.ALL_SIGNALS,\n        socket_path=None,\n        reinstall_delay=0.5,\n        locals=None,\n        strict=True,\n    )\n\n* ``verbose`` - Set it to ``False`` to squelch the logging.\n* ``verbose_destination`` - Destination for verbose messages. Set it to a file descriptor or handle. Default is\n  unbuffered stderr (stderr ``2`` file descriptor).\n* ``patch_fork`` - Set it to ``False`` if you don't want your ``os.fork`` and ``os.forkpy`` monkeypatched\n* ``activate_on`` - Set to ``\"USR1\"``, ``\"USR2\"`` or some other signal name, or a number if you want the Manhole thread\n  to start when this signal is sent. This is desirable in case you don't want the thread active all the time.\n* ``thread`` - Set to ``True`` to start the always-on ManholeThread. Default: ``True``.\n  Automatically switched to ``False`` if ``oneshot_on`` or ``activate_on`` are used.\n* ``oneshot_on`` - Set to ``\"USR1\"``, ``\"USR2\"`` or some other signal name, or a number if you want the Manhole to\n  listen for connection in the signal handler. This is desireable in case you don't want threads at all.\n* ``sigmask`` - Will set the signal mask to the given list (using ``signalfd.sigprocmask``). No action is done if\n  ``signalfd`` is not importable. **NOTE**: This is done so that the Manhole thread doesn't *steal* any signals;\n  Normally that is fine because Python will force all the signal handling to be run in the main thread but signalfd\n  doesn't.\n* ``socket_path`` - Use a specific path for the unix domain socket (instead of ``/tmp/manhole-\u003cpid\u003e``). This disables\n  ``patch_fork`` as children cannot reuse the same path.\n* ``reinstall_delay`` - Delay the unix domain socket creation *reinstall_delay* seconds. This alleviates\n  cleanup failures when using fork+exec patterns.\n* ``locals`` - Names to add to manhole interactive shell locals.\n* ``daemon_connection`` - The connection thread is daemonic (dies on app exit). Default: ``False``.\n* ``redirect_stderr`` - Redirect output from stderr to manhole console. Default: ``True``.\n* ``strict`` - If ``True`` then ``AlreadyInstalled`` will be raised when attempting to install manhole twice.\n  Default: ``True``.\n\nEnvironment variable installation\n---------------------------------\n\nManhole can be installed via the ``PYTHONMANHOLE`` environment variable.\n\nThis::\n\n    PYTHONMANHOLE='' python yourapp.py\n\nIs equivalent to having this in ``yourapp.py``::\n\n    import manhole\n    manhole.install()\n\nAny extra text in the environment variable is passed to ``manhole.install()``. Example::\n\n    PYTHONMANHOLE='oneshot_on=\"USR2\"' python yourapp.py\n\nWhat happens when you actually connect to the socket\n----------------------------------------------------\n\n1. Credentials are checked (if it's same user or root)\n2. ``sys.__std*__``/``sys.std*`` are redirected to the UDS\n3. Stacktraces for each thread are written to the UDS\n4. REPL is started so you can fiddle with the process\n\nKnown issues\n============\n\n* Using threads and file handle (not raw file descriptor) ``verbose_destination`` can cause deadlocks. See bug reports:\n  `PyPy \u003chttps://github.com/pypy/pypy/issues/1895\u003e`_ and `Python 3.4 \u003chttp://bugs.python.org/issue22697\u003e`_.\n\nSIGTERM and socket cleanup\n--------------------------\n\nBy default Python doesn't call the ``atexit`` callbacks with the default SIGTERM handling. This makes manhole leave\nstray socket files around. If this is undesirable you should install a custom SIGTERM handler so ``atexit`` is\nproperly invoked.\n\nExample:\n\n.. code-block:: python\n\n    import signal\n    import sys\n\n    def handle_sigterm(signo, frame):\n        sys.exit(128 + signo)  # this will raise SystemExit and cause atexit to be called\n\n    signal.signal(signal.SIGTERM, handle_sigterm)\n\nUsing Manhole with uWSGI\n------------------------\n\nBecause uWSGI overrides signal handling Manhole is a bit more tricky to setup. One way is to use \"uWSGI signals\" (not\nthe POSIX signals) and have the workers check a file for the pid you want to open the Manhole in.\n\nStick something this in your WSGI application file:\n\n.. sourcecode:: python\n\n    from __future__ import print_function\n    import sys\n    import os\n    import manhole\n\n    stack_dump_file = '/tmp/manhole-pid'\n    uwsgi_signal_number = 17\n\n    try:\n        import uwsgi\n\n        if not os.path.exists(stack_dump_file):\n            open(stack_dump_file, 'w')\n\n        def open_manhole(dummy_signum):\n            with open(stack_dump_file, 'r') as fh:\n                pid = fh.read().strip()\n                if pid == str(os.getpid()):\n                    inst = manhole.install(strict=False, thread=False)\n                    inst.handle_oneshot(dummy_signum, dummy_signum)\n\n        uwsgi.register_signal(uwsgi_signal_number, 'workers', open_manhole)\n        uwsgi.add_file_monitor(uwsgi_signal_number, stack_dump_file)\n\n        print(\"Listening for stack mahole requests via %r\" % (stack_dump_file,), file=sys.stderr)\n    except ImportError:\n        print(\"Not running under uwsgi; unable to configure manhole trigger\", file=sys.stderr)\n    except IOError:\n        print(\"IOError creating manhole trigger %r\" % (stack_dump_file,), file=sys.stderr)\n\n\n    # somewhere bellow you'd have something like\n    from django.core.wsgi import get_wsgi_application\n    application = get_wsgi_application()\n    # or\n    def application(environ, start_response):\n        start_response('200 OK', [('Content-Type', 'text/plain'), ('Content-Length', '2')])\n        yield b'OK'\n\nTo open the Manhole just run `echo 1234 \u003e /tmp/manhole-pid` and then `manhole-cli 1234`.\n\nRequirements\n============\n\n:OS: Linux, OS X\n:Runtime: Python 2.7, 3.4, 3.5, 3.6 or PyPy\n\nSimilar projects\n================\n\n* Twisted's `manhole \u003chttp://twistedmatrix.com/documents/current/api/twisted.conch.manhole.html\u003e`__ - it has colors and\n  server-side history.\n* `wsgi-shell \u003chttps://github.com/GrahamDumpleton/wsgi-shell\u003e`_ - spawns a thread.\n* `pyrasite \u003chttps://github.com/lmacken/pyrasite\u003e`_ - uses gdb to inject code.\n* `pydbattach \u003chttps://github.com/albertz/pydbattach\u003e`_ - uses gdb to inject code.\n* `pystuck \u003chttps://github.com/alonho/pystuck\u003e`_ - very similar, uses `rpyc \u003chttps://github.com/tomerfiliba/rpyc\u003e`_ for\n  communication.\n* `pyringe \u003chttps://github.com/google/pyringe\u003e`_ - uses gdb to inject code, more reliable, but relies on `dbg` python\n  builds unfortunatelly.\n* `pdb-clone \u003chttps://pypi.python.org/pypi/pdb-clone\u003e`_ - uses gdb to inject code, with a `different strategy\n  \u003chttps://code.google.com/p/pdb-clone/wiki/RemoteDebugging\u003e`_.\n","funding_links":[],"categories":["Debugging Tools","资源列表","Python","调试工具","Debugging Tools [🔝](#readme)","\u003ca id=\"324874bb7c3ead94eae6f1fa1af4fb68\"\u003e\u003c/a\u003eDebug\u0026\u0026调试","Awesome Python"],"sub_categories":["调试工具","Other dialects and variants","\u003ca id=\"d22bd989b2fdaeda14b64343b472dfb6\"\u003e\u003c/a\u003e工具","Debugging Tools"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionelmc%2Fpython-manhole","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fionelmc%2Fpython-manhole","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionelmc%2Fpython-manhole/lists"}