diff --git a/CHANGES.rst b/CHANGES.rst index e20c1e4571e..58faf105d9b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,11 @@ Deprecated Features added -------------- +* #13163: Add the :confval:`print_traceback` configuration option + to control whether tracebacks are printed in full when Sphinx + encounters an internal error. + Patch by Kevin Deldycke. + Bugs fixed ---------- diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index b65b9b79f79..341d71a9f30 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -317,6 +317,21 @@ General configuration (or, if translation is enabled with :confval:`language`, an equivalent format for the selected locale). +.. confval:: print_traceback + :type: :code-py:`bool` + :default: :code-py:`True` + + If :code-py:`False`, the full traceback will not be printed when Sphinx + encounters an internal fatal error. It will instead print a short message + and exit. + + .. note:: + + Whatever the setting is set to, the traceback is always saved in a + temporary file named ``sphinx-err-xxxxxxxx.log``. + + .. versionadded:: 8.2.0 + Options for figure numbering ---------------------------- diff --git a/sphinx/_cli/util/errors.py b/sphinx/_cli/util/errors.py index e630df22b6c..b756d88e743 100644 --- a/sphinx/_cli/util/errors.py +++ b/sphinx/_cli/util/errors.py @@ -59,7 +59,7 @@ def error_info(messages: str, extensions: str, traceback: str) -> str: """ -def save_traceback(app: Sphinx | None, exc: BaseException) -> str: +def save_traceback(app: Sphinx | None, exc: BaseException) -> tuple[str, str]: """Save the given exception's traceback in a temporary file.""" if isinstance(exc, SphinxParallelError): exc_format = '(Error in parallel process)\n' + exc.traceback @@ -78,12 +78,14 @@ def save_traceback(app: Sphinx | None, exc: BaseException) -> str: if ext.version != 'builtin' ) + output = error_info(last_msgs, exts_list, exc_format) + with tempfile.NamedTemporaryFile( suffix='.log', prefix='sphinx-err-', delete=False ) as f: - f.write(error_info(last_msgs, exts_list, exc_format).encode('utf-8')) + f.write(output.encode('utf-8')) - return f.name + return f.name, output def handle_exception( @@ -164,7 +166,9 @@ def print_red(*values: str) -> None: print_red(__('Exception occurred:')) print_err(formatted_tb) - traceback_info_path = save_traceback(app, exception) + traceback_info_path, traceback_output = save_traceback(app, exception) + if not app or app.config.print_traceback: + print_err(traceback_output) print_err(__('The full traceback has been saved in:')) print_err(traceback_info_path) print_err() diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index 37f3f26c453..8ae6d1204c0 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -72,7 +72,9 @@ def handle_exception( elif isinstance(exception, UnicodeError): print(red(__('Encoding error:')), file=stderr) print(terminal_safe(str(exception)), file=stderr) - tbpath = save_traceback(app, exception) + tbpath, tboutput = save_traceback(app, exception) + if not app or app.config.print_traceback: + print(tboutput) print( red( __( @@ -102,7 +104,9 @@ def handle_exception( else: print(red(__('Exception occurred:')), file=stderr) print(format_exception_cut_frames().rstrip(), file=stderr) - tbpath = save_traceback(app, exception) + tbpath, tboutput = save_traceback(app, exception) + if not app or app.config.print_traceback: + print(tboutput) print( red( __( diff --git a/sphinx/config.py b/sphinx/config.py index 24b0ba2cd9c..e85acaeb199 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -207,6 +207,7 @@ class Config: 'today': _Opt('', 'env', ()), # the real default is locale-dependent 'today_fmt': _Opt(None, 'env', frozenset((str,))), + 'print_traceback': _Opt(True, 'env', ()), 'language': _Opt('en', 'env', frozenset((str,))), 'locale_dirs': _Opt(['locales'], 'env', ()), diff --git a/sphinx/util/exceptions.py b/sphinx/util/exceptions.py index f12732558b5..94cb70fdf9c 100644 --- a/sphinx/util/exceptions.py +++ b/sphinx/util/exceptions.py @@ -12,7 +12,7 @@ from sphinx.application import Sphinx -def save_traceback(app: Sphinx | None, exc: BaseException) -> str: +def save_traceback(app: Sphinx | None, exc: BaseException) -> tuple[str, str]: """Save the given exception's traceback in a temporary file.""" import platform @@ -40,10 +40,7 @@ def save_traceback(app: Sphinx | None, exc: BaseException) -> str: if ext.version != 'builtin' ) - with NamedTemporaryFile( - 'w', encoding='utf-8', suffix='.log', prefix='sphinx-err-', delete=False - ) as f: - f.write(f"""\ + output = f"""\ # Platform: {sys.platform}; ({platform.platform()}) # Sphinx version: {sphinx.__display_version__} # Python version: {platform.python_version()} ({platform.python_implementation()}) @@ -59,8 +56,13 @@ def save_traceback(app: Sphinx | None, exc: BaseException) -> str: # Traceback: {exc_format} -""") - return f.name +""" + + with NamedTemporaryFile( + 'w', encoding='utf-8', suffix='.log', prefix='sphinx-err-', delete=False + ) as f: + f.write(output) + return f.name, output def format_exception_cut_frames(x: int = 1) -> str: