-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLI: rework the logging verbosity implementation
The intended behavior of logging of the system when invoked through the command line interface `verdi` is as follows: * The verbosity of output produced by invoked CLI commands can be controlled through a single `-v/--verbosity` option that is accepted by the CLI at every command level. * It takes the values of the log levels of Python's logging system, including AiiDA's custom `REPORT` level. * The verbosity level should be applied to all logging messages, so not just those messages that are logged directly by the command itself, but also any messages logged by module code that is indirectly called in the stack. Given that the implementation is built on top of the logging system, the log level set by the verbosity option has to be added to the logging configuration somehow. The logging is configured by invoking the method `aiida.common.log.configure_logging` which consumes a dictionary with the logging configuration that has certain fields that are dynamically determined from the loaded profile. Unfortunately, this method can be called by module code as well, therefore, the verbosity option can not rely on using this method to configure the logging. Because once the callback is finished, any other code invoked by the command that calls this same method, will undo the changes of the verbosity option. The original implementation attempted to solve this problem by storing the selected log level through the verbosity option, in the profile instance loaded in memory. Since the log level would be read dynamically from the profile in the `configure_logging` method, even when it would be recalled, the correct log level would be configured. This approach had various disadvantages, however. It tied the option closely to the concept of a profile. Even though all `verdi` commands have the profile option added by default and therefore this does not really pose a problem, it made the verbosity option useless in any other context. The tight coupling also made the logic more difficult to understand. Finally, even though the change of the profile setting was only made in memory and so in principle the change should be temporary within the scope of the CLI command, if the command decided to store the profile, which can for example happen in commands that are designed to change the profile, the change in log level option would also be written to disk, causing an undesirable side effect of this implementation. Here, we change the implementation by fully decoupling the verbosity option from the profile and simply having the callback write the selected log level to a global constant in the `aiida.common.log` module called `CLI_LOG_LEVEL`. The `configure_logging` module will check this constant and when set, will configure all loggers with the `cli` handler with the correspondig log level. This design still has a downside in that it hard-couples the generic `aiida.common.log` module to the more specific `aiida.cmdline` module, but it is not obvious to get the CLI dependent log level injected into this module some other way. Finally, the original implementation allowed a default value for the verbosity option to be configured through the config by setting the `logging.aiida_loglevel` option. However, since that also determines the log level for AiiDA when not run through the CLI, i.e. during normal operations, this could have undesirable side-effects. For example, if the option was set to `WARNING` to temporarily limit the number of logs from processes when being run, certain CLI commands would also no longer report any output.
- Loading branch information
Showing
12 changed files
with
212 additions
and
138 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# -*- coding: utf-8 -*- | ||
########################################################################### | ||
# Copyright (c), The AiiDA team. All rights reserved. # | ||
# This file is part of the AiiDA code. # | ||
# # | ||
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core # | ||
# For further information on the license, see the LICENSE.txt file # | ||
# For further information please visit http://www.aiida.net # | ||
########################################################################### | ||
"""Tests for the :class:`~aiida.cmdline.params.options.main.VERBOSITY` option.""" | ||
import logging | ||
|
||
import click | ||
import pytest | ||
|
||
from aiida.cmdline.utils import echo | ||
from aiida.cmdline.params import options | ||
from aiida.common.log import AIIDA_LOGGER, LOG_LEVELS | ||
|
||
|
||
@click.command() | ||
@options.VERBOSITY() | ||
def cmd(): | ||
"""Test command prints messages through the ``AIIDA_LOGGER`` and the ``CMDLINE_LOGGER``. | ||
The messages to the ``CMDLINE_LOGGER`` are performed indirect through the utilities of the ``echo`` module. | ||
""" | ||
for log_level in LOG_LEVELS.values(): | ||
AIIDA_LOGGER.log(log_level, 'aiida') | ||
|
||
echo.echo_debug('verdi') | ||
echo.echo_info('verdi') | ||
echo.echo_report('verdi') | ||
echo.echo_warning('verdi') | ||
echo.echo_error('verdi') | ||
echo.echo_critical('verdi') | ||
|
||
|
||
@pytest.mark.usefixtures('reset_log_level') | ||
def test_default(run_cli_command): | ||
"""Test the command without explicitly specifying the verbosity. | ||
The default log level is ``REPORT`` so its messages and everything above should show and the rest not. | ||
""" | ||
result = run_cli_command(cmd, raises=True) | ||
|
||
for log_level_name, log_level in LOG_LEVELS.items(): | ||
if log_level >= logging.REPORT: # pylint: disable=no-member | ||
assert f'{log_level_name.capitalize()}: verdi' in result.output | ||
assert f'{log_level_name.capitalize()}: aiida' in result.output | ||
else: | ||
assert f'{log_level_name.capitalize()}: verdi' not in result.output | ||
assert f'{log_level_name.capitalize()}: aiida' not in result.output | ||
|
||
|
||
@pytest.mark.parametrize('option_log_level', [level for level in LOG_LEVELS.values() if level != logging.NOTSET]) | ||
@pytest.mark.usefixtures('reset_log_level') | ||
def test_explicit(run_cli_command, option_log_level): | ||
"""Test explicitly settings a verbosity""" | ||
log_level_name = logging.getLevelName(option_log_level) | ||
result = run_cli_command(cmd, ['--verbosity', log_level_name], raises=True) | ||
|
||
for log_level_name, log_level in LOG_LEVELS.items(): | ||
if log_level >= option_log_level: | ||
assert f'{log_level_name.capitalize()}: verdi' in result.output | ||
assert f'{log_level_name.capitalize()}: aiida' in result.output | ||
else: | ||
assert f'{log_level_name.capitalize()}: verdi' not in result.output | ||
assert f'{log_level_name.capitalize()}: aiida' not in result.output | ||
|
||
|
||
@pytest.mark.usefixtures('reset_log_level', 'override_logging') | ||
def test_config_aiida_loglevel(run_cli_command, caplog): | ||
"""Test the behavior of the ``--verbosity`` option when the ``logging.aiida_loglevel`` config option is set. | ||
Even though the ``CMDLINE_LOGGER`` is technically a child of the ``AIIDA_LOGLEVEL`` and so normally the former | ||
should not override the latter, that is actually the desired behavior. The option should ensure that it overrides | ||
the value of the ``AIIDA_LOGGER`` that may be specified on the profile config. | ||
""" | ||
# First log a ``DEBUG`` message to the ``AIIDA_LOGGER`` and capture it to see the logger is properly configured. | ||
message = 'debug test message' | ||
|
||
with caplog.at_level(logging.DEBUG): | ||
AIIDA_LOGGER.debug(message) | ||
|
||
assert message in caplog.text | ||
|
||
# Now we invoke the command while passing a verbosity level that is higher than is configured for the | ||
# ``AIIDA_LOGGER``. The explicit verbosity value should override the value configured on the profile. | ||
option_log_level = logging.WARNING | ||
option_log_level_name = logging.getLevelName(option_log_level) | ||
result = run_cli_command(cmd, ['--verbosity', option_log_level_name], raises=True) | ||
|
||
for log_level_name, log_level in LOG_LEVELS.items(): | ||
if log_level >= option_log_level: | ||
assert f'{log_level_name.capitalize()}: verdi' in result.output | ||
assert f'{log_level_name.capitalize()}: aiida' in result.output | ||
else: | ||
assert f'{log_level_name.capitalize()}: verdi' not in result.output | ||
assert f'{log_level_name.capitalize()}: aiida' not in result.output |
Oops, something went wrong.