Container debugging
In order to observe the GBD WebSuite in it's natural habitat, the container, we can leverage debugpy and the DAP (Debug Adapter Protocol). This allows every editor/IDE that supports DAP to attach to a running python process and set breakpoints and inspect the application state.
I create a folder ~/gws/debug
that contains all required boilerplate:
docker-compose.debug.yaml
server.sh
wsgi_main.py
gws
docker-compose.debug.yml
services:
qgis:
image: gbdconsult/gbd-qgis-server-amd64:3.34.12
container_name: qgis
# ... see other composefiles for missing settings here
gws:
image: gbdconsult/gws-amd64:8.1
container_name: gws
ports:
- "0.0.0.0:3333:80" # default http on 3333
- "0.0.0.0:5000:5000" # forward mpx port (optional)
- "0.0.0.0:5678:5678" # debug adapter port
volumes:
- ${GWS_PROJECT_DIR}/gws-${GWS_PROJECT_NAME}/data:/data:ro
- ${GWS_VAR_DIR}:/gws-var
- ${GWS_PROJECT_DIR}/gbd-websuite/app:/gws-app
# this is only relevant for client-side plugin development
- /plugins:/plugins
# this allows us to inject debug capabilities into the container
- .:/debug
# here we call our custom start script
command: ["/debug/gws", "server", "start"]
tmpfs:
- /tmp
environment:
- GWS_CONFIG=/data/config/local.cx
- GWS_MANIFEST=/data/MANIFEST.json
- GWS_LOG_LEVEL=DEBUG
- PG_SERVICEFILE=/data/pg_service.local.conf
# these might fix problems when starting/attaching to debug server
- GWS_WEB_WORKERS=1
- PYDEVD_LOAD_NATIVE_LIB=0
- PYDEVD_USE_CYTHON=0
uwsgi_web.ini
This will replace the /gws-var/server/uwsgi_web.ini
with a debugfriendly version, where we enforce only one thread, and don't kill our worker after 60s
Ensure that you make the following changes, rest stays the same:
[uwsgi]
daemonize = false
threads = 1
# harakiri-verose = true
# harakiri = 60
processes = 1
wsgi-file = /debug/wsgi_main.py
honour-stdin = true
single-interpreter = true
I'm not 100% sure if honour-stdin and single-interpreter are neccessary, needs testing.
server.sh
This will replace /gws-var/server.sh
, as this is where uwsgi is started, and we need to pass the path to our uwsgi_web.ini
rsyslogd -i /tmp/gws/pids/rsyslogd.pid -f /gws-var/server/syslog.conf
uwsgi --ini /debug/uwsgi_web.ini
uwsgi --ini /gws-var/server/uwsgi_mapproxy.ini
uwsgi --ini /gws-var/server/uwsgi_spool.ini
exec nginx -c /gws-var/server/nginx.conf
wsgi_main.py
This is the entry point of the application for uwsgi. We can start the debug server in this file, which allows us to attach to the application from our IDE:
import debugpy
# the in_process_debug_adapter=True is important because of uwsgi shenaningans
# the 0.0.0.0 ip is important, because of container port forwarding
debugpy.listen(("0.0.0.0", 5678), in_process_debug_adapter=True)
# if we want to immediatly hold and wait until we manually attach from our ide
# we can add
# debugpy.wait_for_client()
import gws.base.web.wsgi_app as wsgi_app
application = wsgi_app.application
We could also add the debugpy related lines to any other python file, for example inside a plugin which only purpose is to enable debugging, but this way we have the option to wait for debug adapter client attaching, and set a breakpoint during import of gws.base.web.wsgi_app
gws
This replaces the /gws-app/bin/gws
script, and is the file we define as entry command in our docker-compose.yml. Modify the original script as follows:
First thing we want to do is ensure debugpy is installed
#!/bin/bash
pip install debugpy
...
To debug the configuration step, replace the following lines:
$PYTHON $MAIN_PY "$@"
with
$PYTHON -m debugpy --listen 0.0.0.0:5678 --wait-for-client $MAIN_PY "$@"
To debug the running application behind uwsgi point the exec
calls to our /debug/server.sh
which handles starting the uwsgi and nginx processes.
Running the Container
just like any other container:
docker compose -f docker-compose.debug.yml up -d qgis
and
docker compose -f docker-compose.debug.yml up gws
depending on if you have told debugpy to wait-for-client during configuration, you will have to connect before the application starts, and then connect again when the uwsgi app is running
Attaching from VSCode
in your .vscode/launch.json
you can create a configuration as follows:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}/app",
"remoteRoot": "/gws-app"
}
]
}
]
}
ensure you have correct path mappings in order to be able to set breakpoints
you can set a breakpoint before attaching, to halt instantly after a debugpy.wait_for_client()
line
connecting from different workspace (plugin directory) in vscode
if you have a plugin as it's own workspace you can add a path mapping like
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/plugins/temporal"
},
{
"localRoot": "/home/<user>/gws/gbd-websuite/app",
"remoteRoot": "/gws-app"
}
in order to debug the plugin code, and also be able to jump into gws functions
in that case you might also want to add the following extra paths in your .vscode/settings.json
{
"python.autoComplete.extraPaths": [
"/home/<user>/gws/gbd-websuite/app/"
],
"python.analysis.extraPaths": [
"/home/<user>/gws/gbd-websuite/app/"
],
}
and add a tsconfig.json
like this to your workspace root for typescript support
{
"extends": "../../gbd-websuite/app/js/tsconfig.json",
}