From: Ashwin M. Joshi Date: Tue, 10 Feb 2026 06:29:49 +0000 (+0530) Subject: mgr/cephadm: Control cephadm.log messages based on a new mgr logging level flag X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=63b91836b00bae9fb2ba19ee837edccb7f2840bb;p=ceph.git mgr/cephadm: Control cephadm.log messages based on a new mgr logging level flag Introduces a new 'cephadm_binary_logging_level' config option to control the verbosity of cephadm logging to persistent destinations (cephadm.log, syslog). - Adds --logging-level CLI flag (info, debug, error, warning) - Adds mgr/cephadm/cephadm_binary_logging_level config option - Applies logging level to file and syslog handlers - Console handlers maintain their defaults for terminal UX Fixes: https://tracker.ceph.com/issues/74872 Signed-off-by: Ashwin M. Joshi --- diff --git a/src/cephadm/cephadm.py b/src/cephadm/cephadm.py index 45cb599e648..4bf0f2b1015 100755 --- a/src/cephadm/cephadm.py +++ b/src/cephadm/cephadm.py @@ -4977,6 +4977,11 @@ def _get_parser(): action='store_true', default=False, help='Do not run containers with --cgroups=split (currently only relevant when using podman)') + parser.add_argument( + '--logging-level', + choices=['info', 'debug', 'error', 'warning'], + default='debug', + help='Tunable log level for cephadm binary: info, debug, error, warning (default: debug)') subparsers = parser.add_subparsers(help='sub-command') diff --git a/src/cephadm/cephadmlib/context.py b/src/cephadm/cephadmlib/context.py index 3411c199ebb..502d2c239c7 100644 --- a/src/cephadm/cephadmlib/context.py +++ b/src/cephadm/cephadmlib/context.py @@ -31,6 +31,7 @@ class BaseConfig: self.memory_request: Optional[int] = None self.memory_limit: Optional[int] = None self.log_to_journald: Optional[bool] = None + self.logging_level: str = 'debug' self.container_init: bool = CONTAINER_INIT # FIXME(refactor) : should be Optional[ContainerEngine] diff --git a/src/cephadm/cephadmlib/logging.py b/src/cephadm/cephadmlib/logging.py index f722a33e78d..b343e0d79dc 100644 --- a/src/cephadm/cephadmlib/logging.py +++ b/src/cephadm/cephadmlib/logging.py @@ -173,7 +173,9 @@ def _copy(obj: Any) -> Any: def _complete_logging_config( - interactive: bool, destinations: Optional[List[str]] + interactive: bool, + destinations: Optional[List[str]], + logging_level: str = 'debug', ) -> Dict[str, Any]: """Return a logging configuration dict, based on the runtime parameters cephadm was invoked with. @@ -183,6 +185,12 @@ def _complete_logging_config( if interactive: lc = _copy(_interactive_logging_config) + # Apply logging level to persistent destinations only (cephadm.log, syslog). + # Console handlers keep their template defaults for terminal UX. + level_upper = logging_level.upper() + lc['handlers']['log_file']['level'] = level_upper + lc['handlers']['syslog']['level'] = level_upper + handlers = lc['loggers']['']['handlers'] if not destinations: handlers.append(LogDestination.file.value) @@ -206,9 +214,11 @@ def cephadm_init_logging( if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR) + logging_level = getattr(ctx, 'logging_level', 'debug').lower() lc = _complete_logging_config( any(op in args for op in _INTERACTIVE_CMDS), getattr(ctx, 'log_dest', None), + logging_level=logging_level, ) logging.config.dictConfig(lc) @@ -228,6 +238,7 @@ def cephadm_init_logging( # option is set if ctx.verbose and handler.name in _VERBOSE_HANDLERS: handler.setLevel(QUIET_LOG_LEVEL) + logger.debug('%s\ncephadm %s' % ('-' * 80, args)) diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index ef4699838d1..555b7065761 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -509,6 +509,13 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule): desc="Destination for cephadm command persistent logging", enum_allowed=['file', 'syslog', 'file,syslog'], ), + Option( + 'cephadm_binary_logging_level', + type='str', + default='debug', + desc='Logging verbosity for the cephadm binary when invoked by the mgr (e.g. check-host, gather-facts).', + enum_allowed=['info', 'debug', 'error', 'warning'] + ), Option( 'oob_default_addr', type='str', @@ -622,6 +629,7 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule): self.certificate_automated_rotation_enabled = False self.certificate_check_debug_mode = False self.certificate_check_period = 0 + self.cephadm_binary_logging_level = 'debug' self.notify(NotifyType.mon_map, None) self.config_notify() diff --git a/src/pybind/mgr/cephadm/serve.py b/src/pybind/mgr/cephadm/serve.py index 1f1b5c2fd6f..e4f57d8a7ee 100644 --- a/src/pybind/mgr/cephadm/serve.py +++ b/src/pybind/mgr/cephadm/serve.py @@ -1738,6 +1738,9 @@ class CephadmServe: if image: final_args.extend(['--image', image]) + cephadm_log_level = self.mgr.cephadm_binary_logging_level or 'debug' + final_args.extend(['--logging-level', cephadm_log_level]) + if not self.mgr.container_init: final_args += ['--no-container-init'] diff --git a/src/pybind/mgr/cephadm/tests/test_cephadm.py b/src/pybind/mgr/cephadm/tests/test_cephadm.py index e462d17e761..269a1664e68 100644 --- a/src/pybind/mgr/cephadm/tests/test_cephadm.py +++ b/src/pybind/mgr/cephadm/tests/test_cephadm.py @@ -3117,3 +3117,112 @@ Traceback (most recent call last): assert wait(cephadm_module, c) == ['Scheduled osd.foo update...'] cephadm_module.set_osd_spec('osd.foo', ['1']) + + +class TestCephadmBinaryLoggingLevel: + """Test that host-status / cephadm binary logs are suppressed based on + mgr/cephadm/cephadm_binary_logging_level. + """ + @pytest.mark.parametrize("logging_level", ['info', 'debug', 'error', 'warning']) + @mock.patch("cephadm.ssh.SSHManager._remote_connection") + @mock.patch("cephadm.ssh.SSHManager._execute_command") + @mock.patch("cephadm.ssh.SSHManager._check_execute_command") + def test_check_host_invokes_cephadm_with_logging_level( + self, check_execute_command, execute_command, remote_connection, cephadm_module, logging_level + ): + """Cephadm binary must be invoked with --logging-level matching + mgr/cephadm/cephadm_binary_logging_level (info, debug, error, warning). + Check that mgr builds the cephadm command with appropriate --logging-level flag + Use check-host as a sample command although all cephadm commands receive the flag. + """ + remote_connection.side_effect = async_side_effect(mock.Mock()) + check_execute_command.side_effect = async_side_effect('/usr/bin/python3') + captured_commands = [] + + async def capture_execute(host, cmd, *args, **kwargs): + if hasattr(cmd, 'args'): + if 'check-host' in cmd.args: + captured_commands.append(cmd) + # Return valid JSON for commands that use _run_cephadm_json + if 'ls' in cmd.args: + return ('[]', '', 0) + if 'gather-facts' in cmd.args: + return ('{}', '', 0) + if 'list-networks' in cmd.args: + return ('[]', '', 0) + return ('', '', 0) + + execute_command.side_effect = capture_execute + + cephadm_module.cephadm_binary_logging_level = logging_level + with with_host(cephadm_module, 'test'): + pass + + check_host_cmds = [c for c in captured_commands if 'check-host' in c.args] + assert len(check_host_cmds) >= 1, ( + f'expected at least one check-host invocation for level {logging_level!r}' + ) + cmd = check_host_cmds[0] + assert '--logging-level' in cmd.args, ( + f'cephadm should be called with --logging-level when level is {logging_level!r}' + ) + idx = cmd.args.index('--logging-level') + assert cmd.args[idx + 1] == logging_level, ( + f'cephadm should be called with --logging-level {logging_level!r}' + ) + + +class TestCephadmLogDestination: + """Test that cephadm is invoked with --log-dest matching + mgr/cephadm/cephadm_log_destination. + """ + @pytest.mark.parametrize( + "log_destination,expected_log_dests", + [ + ('file', ['file']), + ('syslog', ['syslog']), + ('file,syslog', ['file', 'syslog']), + ], + ) + @mock.patch("cephadm.ssh.SSHManager._remote_connection") + @mock.patch("cephadm.ssh.SSHManager._execute_command") + @mock.patch("cephadm.ssh.SSHManager._check_execute_command") + def test_check_host_invokes_cephadm_with_log_dest( + self, check_execute_command, execute_command, remote_connection, + cephadm_module, log_destination, expected_log_dests, + ): + """check-host must be invoked with --log-dest matching + mgr/cephadm/cephadm_log_destination (file, syslog, or both). + """ + remote_connection.side_effect = async_side_effect(mock.Mock()) + check_execute_command.side_effect = async_side_effect('/usr/bin/python3') + captured_commands = [] + + async def capture_execute(host, cmd, *args, **kwargs): + if hasattr(cmd, 'args'): + if 'check-host' in cmd.args: + captured_commands.append(cmd) + if 'ls' in cmd.args: + return ('[]', '', 0) + if 'gather-facts' in cmd.args: + return ('{}', '', 0) + if 'list-networks' in cmd.args: + return ('[]', '', 0) + return ('', '', 0) + + execute_command.side_effect = capture_execute + + cephadm_module.cephadm_log_destination = log_destination + with with_host(cephadm_module, 'test'): + pass + + check_host_cmds = [c for c in captured_commands if 'check-host' in c.args] + assert len(check_host_cmds) >= 1, ( + f'expected at least one check-host invocation for dest {log_destination!r}' + ) + cmd = check_host_cmds[0] + for dest in expected_log_dests: + assert f'--log-dest={dest}' in cmd.args, ( + f'cephadm should be called with --log-dest={dest!r} ' + f'when cephadm_log_destination is {log_destination!r}' + )