From 89f484d7b9cd6e6734c8bf19867a3d0a0deadd52 Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Thu, 7 May 2026 08:09:32 +0200 Subject: [PATCH] fix(logging): tolerate unset options dict in worker bootstrap CLI parsers seed salt._logging's global options dict at startup via LogLevelMixIn.__setup_logging_config(). Non-CLI consumers (RunnerClient.asynchronous, SSHClient, salt.utils.process.Process subclasses, parallel states) have no parser, so the dict stays None. Process.__new__ snapshots that None into instance.__logging_config__; wrapped_run_func then calls set_logging_options_dict(None) defensively, which forwards to set_lowest_log_level_by_opts(None).get(...) and AttributeErrors on the worker. The parent exits 0 with a misleading "Target did not return any data" / dead jid / 'result': None. Make set_logging_options_dict(None) and setup_logging() (when nothing has been seeded) no-op gracefully. Workers fall back to whatever logger configuration they inherited from the parent. CLI tools always seed before calling and are unaffected. Fixes #68332 Signed-off-by: Teddy Andrieux --- changelog/68332.fixed.md | 1 + salt/_logging/impl.py | 4 +++- .../pytests/functional/utils/test_process.py | 15 ++++++++++++ tests/pytests/unit/_logging/test_impl.py | 23 +++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 changelog/68332.fixed.md create mode 100644 tests/pytests/unit/_logging/test_impl.py diff --git a/changelog/68332.fixed.md b/changelog/68332.fixed.md new file mode 100644 index 000000000000..ea0d32d84c6e --- /dev/null +++ b/changelog/68332.fixed.md @@ -0,0 +1 @@ +Fixed worker process crash when salt is used outside CLI tools. diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py index b73f2ee98f4e..e8749d9a470b 100644 --- a/salt/_logging/impl.py +++ b/salt/_logging/impl.py @@ -449,6 +449,8 @@ def set_logging_options_dict(opts): """ Create a logging related options dictionary based off of the loaded salt config """ + if opts is None: + return try: if isinstance(set_logging_options_dict.__options_dict__, ImmutableDict): raise RuntimeError( @@ -999,7 +1001,7 @@ def setup_log_granular_levels(log_granular_levels): def setup_logging(): opts = get_logging_options_dict() if not opts: - raise RuntimeError("The logging options have not been set yet.") + return if ( opts.get("configure_console_logger", True) and not is_console_handler_configured() diff --git a/tests/pytests/functional/utils/test_process.py b/tests/pytests/functional/utils/test_process.py index 067d3a1d7e65..979435768cd2 100644 --- a/tests/pytests/functional/utils/test_process.py +++ b/tests/pytests/functional/utils/test_process.py @@ -203,3 +203,18 @@ def run_sync(): assert ran == [True] finally: process_manager.terminate() + + +def test_process_unseeded_logging_options(): + """ + Regression test for issue #68332. + """ + + def target(): + pass + + salt._logging.set_logging_options_dict.__options_dict__ = None + proc = salt.utils.process.Process(target=target) + proc.start() + proc.join() + assert proc.exitcode == 0 diff --git a/tests/pytests/unit/_logging/test_impl.py b/tests/pytests/unit/_logging/test_impl.py new file mode 100644 index 000000000000..0961737dff7d --- /dev/null +++ b/tests/pytests/unit/_logging/test_impl.py @@ -0,0 +1,23 @@ +""" +tests.pytests.unit._logging.test_impl +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Test salt's logging implementation +""" + +import salt._logging.impl + + +def test_set_logging_options_dict_with_none(): + """ + Regression test for issue #68332. + """ + salt._logging.impl.set_logging_options_dict(None) + + +def test_setup_logging_with_unseeded_options(): + """ + Regression test for issue #68332. + """ + salt._logging.impl.set_logging_options_dict.__options_dict__ = None + salt._logging.impl.setup_logging()