From f39f2bcab263904573dc0e064a568af7a3c45f4f Mon Sep 17 00:00:00 2001 From: Onxx-datas Date: Mon, 6 Jul 2026 16:33:12 +0500 Subject: [PATCH 1/4] Added documentation on panel instrumentation for production use (#2203) --- docs/index.rst | 1 + docs/production.rst | 231 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 docs/production.rst diff --git a/docs/index.rst b/docs/index.rst index 48c217b1a..4f2251803 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Django Debug Toolbar configuration checks tips + production panels commands resources diff --git a/docs/production.rst b/docs/production.rst new file mode 100644 index 000000000..96c25ff88 --- /dev/null +++ b/docs/production.rst @@ -0,0 +1,231 @@ +Installing in production +========================= + +.. _production-usage: + +It's common to want to install the Django Debug Toolbar in an environment +that also serves production (or production-like) traffic, and rely on +:ref:`SHOW_TOOLBAR_CALLBACK ` (or ``DEBUG``) to decide, per request, +whether the toolbar is actually shown. This page documents what actually +happens when the toolbar is installed but nt shown, so you can make an +informed decision about whether that's safe for your deployment. + +.. important:: + + ``SHOW_TOOLBAR_CALLBACK`` only controls whether the toolbar is *rendered + and populated* for a given request. It does **not** prevent every panel + from touching global state at process start time. Some panels install + their instrumentation (which sometimes means monkey-patching Django or + third-party code) as soon as the ``debug_toolbar`` app is loaded, + regardless of ``SHOW_TOOLBAR_CALLBACK``, ``DISABLE_PANELS``, or whether + any request ever triggers the toolbar. The only way to avoid that + instrumentation entirely is to remove the panel from + ``DEBUG_TOOLBAR_PANELS`` so its module is never imported. + +How instrumentation is set up +------------------------------ + +Each panel can hook into three different points in its lifecycle: + +* :meth:`~debug_toolbar.panels.Panel.ready`, a classmethod called once for + every panel listed in ``DEBUG_TOOLBAR_PANELS``, when the + ``debug_toolbar`` app is loaded (i.e. at process/interpreter start, + regardless of ``DEBUG``, ``SHOW_TOOLBAR_CALLBACK``, or ``DISABLE_PANELS``). +* Module improt side effects. A few panels install patches directly at the + top of their module, outside of any method. Because + ``DebugToolbar.get_panel_classes()`` + imports every panel listed in ``DEBUG_TOOLBAR_PANELS`` while + building the panel list, these patches run at the same time as ``ready()`` + and are just as unconditional. +* :meth:`~debug_toolbar.panels.Panel.enable_instrumentation` / + :meth:`~debug_toolbar.panels.Panel.disable_instrumentation`, called by + ``debug_toolbar.middleware.DebugToolbarMiddleware`` at the start and + end of a request, but **only** when ``SHOW_TOOLBAR_CALLBACK`` returns + ``True`` for that request *and* the panel iself is enabled (i.e. not + listed in ``DISABLE_PANELS`` and not turned off via the panel's cookie). + +``DISABLE_PANELS`` stops a panel from being included in +``toolbar.enabled_panels``, which means its ``enable_instrumentation()`` and +``disable_instrumentation()`` are never called. It has no effect on +``ready()`` or on import-time patches: those already ran once for every +panel in ``DEBUG_TOOLBAR_PANELS`` before any request was processed. + +Audit summary +-------------- + +The table below summarizes, for each built-in panel, when its +instrumentation is installed, whether it can be disabled entirely (i.e. +avoided without removing the panel from ``DEBUG_TOOLBAR_PANELS``), and +whether it monkey-patches anything. + +.. list-table:: + :header-rows: 1 + :widths: 18 42 20 20 + + * - Panel + - When instrumentation is installed + - Fully avoidable without removing from ``DEBUG_TOOLBAR_PANELS``? + - Monkey-patches? + + * - :class:`~debug_toolbar.panels.cache.CachePanel` + - ``ready()`` permanently wraps + ``CacheHandler.create_connection`` at startup so that any cache + connection opende while a request is being instrumented gets + wrapped too. ``enable_instrumentation()`` then wraps the methods + (``get``, ``set``, ``delete``, etc.) of every already-open cache + connection, per request. + - No. The ``create_connection`` wrap installed by ``ready()`` is + permanent for the life of the process. + - Yes. Wraps ``CacheHandler.create_connection`` and the individual + cache backend methods listed in ``CachePanel.WRAPPED_CACHE_METHODS``. + + * - :class:`~debug_toolbar.panels.sql.SQLPanel` + - Only in ``enable_instrumentation()``, called per request. + There is no ``ready()`` hook. + - Yes. With no request enabling the panel, nothing is patched. + - Yes, but only while active. Wraps ``connection.cursor()`` and + ``connection.chunked_cursor()`` for each open database connection + for the duration of the request. + + * - :class:`~debug_toolbar.panels.templates.TemplatesPanel` + - At **import time** (module-level code, not even ``ready()``). + ``Template._render`` and ``RequestContext.bind_template`` are + replaced as soon as the module is imported, and the Jinja2 + ``Template.render`` method is patched too if ``jinja2`` is + installed. ``enable_instrumentation()`` only connects the + ``template_rendered`` signal receiver per request. + - No. Because the patch is a side effect of importing the module, + it happens as soon as ``TemplatesPanel`` is loaded from + ``DEBUG_TOOLBAR_PANELS``. + - Yes. Patches ``django.template.base.Template._render``, + ``django.template.context.RequestContext.bind_template``, and + (conditionally) ``django.template.backends.jinja2.Template.render``. + + * - :class:`~debug_toolbar.panels.staticfiles.StaticFilesPanel` + - ``ready()`` permanently mixes an ``URLMixin`` class into + ``staticfiles_storage.__class__.__bases__`` at startup. + ``enable_instrumentation()`` connects a signal receiver and sets a + ``contextvar`` per request. + - No. The storage class hierarchy is changed once, at strtup, and + is not reverted. + - Yes. Mutates the ``__bases__`` of the configured staticfiles + storage class. + + * - :class:`~debug_toolbar.panels.profiling.ProfilingPanel` + - Only in ``process_request()``, and only when the panel is enabled + for the current request. There is no ``ready()`` hook. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.redirects.RedirectsPanel` + - Only in ``process_request()``/``aprocess_request()``, and only + when the panel is enabled for the current request. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.history.HistoryPanel` + - No instrumentation; reads data already collected by other panels. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.versions.VersionsPanel` + - No instrumentation. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.timer.TimerPanel` + - No instrumentation. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.settings.SettingsPanel` + - No instrumentation. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.headers.HeadersPanel` + - No instrumentation. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.request.RequestPanel` + - No instrumentation. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.alerts.AlertsPanel` + - No instrumentation. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.signals.SignalsPanel` + - No instrumentation; reads signal receivers already registered by + Django/your project at ``generate_stats()`` time. + - Yes. + - No. + + * - :class:`~debug_toolbar.panels.community.CommunityPanel` + - No instrumentation. + - Yes. + - No. + +Recommendation for production-like environments +------------------------------------------------- + +If you install the toolbar in an environment that aslo serves real traffic, +and you rely on ``SHOW_TOOLBAR_CALLBACK`` to gate it, the panels below are +safe to leave in ``DEBUG_TOOLBAR_PANELS`` because they only touch +global state while actively enabled for a request: + +* ``HistoryPanel`` +* ``VersionsPanel`` +* ``TimerPanel`` +* ``SettingsPanel`` +* ``HeadersPanel`` +* ``RequestPanel`` +* ``SQLPanel`` +* ``AlertsPanel`` +* ``SignalsPanel`` +* ``CommunityPanel`` +* ``RedirectsPanel`` (disabled by default) +* ``ProfilingPanel`` (disabled by default) + +``CachePanel``, ``TemplatesPanel``, and ``StaticFilesPanel`` install +permanent, process-wide monkey-patches the moment they are imported +(``ready()`` or module import), *before* any request is ever processed, and +those patches stay in place even if ``SHOW_TOOLBAR_CALLBACK`` always returns +``False`` and even if the panel is listed in ``DISABLE_PANELS``. If you need +to guarantee that the toolbar leaves the codebase's runtime behavior +completely untouched, remove those three panels from +``DEBUG_TOOLBAR_PANELS`` in the deployment(s) where that matters, for +example by building the setting conditionally:: + + DEBUG_TOOLBAR_PANELS = [ + panel + for panel in [ + "debug_toolbar.panels.history.HistoryPanel", + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.alerts.AlertsPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.community.CommunityPanel", + ] + if not IS_PRODUCTION_LIKE + or panel + not in { + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.cache.CachePanel", + } + ] + +Keep in mind tht this trims functionality: the Templates, Static files, and +Cache panels will not be available at all, even when the toolbar is shown, +in environments where they've been excluded this way. From 18604e598d1309ce7f3dcaf4de144bd00ee39880 Mon Sep 17 00:00:00 2001 From: onxxdatas Date: Sun, 19 Jul 2026 19:20:07 +0500 Subject: [PATCH 2/4] Fix doc8 line length and typo in production.rst --- docs/production.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/production.rst b/docs/production.rst index 96c25ff88..c1eb8ebab 100644 --- a/docs/production.rst +++ b/docs/production.rst @@ -5,10 +5,11 @@ Installing in production It's common to want to install the Django Debug Toolbar in an environment that also serves production (or production-like) traffic, and rely on -:ref:`SHOW_TOOLBAR_CALLBACK ` (or ``DEBUG``) to decide, per request, -whether the toolbar is actually shown. This page documents what actually -happens when the toolbar is installed but nt shown, so you can make an -informed decision about whether that's safe for your deployment. +:ref:`SHOW_TOOLBAR_CALLBACK ` (or ``DEBUG``) to +decide, per request, whether the toolbar is actually shown. This page +documents what actually happens when the toolbar is installed but not +shown, so you can make an informed decision about whether that's safe +for your deployment. .. important:: From eb44c3aa5ab2105e91344fd1d17160e767d5f258 Mon Sep 17 00:00:00 2001 From: onxxdatas Date: Sun, 19 Jul 2026 22:42:51 +0500 Subject: [PATCH 3/4] Fix typos and add words to spelling wordlist in production.rst --- docs/production.rst | 12 ++++++------ docs/spelling_wordlist.txt | 22 ++++++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/production.rst b/docs/production.rst index c1eb8ebab..e15d04717 100644 --- a/docs/production.rst +++ b/docs/production.rst @@ -32,7 +32,7 @@ Each panel can hook into three different points in its lifecycle: every panel listed in ``DEBUG_TOOLBAR_PANELS``, when the ``debug_toolbar`` app is loaded (i.e. at process/interpreter start, regardless of ``DEBUG``, ``SHOW_TOOLBAR_CALLBACK``, or ``DISABLE_PANELS``). -* Module improt side effects. A few panels install patches directly at the +* Module import side effects. A few panels install patches directly at the top of their module, outside of any method. Because ``DebugToolbar.get_panel_classes()`` imports every panel listed in ``DEBUG_TOOLBAR_PANELS`` while @@ -42,7 +42,7 @@ Each panel can hook into three different points in its lifecycle: :meth:`~debug_toolbar.panels.Panel.disable_instrumentation`, called by ``debug_toolbar.middleware.DebugToolbarMiddleware`` at the start and end of a request, but **only** when ``SHOW_TOOLBAR_CALLBACK`` returns - ``True`` for that request *and* the panel iself is enabled (i.e. not + ``True`` for that request *and* the panel itself is enabled (i.e. not listed in ``DISABLE_PANELS`` and not turned off via the panel's cookie). ``DISABLE_PANELS`` stops a panel from being included in @@ -71,7 +71,7 @@ whether it monkey-patches anything. * - :class:`~debug_toolbar.panels.cache.CachePanel` - ``ready()`` permanently wraps ``CacheHandler.create_connection`` at startup so that any cache - connection opende while a request is being instrumented gets + connection opened while a request is being instrumented gets wrapped too. ``enable_instrumentation()`` then wraps the methods (``get``, ``set``, ``delete``, etc.) of every already-open cache connection, per request. @@ -107,7 +107,7 @@ whether it monkey-patches anything. ``staticfiles_storage.__class__.__bases__`` at startup. ``enable_instrumentation()`` connects a signal receiver and sets a ``contextvar`` per request. - - No. The storage class hierarchy is changed once, at strtup, and + - No. The storage class hierarchy is changed once, at startup, and is not reverted. - Yes. Mutates the ``__bases__`` of the configured staticfiles storage class. @@ -173,7 +173,7 @@ whether it monkey-patches anything. Recommendation for production-like environments ------------------------------------------------- -If you install the toolbar in an environment that aslo serves real traffic, +If you install the toolbar in an environment that also serves real traffic, and you rely on ``SHOW_TOOLBAR_CALLBACK`` to gate it, the panels below are safe to leave in ``DEBUG_TOOLBAR_PANELS`` because they only touch global state while actively enabled for a request: @@ -227,6 +227,6 @@ example by building the setting conditionally:: } ] -Keep in mind tht this trims functionality: the Templates, Static files, and +Keep in mind that this trims functionality: the Templates, Static files, and Cache panels will not be available at all, even when the toolbar is shown, in environments where they've been excluded this way. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index fe5658565..9d9cb8ab3 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,11 +1,3 @@ -Hatchling -Hotwire -Jazzband -Makefile -Pympler -Roboto -Transifex -Werkzeug aenable ajax asgi @@ -16,6 +8,7 @@ backported biome booleans checkbox +codebase contrib csp debounce @@ -26,16 +19,21 @@ fallbacks flamegraph flatpages frontend +Hatchling +Hotwire htmx inlining instantiation instrumentation isort -jQuery +Jazzband jinja +jQuery jrestclient js +lifecycle lookups +Makefile margins memcache memcached @@ -55,12 +53,14 @@ py pyenchant pyflame pylibmc +Pympler pytest pyupgrade querysets refactoring reinitializing resizing +Roboto runserver spellchecking sphinxcontrib @@ -72,7 +72,9 @@ staticfiles theming timeline tox -uWSGI +Transifex unhandled unhashable +uWSGI validator +Werkzeug From 0551f319509df0edddbec60e138cd7e8ba18c1f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:44:30 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/spelling_wordlist.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 9d9cb8ab3..408d27547 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,3 +1,11 @@ +Hatchling +Hotwire +Jazzband +Makefile +Pympler +Roboto +Transifex +Werkzeug aenable ajax asgi @@ -19,21 +27,17 @@ fallbacks flamegraph flatpages frontend -Hatchling -Hotwire htmx inlining instantiation instrumentation isort -Jazzband -jinja jQuery +jinja jrestclient js lifecycle lookups -Makefile margins memcache memcached @@ -53,14 +57,12 @@ py pyenchant pyflame pylibmc -Pympler pytest pyupgrade querysets refactoring reinitializing resizing -Roboto runserver spellchecking sphinxcontrib @@ -72,9 +74,7 @@ staticfiles theming timeline tox -Transifex +uWSGI unhandled unhashable -uWSGI validator -Werkzeug