Changeset View
Changeset View
Standalone View
Standalone View
swh/core/cli/__init__.py
# Copyright (C) 2019 The Software Heritage developers | # Copyright (C) 2019 The Software Heritage developers | ||||
# See the AUTHORS file at the top-level directory of this distribution | # See the AUTHORS file at the top-level directory of this distribution | ||||
# License: GNU General Public License version 3, or any later version | # License: GNU General Public License version 3, or any later version | ||||
# See top-level LICENSE file for more information | # See top-level LICENSE file for more information | ||||
import logging | import logging | ||||
import logging.config | import logging.config | ||||
from typing import Optional | |||||
import warnings | import warnings | ||||
import click | import click | ||||
import pkg_resources | import pkg_resources | ||||
LOG_LEVEL_NAMES = ["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | LOG_LEVEL_NAMES = ["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | ||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) | CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) | ||||
Show All 27 Lines | |||||
def clean_exit_on_signal(signal, frame): | def clean_exit_on_signal(signal, frame): | ||||
"""Raise a SystemExit exception to let command-line clients wind themselves | """Raise a SystemExit exception to let command-line clients wind themselves | ||||
down on exit""" | down on exit""" | ||||
raise SystemExit(0) | raise SystemExit(0) | ||||
def validate_loglevel_params(ctx, param, value): | |||||
"""Validate the --log-level parameters, with multiple values""" | |||||
if value is None: | |||||
return None | |||||
return [validate_loglevel(ctx, param, v) for v in value] | |||||
def validate_loglevel(ctx, param, value): | |||||
"""Validate a single loglevel specification, of the form LOGLEVEL or | |||||
module:LOGLEVEL.""" | |||||
if ":" in value: | |||||
try: | |||||
module, log_level = value.split(":") | |||||
except ValueError: | |||||
raise click.BadParameter( | |||||
"Invalid log level specification `%s`, " | |||||
"needs to be in format `module:LOGLEVEL`" % value | |||||
) | |||||
else: | |||||
module = None | |||||
log_level = value | |||||
if log_level not in LOG_LEVEL_NAMES: | |||||
raise click.BadParameter( | |||||
"Log level %s unknown (in `%s`) needs to be one of %s" | |||||
% (log_level, value, ", ".join(LOG_LEVEL_NAMES)) | |||||
) | |||||
return (module, log_level) | |||||
@click.group( | @click.group( | ||||
context_settings=CONTEXT_SETTINGS, | context_settings=CONTEXT_SETTINGS, | ||||
cls=AliasedGroup, | cls=AliasedGroup, | ||||
option_notes="""\ | option_notes="""\ | ||||
If both options are present, --log-level will override the root logger | If both options are present, --log-level values will override the configuration | ||||
configuration set in --log-config. | in --log-config. | ||||
The --log-config YAML must conform to the logging.config.dictConfig schema | The --log-config YAML must conform to the logging.config.dictConfig schema | ||||
documented at https://docs.python.org/3/library/logging.config.html. | documented at https://docs.python.org/3/library/logging.config.html. | ||||
""", | """, | ||||
) | ) | ||||
@click.option( | @click.option( | ||||
"--log-level", | "--log-level", | ||||
"-l", | "-l", | ||||
"log_levels", | |||||
default=None, | default=None, | ||||
type=click.Choice(LOG_LEVEL_NAMES), | callback=validate_loglevel_params, | ||||
help="Log level (defaults to INFO).", | multiple=True, | ||||
help=( | |||||
"Log level (defaults to INFO). " | |||||
"Can override the log level for a specific module, by using the " | |||||
"`specific.module:LOGLEVEL` syntax (e.g. `--log-level swh.core:DEBUG` " | |||||
"will enable DEBUG logging for swh.core)." | |||||
), | |||||
) | ) | ||||
@click.option( | @click.option( | ||||
"--log-config", | "--log-config", | ||||
default=None, | default=None, | ||||
type=click.File("r"), | type=click.File("r"), | ||||
help="Python yaml logging configuration file.", | help="Python yaml logging configuration file.", | ||||
) | ) | ||||
@click.option( | @click.option( | ||||
"--sentry-dsn", default=None, help="DSN of the Sentry instance to report to" | "--sentry-dsn", default=None, help="DSN of the Sentry instance to report to" | ||||
) | ) | ||||
@click.option( | @click.option( | ||||
"--sentry-debug/--no-sentry-debug", | "--sentry-debug/--no-sentry-debug", | ||||
default=False, | default=False, | ||||
hidden=True, | hidden=True, | ||||
help="Enable debugging of sentry", | help="Enable debugging of sentry", | ||||
) | ) | ||||
@click.pass_context | @click.pass_context | ||||
def swh(ctx, log_level, log_config, sentry_dsn, sentry_debug): | def swh(ctx, log_levels, log_config, sentry_dsn, sentry_debug): | ||||
"""Command line interface for Software Heritage. | """Command line interface for Software Heritage. | ||||
""" | """ | ||||
import signal | import signal | ||||
import yaml | import yaml | ||||
from ..sentry import init_sentry | from ..sentry import init_sentry | ||||
signal.signal(signal.SIGTERM, clean_exit_on_signal) | signal.signal(signal.SIGTERM, clean_exit_on_signal) | ||||
signal.signal(signal.SIGINT, clean_exit_on_signal) | signal.signal(signal.SIGINT, clean_exit_on_signal) | ||||
init_sentry(sentry_dsn, debug=sentry_debug) | init_sentry(sentry_dsn, debug=sentry_debug) | ||||
if log_level is None and log_config is None: | set_default_loglevel: Optional[str] = None | ||||
log_level = "INFO" | |||||
if log_config: | if log_config: | ||||
logging.config.dictConfig(yaml.safe_load(log_config.read())) | logging.config.dictConfig(yaml.safe_load(log_config.read())) | ||||
set_default_loglevel = logging.root.getEffectiveLevel() | |||||
if not log_levels: | |||||
log_levels = [] | |||||
if log_level: | for module, log_level in log_levels: | ||||
logger = logging.getLogger(module) | |||||
log_level = logging.getLevelName(log_level) | log_level = logging.getLevelName(log_level) | ||||
logging.root.setLevel(log_level) | logger.setLevel(log_level) | ||||
if module is None: | |||||
set_default_loglevel = log_level | |||||
if not set_default_loglevel: | |||||
logging.root.setLevel("INFO") | |||||
set_default_loglevel = "INFO" | |||||
ctx.ensure_object(dict) | ctx.ensure_object(dict) | ||||
ctx.obj["log_level"] = log_level | ctx.obj["log_level"] = set_default_loglevel | ||||
def main(): | def main(): | ||||
# Even though swh() sets up logging, we need an earlier basic logging setup | # Even though swh() sets up logging, we need an earlier basic logging setup | ||||
# for the next few logging statements | # for the next few logging statements | ||||
logging.basicConfig() | logging.basicConfig() | ||||
# load plugins that define cli sub commands | # load plugins that define cli sub commands | ||||
for entry_point in pkg_resources.iter_entry_points("swh.cli.subcommands"): | for entry_point in pkg_resources.iter_entry_points("swh.cli.subcommands"): | ||||
Show All 21 Lines |