Debugging Plone on WSGI#
When debugging Plone behind a WSGI server, there are a couple of things to remember.
The wsgitraining.site
package contained in our buildout comes with a debugging view that sets a breakpoint:
class DebuggingView(BrowserView):
def __call__(self):
self.msg = _(u'A small message')
import pdb; pdb.set_trace()
return self.index()
It is available at http://localhost:8080/Plone/debugging-view.
It also provides a very basic view that raises an AttributeError
available at http://localhost:8080/Plone/attribute-error-view.
Debugging with waitress#
Debugging with waitresss doesn't feel different from what you know from ZServer. Limiting to one worker thread might make debugging easer for you.
The widely used pdbpp
, Products.PdbDebugMode
, plone.app.debugtoolbar
and Products.enablesettrace
add-ons also work as expected.
werkzeug debugging#
The werkzeug WSGI server provides an additional debugging WSGI middleware that renders tracebacks and also provides an interactive debug console. To quote from this blog post this is basically remote remote code execution by design, so never use the werkzeug debugger in production (although the original vulnerability has been mitigated by introducing a debugger PIN).
To get this working for Plone we first have to disable our standard exception views.
We can do this by means of an additional option in the [instance]
buildout part:
...
[instance]
recipe = plone.recipe.zope2instance
user = admin:admin
zeo-client = on
zeo-address = 8100
shared-blob = on
blob-storage = ${buildout:directory}/var/blobstorage
debug-exceptions = on
eggs =
Plone
Pillow
wsgitraining.site
dataflake.wsgi.werkzeug
wsgi-ini-template = ${buildout:directory}/templates/werkzeugdebugger.ini.in
We also have to specify the correct entry point in the ini
template to use the werkzeug debugger:
[server:main]
use = egg:dataflake.wsgi.werkzeug#debugger
hostname = 127.0.0.1
port = 8080
...
Start the instance with bin/instance fg
.
Use the attribute-error-view
from the training add-on to see a traceback rendered by werkzeug debugger.
If you move the mouse to the right of a stack frame, you will see a symbol of a terminal.
Click on it.
You will be prompted for a PIN once.
Enter the PIN provided on the console where you started the instance.
You will see an interactive console in the browser where you can enter arbitrary Python code.
Debugging uWSGI#
When trying to open the debugging view in uWSGI, you will see an error message in both console and the browser instead of the pdb prompt.
> /home/thomas/devel/plone/wsgitraining_buildout/src/wsgitraining.site/src/wsgitraining/site/views/debugging_view.py(18)__call__()
-> return self.index()
(Pdb)
ERROR:Zope.SiteErrorLog:1570455986.90912460.23307293753294422 http://localhost:8080/Plone/debugging-view
Traceback (innermost last):
Module ZPublisher.WSGIPublisher, line 155, in transaction_pubevents
Module ZPublisher.WSGIPublisher, line 337, in publish_module
Module ZPublisher.WSGIPublisher, line 255, in publish
Module ZPublisher.mapply, line 85, in mapply
Module ZPublisher.WSGIPublisher, line 61, in call_object
Module wsgitraining.site.views.debugging_view, line 18, in __call__
Module wsgitraining.site.views.debugging_view, line 18, in __call__
Module bdb, line 88, in trace_dispatch
Module bdb, line 113, in dispatch_line
bdb.BdbQuit
To make uWSGI stop at the pdb.set_trace()
you need to start it with the honour-stdin
flag set to true
.
This flag will prevent uWSGI from redirecting stdin
to /dev/null
, which is the default behaviour.
You can do so by modifying the inline template in the [uwsgiini]
part and rerun buildout.
...
[uwsgiini]
recipe = collective.recipe.template
input = inline:
[uwsgi]
http-socket = 0.0.0.0:8080
socket = 127.0.0.1:8081
chdir = ${buildout:directory}/bin
module = wsgi:application
master = false
honour-stdin = true
enable-threads = true
processes = 1
threads = 1
output = ${buildout:directory}/etc/uwsgi.ini
...
After running buildout and starting your instance with bin/uwsgi-instance
you will see an interactive console and uWSGI will not serve any requests at first (the browser will hang forever instead of showing a page).
*** Operational MODE: single process ***
Class Products.CMFFormController.ControllerPythonScript.ControllerPythonScript has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs'
Class Products.CMFFormController.ControllerValidator.ControllerValidator has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs'
/home/thomas/.buildout/eggs/cp37m/pyScss-1.3.5-py3.7-linux-x86_64.egg/scss/selector.py:54: FutureWarning: Possible nested set at position 329
''', re.VERBOSE | re.MULTILINE)
WARNING:plone.behavior:Specifying 'for' in behavior 'Tiles' if no 'factory' is given has no effect and is superfluous.
>>>
Press Ctrl+D
to continue the instance startup:
>>>
now exiting InteractiveConsole...
WSGI app 0 (mountpoint='') ready in 52 seconds on interpreter 0x55fe3f766d30 pid: 7018 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 7018, cores: 1)
...
Now if you open the debugging-view
again you will see the pdb
prompt.
All looks fine now, however you will not be able to terminate the instance with Ctrl+C
.
Instead you can press Ctrl+Z
to send the instance to the background and then kill it with kill %1
(or whatever job number you're seeing on the console).
This behaviour is the reason why we don't put honour-stdin
in the .ini
template by default.