dbgr is a full featured web debugger based on a client-server architecture, similar in spirit to pudb but running as a web application instead of a terminal UI. It lets you step through code, inspect locals/globals in a live tree view, set breakpoints, and evaluate Python expressions, all from your browser.
This project is a continuation of the original wdb
client/server debugger by Florian Mounier (Kozea), and of
wdb_server_aiohttp,
Shepilov Vladislav's aiohttp-based rewrite of the original tornado server.
Both of those projects had gone stale (last releases several years old, built
on stacks that have since been deprecated), so dbgr picks up where they
left off: the server was migrated from aiohttp to FastAPI, the frontend
editor was upgraded from CodeMirror 5 to CodeMirror 6, and a pudb-style
variables/watches panel was added next to the source and console panes.
This is an actively maintained fork meant to keep the project usable on current Python and browser versions. Contributions, bug reports, and ideas for where to take it next are welcome — see Contribute below.
aiohttp->FastAPI/uvicornserver- CodeMirror 5 -> CodeMirror 6 editor
- Added a pudb-style variables/watches panel (locals, globals, and expandable containers) next to the source and console panes
tornado->aiohttp(inherited fromwdb_server_aiohttp) ->FastAPI- Coffeescript -> Typescript
- Bower -> npm/yarn
- Grunt -> Webpack
- yapf -> black
- Support for themes on any screen size
- Support for Safari
- Works without internet access (the original project required Google Fonts from a CDN)
- Renamed packages/imports:
wdb->dbgr,wdb.server->dbgr-server
dbgr is a full featured web debugger based on a client-server architecture.
The dbgr server, which is responsible for managing debugging instances along with browser connections (through websockets), is based on FastAPI. The dbgr client allows step by step debugging, in-program python code execution, code edition (based on CodeMirror), and setting breakpoints.
Due to this architecture, all of this is fully compatible with multithread and multiprocess programs.
dbgr works with Python 3. It is possible to debug a program running on one computer with a debugging server running on another computer, inside a web page on a third computer.
It is also possible to pause a currently running python process/thread using
code injection from the web interface (this requires gdb and ptrace
enabled).
In other words, it's a very enhanced version of pdb directly in your
browser, with nice features.
Install the server (this pulls in the web interface and the debugging protocol server):
$ pip install dbgr-serverIn each virtualenv/interpreter you want to debug, install the client:
$ pip install dbgr(You must have the server installed and running somewhere reachable from the process you're debugging.)
To try dbgr, first start the dbgr server:
$ dbgr.server.py &Next run:
$ python -m dbgr your_file.pyDbgr will open a debugging window right in your browser, paused at the beginning of your program.
You can access http://localhost:1984/ to have an overview of the server.
NB: You have to start the server only once. Multiple debugging sessions can be run simultaneously without problem.
This is not the only way to debug a program, see below.
To debug any program, with the server on, just add:
import dbgr
dbgr.set_trace()anywhere in your code. Your program will stop at the set_trace line (just
like pdb).
To inspect your code on exception, you can do the following:
from dbgr import trace
with trace():
wrong_code()Any exception during wrong_code will launch a debugging session.
You can also use the start_trace() and stop_trace() methods (put
stop_trace in a finally block to avoid tracing the rest of your program
after an exception).
dbgr provides some tools to make it work nicely with different web servers:
For WSGI servers you can use the DbgrMiddleware:
from dbgr.ext import DbgrMiddleware
wsgi_app = Whatever_wsgi_server_lib()
my_app = DbgrMiddleware(wsgi_app)
my_app.serve_forever()from flask import Flask
from dbgr.ext import DbgrMiddleware
app = Flask(__name__)
app.debug = True
app.wsgi_app = DbgrMiddleware(app.wsgi_app)
app.run(use_debugger=False) # Disable builtin Werkzeug debuggerAdd the middleware in your wsgi.py, after:
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()add:
from dbgr.ext import DbgrMiddleware
application = DbgrMiddleware(application)And in your settings.py, activate exception propagation:
DEBUG = True
DEBUG_PROPAGATE_EXCEPTIONS = Trueimport cherrypy
from dbgr.ext import DbgrMiddleware
class HelloWorld(object):
@cherrypy.expose
def index(self):
undefined_method() # This will fail
return "Hello World!"
cherrypy.config.update({'global': {'request.throw_errors': True}})
app = cherrypy.Application(HelloWorld())
app.wsgiapp.pipeline.append(('debugger', DbgrMiddleware))
cherrypy.quickstart(app)In Tornado, which is not a WSGI server, you can use the dbgr_tornado
function, which will monkey-patch the execute method on RequestHandlers:
from dbgr.ext import dbgr_tornado
from tornado.web import Application
my_app = Application([(r"/", MainHandler)])
if options.debug:
dbgr_tornado(my_app)
my_app.listen(8888)If dbgr slows down your application too much (tracing everything takes time), you can start it disabled with:
my_app = DbgrMiddleware(wsgi_app, start_disabled=True) # or
dbgr_tornado(my_app, start_disabled=True)Then when you get an exception just click on the on/off button.
You can easily do remote debugging with dbgr:
Let's say you want to run a program p.py on computer A and you want to
debug it on computer B.
Start the dbgr server on computer A and launch this:
DBGR_NO_BROWSER_AUTO_OPEN=True python -m dbgr p.pyAnd open a browser on computer B at the url given by the dbgr log.
Now you can also run the dbgr server on a computer C and run on computer A:
DBGR_NO_BROWSER_AUTO_OPEN=True DBGR_SOCKET_SERVER=computerC.addr DBGR_SOCKET_PORT=19840 python -m dbgr p.pyAnd go with computer B to http://computerC/debug/session/[uuid in log],
where you can step into p.py running on computer A.
You can use different configurations. See dbgr.server.py --help for
changing ports on the server, and these environment variables for dbgr
instances:
DBGR_SOCKET_SERVER # dbgr server host
DBGR_SOCKET_PORT # dbgr server socket port
DBGR_WEB_SERVER # dbgr server host for browser opening
DBGR_WEB_PORT # dbgr server http port
DBGR_NO_BROWSER_AUTO_OPEN # Disable automatic browser opening (needed if the browser is not on the same machine)
This repo ships a Containerfile (works with both podman build and
docker build -f Containerfile) and a compose.yml to run the dbgr server
in a container.
Quick start:
$ podman compose up --build # or: docker compose up --buildThis builds the image and starts the dbgr-server service, publishing:
1984— the web UI (openhttp://localhost:1984to see running sessions)19840— the socket port dbgr clients connect to
Or build/run it manually without compose:
$ podman build -f Containerfile -t dbgr-server .
$ podman run --rm -p 1984:1984 -p 19840:19840 dbgr-serverThe basic setup for debugging an app that runs in its own container looks like this:
- Start the
dbgr-servercontainer (viacompose.ymlor manually), with port1984published to your host — this serves the debugging web UI. - In your app container, install the
dbgrclient (pip install dbgr) and set theDBGR_*environment variables below so it can reach thedbgr-servercontainer over the socket port (19840). - When a trace is reached, open the URL dbgr prints (or
http://localhost:1984directly) to step through the code.
These are read by the dbgr client (the library you pip install and
import in the process you're debugging) — set them on whatever container
runs your application, not on dbgr-server itself:
| Variable | Default | Meaning |
|---|---|---|
DBGR_SOCKET_SERVER |
localhost |
Host of the dbgr-server socket endpoint the client connects to. In compose.yml, use the service name (e.g. dbgr-server) so it resolves inside the docker network. |
DBGR_SOCKET_PORT |
19840 |
Port of that socket endpoint. |
DBGR_WEB_SERVER |
(falls back to DBGR_SOCKET_SERVER) |
Host used to build the "open this in your browser" link. Set this to whatever hostname your browser can resolve (usually localhost if ports are published to the host) — it does not need to match DBGR_SOCKET_SERVER. |
DBGR_WEB_PORT |
1984 |
Port of the dbgr-server web UI, used in that same link. |
DBGR_NO_BROWSER_AUTO_OPEN |
False |
Set to True inside containers — there's no browser to auto-open, and dbgr will otherwise try (and fail) to launch one. Use the printed URL instead. |
DBGR_LOG |
WARNING |
Log level for all dbgr client loggers. |
DBGR_<NAME>_LOG |
DBGR_LOG |
Per-logger override; NAME is one of MAIN, TRACE, UI, EXT, BP (e.g. DBGR_TRACE_LOG=DEBUG). |
compose.yml includes a commented-out example app service showing how to
wire these up against the dbgr-server service defined next to it.
Example minimal docker-compose.yml, starting from
the official example for using Docker with Django:
services:
db:
image: postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
- dbgr-server
environment:
DBGR_SOCKET_SERVER: dbgr-server
DBGR_WEB_SERVER: localhost
DBGR_NO_BROWSER_AUTO_OPEN: "True"
dbgr-server:
build:
context: .
dockerfile: Containerfile
ports:
- "1984:1984"
- "19840:19840"Add dbgr to your requirements.txt in your web app:
$ echo 'dbgr' >> requirements.txtNow you can use dbgr.set_trace() in your python app:
# ... some code
import dbgr
dbgr.set_trace()Then rebuild your web application and start everything up again:
$ podman compose stop
$ podman compose up --buildNow you can access http://localhost:1984 to see the traces as they come up
in your app.
Once you are in a breakpoint or in an exception, you can eval anything you
want in the prompt under the code. Multi-lines are partially supported using
[Shift] + [Enter]. There is help available by clicking the top help button.
As of now, the following special commands are supported during a breakpoint:
.s or [Ctrl] + [↓] or [F11] : Step into
.n or [Ctrl] + [→] or [F10] : Step over (Next)
.r or [Ctrl] + [↑] or [F9] : Step out (Return)
.c or [Ctrl] + [←] or [F8] : Continue
.u or [F7] : Until (Next over loops)
.j lineno : Jump to lineno (must be at bottom frame and in the same function)
.b arg : Set a session breakpoint, see below for what arg can be
.t arg : Set a temporary breakpoint, arg follows the same syntax as .b
.z arg : Delete existing breakpoint
.l : List active breakpoints
.f : Echo all typed commands in the current debugging session
.d expression : Dump the result of expression in a table
.w expression : Watch expression in current file (click on the name to remove)
.q : Quit
.h : Get some help
.e : Toggle file edition mode
.g : Clear prompt
.i [mime/type;]expression : Display the result in an embed; mime type is auto-detected on Linux and defaults to "text/html" otherwise
iterable!sthg : If cutter is installed, executes cut(iterable).sthg
expr >! file : Write the result of expr in file
!< file : Eval the content of file
[Enter] : Eval the current selected text on the page, useful to eval code in the source
* arg follows this syntax:
[file/module][:lineno][#function][,condition]
which means:
- [file] : Break if any line of `file` is executed
- [file]:lineno : Break on `file` at `lineno`
- [file][:lineno],condition : Break on `file` at `lineno` if `condition` is True (e.g.: i == 10)
- [file]#function : Break when inside `function`
File is always the current file by default; you can also specify a module
like `logging.config`.
You can also eval a variable in the source by middle-clicking on it. You can add/remove a breakpoint by clicking on the line number.
NB: Hotkeys with arrows are purposely not triggered in the eval prompt, to avoid conflicts when typing.
To see which debugging sessions are currently open, open your browser at http://localhost:1984/. You can also close crashed sessions.
From there you should also see all Python processes and their threads
running, and you can try to pause them during execution to do step by step
debugging and current variable inspection. This is highly experimental and
requires gdb and a kernel with ptrace enabled to inject python code into a
running python process. If you get ptrace: Operation not permitted. you
will have to enable it.
Depending on your system it might work with:
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scopeMake sure that dbgr is installed for the python version running the
program too.
To avoid that, you can add a w builtin at the beginning of your
application:
from dbgr.ext import add_w_builtin
add_w_builtin()you can now use the w object anywhere in your code:
my_code()
w.tf # Stop next line
doubtful_code()my_code()
with w.trace():
doubtful_code()dbgr has dynamic code completion in the eval prompt thanks to jedi.
It's a Firefox config flag; visit about:config and set
dom.allow_scripts_to_close_windows to true.
If your logging configuration is set to display DEBUG logs, you may see a log for every imported file in your project any time dbgr is active, like so:
DEBUG 2017-07-16 13:15:03,772 index 49835 123145573191680 parsing Python module /project/.virtualenv/python-3.6.1/lib/python3.6/site-packages/package/file.py for indexing
To silence only this message, add a config for the importmagic module. For
example:
LOGGING = {
...
'loggers': {
...
'importmagic.index': {
'level': 'ERROR',
'propagate': False,
},
},
}This project picks up an old, abandoned debugger and brings it back to a
modern stack, but there's plenty left to do (see TODO.org): finishing the
CodeMirror 6 migration polish, expanding the variables/watches panel,
covering the frontend with unit tests, adding frontend/backend integration
tests, and multithread/multiprocess/async debugging support are all open.
All contributions are more than welcome — fork it and send a PR.
- Florian Mounier @ Kozea — original author of
wdb - Shepilov Vladislav — author of
wdb_server_aiohttp - arkhan — current maintainer of
dbgr
This library is licensed under GPLv3