GUI: Add "Generate debug log" action (#3328)
* Add debug recorder and reporting utilities
Introduce a new debug subsystem under deeplabcut/core/debug. Adds an in-memory, bounded log recorder (InMemoryDebugRecorder) with safe fail-open semantics, helper functions to install/get the recorder, and a scoped log_timing context manager. Provides utilities to collect runtime, package, and external executable information (LibrarySpec, ExecutableSpec, collect_version_summary, collect_executable_summary, collect_debug_sections) and to format/assemble a full debug report (format_debug_report, build_debug_report). Includes small helper functions (_which, _command_version) and sane defaults (lists of core/GUI/TensorFlow libs, ffmpeg executable). Exports are wired in __init__.py.
* Add debug report dialog and helpers
Introduce a reusable debug text dialog and helper functions for generating and displaying DeepLabCut diagnostic reports. Adds deeplabcut/gui/dialogs/debug_dialog.py implementing DebugTextDialog (read-only log view with refresh/copy/keyboard shortcut), providers to render logs and full issue reports, show_debug_report_dialog to install/reuse recorders and present the report, and create_generate_debug_log_action to wire up a QAction. Also exports these symbols via deeplabcut/gui/dialogs/__init__.py. Dialog instances are attached to the parent to avoid GC and duplicate windows.
* Add debug log generation action to Help menu
Import and install a debug recorder and add a "Generate Debug Log" action to the Help menu. The patch imports create_generate_debug_log_action and install_debug_recorder, creates a recorder (logger_name="deeplabcut"), builds the debug-log action with specified options (include_module_paths=False, include_executable_paths=True, log_limit=1000), inserts it into the Help menu with a separator, and adjusts menu sizing. This allows users to generate debug logs from the GUI.
* Add tests for debug logger utilities
Add a new test module for deeplabcut.core.debug covering InMemoryDebugRecorder and log_timing. Tests verify install_debug_recorder idempotency and retrieval, message and exception capture, bounded buffer behavior, render_text contents, clear() resetting records and drop count, and log_timing behavior when enabled, disabled, and with thresholding. Uses pytest fixtures to create isolated logger namespaces and monkeypatch to simulate timings and logger behavior.
* Rename debug tests
Causes conflicts with pytorch tests of same name in CI
* Add license headers and tidy debug exports
Add project/license header comments to deeplabcut/core/debug/__init__.py and deeplabcut/gui/dialogs/__init__.py. In core/debug/__init__.py, clean up the public API exports: remove internal symbols DLC_LOG_TIMING and LOG_QUEUE_MAXLEN from __all__, reorder entries, and ensure collect_executable_summary is exported. These changes clarify public exports and add attribution/licensing headers.
* Use namespaced GUI loggers
Replace generic "GUI" and "console" loggers with namespaced loggers ("deeplabcut.gui" and "deeplabcut.gui.console"). Store a console logger on the MainWindow instance and use it for setting level and printing status messages. This standardizes logger names and avoids relying on a global "console" logger.
* Reuse DebugTextDialog and rename hint arg
Add DebugTextDialog.update_content to update window title, text provider, and hint when reusing an existing dialog instance. Update _get_or_create_debug_dialog to call update_content for existing dialogs instead of recreating them. Rename the initial_hint parameter to text_hint and adjust the show_debug_report_dialog call site accordingly. This lets the debug dialog refresh its metadata without reconstructing the widget.
* Make debug timing and recorder configurable
Add environment-driven debug/timing configuration and make the in-memory debug recorder less intrusive. Changes:
- deeplabcut/core/debug/_debug_utils.py: add license header, import os, add reload_debug_settings_from_env(), and helper parsers _env_flag and _env_optional_float to read boolean/optional-float env vars.
- deeplabcut/core/debug/debug_logger.py: add license header, read DLC_LOG_TIMING and DLC_LOG_TIMING_THRESHOLD_MS from environment, import the new env helpers, and use an effective_threshold in log_timing().
- install_debug_recorder(): add handler_level and ensure_logger_level params, use handler_level for the recorder, and only lower the logger level when explicitly requested.
- Adjust external tools section reporting logic to avoid misleading reporting when executables is None.
Rationale: allow debug timing and thresholds to be controlled via environment variables, make recorder installation configurable without forcing global logger verbosity, and improve robustness of external-tool reporting.
* Fix tests
* Move reload_debug_settings_from_env to debug_logger
Move the reload_debug_settings_from_env function from deeplabcut/core/debug/_debug_utils.py into deeplabcut/core/debug/debug_logger.py. The function (which reloads DLC_LOG_TIMING and DLC_LOG_TIMING_THRESHOLD_MS from environment variables) is removed from _debug_utils.py and added to debug_logger.py to colocate timing/debug env handling with the logging code. No behavioral changes intended.
* Show exec even if default None
* Show wait cursor while building debug text
Import QApplication and set an override WaitCursor in DebugTextDialog.refresh_text while calling the text provider, restoring the cursor in a finally block. This provides visual feedback during potentially long debug-text generation and ensures the cursor is always reset even if the provider raises an exception.
* Auto-init debug recorder logger level
Allow the in-memory debug recorder to optionally auto-initialize the target logger's level. Import typing.Literal and change install_debug_recorder's ensure_logger_level parameter to accept None, int, or the new "auto" sentinel (default). Expand the docstring to document behavior: "auto" sets the logger to handler_level only when the logger has no explicit level (NOTSET); int lowers the logger level only if more restrictive; None leaves the logger untouched. Adjust the logic accordingly and update the GUI caller to pass handler_level and ensure_logger_level="auto" when installing the recorder.
* Fix logger level docstring
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Snapshot iterables and remove redundant refresh
Convert libraries and executables to tuples in make_issue_report_provider so the returned provider can be called repeatedly even if given a one-shot iterable. Comment out redundant initial refresh_text() calls in the dialog (showEvent triggers the initial refresh). Also set logger.propagate = False in the test cleanup to avoid log propagation during tests.
* Prefer module version for OpenCV; adjust logger API
Change install_debug_recorder to remove the special "auto" behavior for ensure_logger_level and make its default None; now only an int will lower the logger level when explicitly requested. Simplify some docstrings and clarify capacity wording. Add LibrarySpec.prefer_module_version and a new _module_version helper to prefer a module's __version__ when collecting versions (falling back to the distribution version if unavailable). Mark opencv-python to prefer the module version and update collect_version_summary to use the resolved module name for module paths.
* Export DebugSection and add tests for debug report
Expose DebugSection from deeplabcut.core.debug by adding it to the module imports and __all__. Expand test_debug_logger.py with extensive tests for building and formatting debug reports, version and executable summaries, and log handling (using monkeypatched helpers and InMemoryDebugRecorder) to validate report contents and edge cases.
* Thread-safe debug recorder and UI cleanup
Make InMemoryDebugRecorder.dropped_count thread-safe by acquiring the internal lock before returning the dropped counter to avoid race conditions (core/debug/debug_logger.py). In the main window, set ensure_logger_level to logging.INFO instead of the string "auto" to ensure the logger level is explicitly configured (gui/window.py). Minor GUI cleanup: update AUTHORS URL to the main branch, remove an unused TYPE_CHECKING stub and a redundant commented refresh call in the debug dialog (gui/dialogs/debug_dialog.py). These changes improve stability and tidy up UI code/comments.
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> C
Cyril Achard committed
eb4f8488af4d01700c197c2083cc4cd08ade3eca
Parent: b4537a5
Committed by GitHub <noreply@github.com>
on 5/18/2026, 3:15:46 PM