From 64b607c42f73caf1a494e0b88621397d03994001 Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.dos.anjos@gmail.com> Date: Mon, 7 Jan 2019 18:11:50 +0100 Subject: [PATCH] [logging] Fix logging for all commands --- bob/devtools/log.py | 140 +++++++++++++++++++++++++++++ bob/devtools/scripts/bdt.py | 69 +------------- bob/devtools/scripts/cb_output.py | 6 +- bob/devtools/scripts/changelog.py | 31 ++++--- bob/devtools/scripts/lasttag.py | 6 +- bob/devtools/scripts/release.py | 24 ++--- bob/devtools/scripts/visibility.py | 17 ++-- doc/api.rst | 3 + 8 files changed, 198 insertions(+), 98 deletions(-) create mode 100644 bob/devtools/log.py diff --git a/bob/devtools/log.py b/bob/devtools/log.py new file mode 100644 index 00000000..a623ccae --- /dev/null +++ b/bob/devtools/log.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Sets-up logging, centrally for Bob. +""" + +import sys +import logging + +# get the default root logger of Bob +_logger = logging.getLogger('bob') + +# by default, warning and error messages should be written to sys.stderr +_warn_err = logging.StreamHandler(sys.stderr) +_warn_err.setLevel(logging.WARNING) +_logger.addHandler(_warn_err) + +# debug and info messages are written to sys.stdout + +class _InfoFilter: + def filter(self, record): + return record.levelno <= logging.INFO + + +_debug_info = logging.StreamHandler(sys.stdout) +_debug_info.setLevel(logging.DEBUG) +_debug_info.addFilter(_InfoFilter()) +_logger.addHandler(_debug_info) + + +# helper functions to instantiate and set-up logging +def setup(logger_name, + format="%(name)s@%(asctime)s -- %(levelname)s: %(message)s"): + """This function returns a logger object that is set up to perform logging + using Bob loggers. + + Parameters + ---------- + logger_name : str + The name of the module to generate logs for + format : :obj:`str`, optional + The format of the logs, see :py:class:`logging.LogRecord` for more + details. By default, the log contains the logger name, the log time, the + log level and the massage. + + Returns + ------- + logger : :py:class:`logging.Logger` + The logger configured for logging. The same logger can be retrieved using + the :py:func:`logging.getLogger` function. + """ + # generate new logger object + logger = logging.getLogger(logger_name) + + # add log the handlers if not yet done + if not logger_name.startswith("bob") and not logger.handlers: + logger.addHandler(_warn_err) + logger.addHandler(_debug_info) + + # this formats the logger to print the desired information + formatter = logging.Formatter(format) + # we have to set the formatter to all handlers registered in the current + # logger + for handler in logger.handlers: + handler.setFormatter(formatter) + + # set the same formatter for bob loggers + for handler in _logger.handlers: + handler.setFormatter(formatter) + + return logger + + +def set_verbosity_level(logger, level): + """Sets the log level for the given logger. + + Parameters + ---------- + logger : :py:class:`logging.Logger` or str + The logger to generate logs for, or the name of the module to generate + logs for. + level : int + Possible log levels are: 0: Error; 1: Warning; 2: Info; 3: Debug. + + Raises + ------ + ValueError + If the level is not in range(0, 4). + """ + if level not in range(0, 4): + raise ValueError( + "The verbosity level %d does not exist. Please reduce the number of " + "'--verbose' parameters in your command line" % level) + # set up the verbosity level of the logging system + log_level = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG + }[level] + + # set this log level to the logger with the specified name + if isinstance(logger, str): + logger = logging.getLogger(logger) + logger.setLevel(log_level) + # set the same log level for the bob logger + _logger.setLevel(log_level) + + +def verbosity_option(**kwargs): + """Adds a -v/--verbose option to a click command. + + Parameters + ---------- + **kwargs + All kwargs are passed to click.option. + + Returns + ------- + callable + A decorator to be used for adding this option. + """ + global _logger + import click + + def custom_verbosity_option(f): + def callback(ctx, param, value): + ctx.meta['verbosity'] = value + set_verbosity_level(_logger, value) + _logger.debug("`bob' logging level set to %d", value) + return value + return click.option( + '-v', '--verbose', count=True, + expose_value=False, default=0, + help="Increase the verbosity level from 0 (only error messages) " + "to 1 (warnings), 2 (info messages), 3 (debug information) by " + "adding the --verbose option as often as desired " + "(e.g. '-vvv' for debug).", + callback=callback, **kwargs)(f) + return custom_verbosity_option diff --git a/bob/devtools/scripts/bdt.py b/bob/devtools/scripts/bdt.py index 4af5720c..846a5177 100644 --- a/bob/devtools/scripts/bdt.py +++ b/bob/devtools/scripts/bdt.py @@ -9,72 +9,8 @@ import pkg_resources import click from click_plugins import with_plugins -import logging -logger = logging.getLogger('bdt') - - -def set_verbosity_level(logger, level): - """Sets the log level for the given logger. - - Parameters - ---------- - logger : :py:class:`logging.Logger` or str - The logger to generate logs for, or the name of the module to generate - logs for. - level : int - Possible log levels are: 0: Error; 1: Warning; 2: Info; 3: Debug. - Raises - ------ - ValueError - If the level is not in range(0, 4). - """ - if level not in range(0, 4): - raise ValueError( - "The verbosity level %d does not exist. Please reduce the number " - "of '--verbose' parameters in your command line" % level - ) - # set up the verbosity level of the logging system - log_level = { - 0: logging.ERROR, - 1: logging.WARNING, - 2: logging.INFO, - 3: logging.DEBUG - }[level] - - # set this log level to the logger with the specified name - if isinstance(logger, str): - logger = logging.getLogger(logger) - logger.setLevel(log_level) - - -def verbosity_option(**kwargs): - """Adds a -v/--verbose option to a click command. - - Parameters - ---------- - **kwargs - All kwargs are passed to click.option. - - Returns - ------- - callable - A decorator to be used for adding this option. - """ - def custom_verbosity_option(f): - def callback(ctx, param, value): - ctx.meta['verbosity'] = value - set_verbosity_level(logger, value) - logger.debug("`bdt' logging level set to %d", value) - return value - return click.option( - '-v', '--verbose', count=True, - expose_value=False, default=2, - help="Increase the verbosity level from 0 (only error messages) " - "to 1 (warnings), 2 (info messages), 3 (debug information) by " - "adding the --verbose option as often as desired " - "(e.g. '-vvv' for debug).", - callback=callback, **kwargs)(f) - return custom_verbosity_option +from ..log import setup +logger = setup('bob') class AliasedGroup(click.Group): @@ -114,6 +50,5 @@ def raise_on_error(view_func): @with_plugins(pkg_resources.iter_entry_points('bdt.cli')) @click.group(cls=AliasedGroup, context_settings=dict(help_option_names=['-?', '-h', '--help'])) -@verbosity_option() def main(): """Bob Development Tools - see available commands below""" diff --git a/bob/devtools/scripts/cb_output.py b/bob/devtools/scripts/cb_output.py index 35c98c0d..cfebdb64 100644 --- a/bob/devtools/scripts/cb_output.py +++ b/bob/devtools/scripts/cb_output.py @@ -1,12 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import logging +logger = logging.getLogger(__name__) + import click from click.testing import CliRunner import conda_build.api as cb from . import bdt - +from ..log import verbosity_option from ..conda import should_skip_build @@ -23,6 +26,7 @@ $ bdt cb-output ../bob.conda/conda/kaldi -m ../bob.admin/gitlab/conda_build_conf @click.argument('recipe_path') @click.option('-m', '--variant-config-files', help='see conda build --help') @click.option('--python', help='see conda build --help') +@verbosity_option() @bdt.raise_on_error def cb_output(recipe_path, variant_config_files, python): """Outputs name(s) of package(s) that would be generated by conda build. diff --git a/bob/devtools/scripts/changelog.py b/bob/devtools/scripts/changelog.py index af80b7a7..a347f89d 100644 --- a/bob/devtools/scripts/changelog.py +++ b/bob/devtools/scripts/changelog.py @@ -4,9 +4,13 @@ import os import sys import datetime +import logging +logger = logging.getLogger(__name__) + import click from . import bdt +from ..log import verbosity_option from ..changelog import get_last_tag_date, write_tags_with_commits from ..changelog import parse_date from ..release import get_gitlab_instance @@ -21,24 +25,24 @@ Examples: 1. Generates the changelog for a single package using merge requests: - $ bdt -vvv changelog group/package.xyz changelog.md + $ bdt changelog group/package.xyz changelog.md 2. The same as above, but dumps the changelog to stdout instead of a file: - $ bdt -vvv changelog group/package.xyz + $ bdt changelog group/package.xyz 3. Generates the changelog for a single package looking at commits (not merge requests): - $ bdt -vvv changelog --mode=commits group/package.xyz changelog.md + $ bdt changelog --mode=commits group/package.xyz changelog.md 4. Generates the changelog for a single package looking at merge requests starting from a given date of January 1, 2016: \b - $ bdt -vvv changelog --mode=mrs --since=2016-01-01 group/package.xyz changelog.md + $ bdt changelog --mode=mrs --since=2016-01-01 group/package.xyz changelog.md 5. Generates a complete list of changelogs for a list of packages (one per line: @@ -47,7 +51,7 @@ Examples: $ curl -o order.txt https://gitlab.idiap.ch/bob/bob.nightlies/raw/master/order.txt $ bdt lasttag bob/bob # copy and paste date to next command - $ bdt -vvv changelog --since="2018-07-17 10:23:40" order.txt changelog.md + $ bdt changelog --since="2018-07-17 10:23:40" order.txt changelog.md ''') @click.argument('target') @click.argument('changelog', type=click.Path(exists=False, dir_okay=False, @@ -67,6 +71,7 @@ Examples: '(see https://dateutil.readthedocs.io/en/stable/parser.html) from ' \ 'which you want to generate the changelog. If not set, the package\'s' \ 'last release date will be used') +@verbosity_option() @bdt.raise_on_error def changelog(target, changelog, group, mode, since): """Generates changelog file for package(s) from the Gitlab server. @@ -90,13 +95,13 @@ def changelog(target, changelog, group, mode, since): # reads package list or considers name to be a package name if os.path.exists(target) and os.path.isfile(target): - bdt.logger.info('Reading package names from file %s...', target) + logger.info('Reading package names from file %s...', target) with open(target, 'rt') as f: packages = [k.strip() for k in f.readlines() if k.strip() and not \ k.strip().startswith('#')] else: - bdt.logger.info('Assuming %s is a package name (file does not ' \ - 'exist)...', target) + logger.info('Assuming %s is a package name (file does not exist)...', + target) packages = [target] # if the user passed a date, convert it @@ -110,11 +115,11 @@ def changelog(target, changelog, group, mode, since): # retrieves the gitlab package object use_package = gl.projects.get(package) - bdt.logger.info('Found gitlab project %s (id=%d)', + logger.info('Found gitlab project %s (id=%d)', use_package.attributes['path_with_namespace'], use_package.id) last_release_date = since or get_last_tag_date(use_package) - bdt.logger.info('Retrieving data (mode=%s) since %s', mode, + logger.info('Retrieving data (mode=%s) since %s', mode, last_release_date.strftime('%b %d, %Y %H:%M')) # add 1s to avoid us retrieving previous release data @@ -127,13 +132,13 @@ def changelog(target, changelog, group, mode, since): if use_package.attributes['namespace'] == use_package.name: # skip system meta-package - bdt.logger.warn('Skipping meta package %s...', + logger.warn('Skipping meta package %s...', use_package.attributes['path_with_namespace']) continue if use_package.attributes['visibility'] not in visibility: - bdt.logger.warn('Skipping package %s (visibility not in ' \ - '"%s")...', use_package.attributes['path_with_namespace'], + logger.warn('Skipping package %s (visibility not in "%s")...', + use_package.attributes['path_with_namespace'], '|'.join(visibility)) continue diff --git a/bob/devtools/scripts/lasttag.py b/bob/devtools/scripts/lasttag.py index 2826ad18..d1e2aa27 100644 --- a/bob/devtools/scripts/lasttag.py +++ b/bob/devtools/scripts/lasttag.py @@ -1,10 +1,13 @@ #!/usr/bin/env python import os +import logging +logger = logging.getLogger(__name__) import click from . import bdt +from ..log import verbosity_option from ..changelog import get_last_tag, parse_date from ..release import get_gitlab_instance @@ -27,6 +30,7 @@ Examples: ''') @click.argument('package') +@verbosity_option() @bdt.raise_on_error def lasttag(package): """Returns the last tag information on a given PACKAGE @@ -39,7 +43,7 @@ def lasttag(package): # we lookup the gitlab group once use_package = gl.projects.get(package) - bdt.logger.info('Found gitlab project %s (id=%d)', + logger.info('Found gitlab project %s (id=%d)', use_package.attributes['path_with_namespace'], use_package.id) tag = get_last_tag(use_package) diff --git a/bob/devtools/scripts/release.py b/bob/devtools/scripts/release.py index 63b8374f..251539fc 100644 --- a/bob/devtools/scripts/release.py +++ b/bob/devtools/scripts/release.py @@ -3,10 +3,13 @@ import os +import logging +logger = logging.getLogger(__name__) import click from . import bdt +from ..log import verbosity_option from ..release import release_bob, parse_and_process_package_changelog from ..release import release_package, wait_for_pipeline_to_finish from ..release import get_gitlab_instance @@ -20,28 +23,28 @@ Examples: 1. Releases a single package: - $ bdt -vvv release --package=bob.package.xyz changelog.md + $ bdt release --package=bob.package.xyz changelog.md 2. If there is a single package in the ``changelog.md`` file, the flag ``--package`` is not required: - $ bdt -vvv release changelog.md + $ bdt release changelog.md 2. Releases the whole of bob using `changelog_since_last_release.md`: - $ bdt -vvv release bob/devtools/data/changelog_since_last_release.md + $ bdt release bob/devtools/data/changelog_since_last_release.md 3. In case of errors, resume the release of the whole of Bob: - $ bdt -vvv release --resume bob/devtools/data/changelog_since_last_release.md + $ bdt release --resume bob/devtools/data/changelog_since_last_release.md 4. The option `-dry-run` can be used to let the script print what it would do instead of actually doing it: - $ bdt -vvv release --dry-run changelog_since_last_release.md + $ bdt release --dry-run changelog_since_last_release.md ''' ) @click.argument('changelog', type=click.File('rb', lazy=False)) @@ -59,6 +62,7 @@ Examples: help='Only goes through the actions, but does not execute them ' \ '(combine with the verbosity flags - e.g. ``-vvv``) to enable ' \ 'printing to help you understand what will be done') +@verbosity_option() @bdt.raise_on_error def release(changelog, group, package, resume, dry_run): """\b @@ -149,7 +153,7 @@ def release(changelog, group, package, resume, dry_run): if line[1:].strip() == package] if not start_idx: - bdt.logger.error('Package %s was not found in the changelog', + logger.error('Package %s was not found in the changelog', package) return @@ -157,14 +161,14 @@ def release(changelog, group, package, resume, dry_run): # if we are in a dry-run mode, let's let it be known if dry_run: - bdt.logger.warn('!!!! DRY RUN MODE !!!!') - bdt.logger.warn('Nothing is being committed to Gitlab') + logger.warn('!!!! DRY RUN MODE !!!!') + logger.warn('Nothing is being committed to Gitlab') # go through the list of packages and release them starting from the # start_idx for i in range(start_idx, len(pkgs) - 1): cur_package_name = changelogs[pkgs[i]][1:].strip() - bdt.logger.info('Processing package %s', changelogs[pkgs[i]]) + logger.info('Processing package %s', changelogs[pkgs[i]]) gitpkg, tag, tag_comments = parse_and_process_package_changelog(gl, use_group, cur_package_name, changelogs[pkgs[i] + 1: pkgs[i + 1]], dry_run) @@ -181,4 +185,4 @@ def release(changelog, group, package, resume, dry_run): if package == cur_package_name and not resume: break - bdt.logger.info('Finished processing %s', changelog) + logger.info('Finished processing %s', changelog) diff --git a/bob/devtools/scripts/visibility.py b/bob/devtools/scripts/visibility.py index 0ef0bc18..67321c26 100644 --- a/bob/devtools/scripts/visibility.py +++ b/bob/devtools/scripts/visibility.py @@ -6,7 +6,11 @@ import sys import click import gitlab +import logging +logger = logging.getLogger(__name__) + from . import bdt +from ..log import verbosity_option from ..release import get_gitlab_instance @@ -19,20 +23,21 @@ Examples: 1. Check the visibility of a package you can access - $ bdt -vvv visibility bob/bob.extension + $ bdt visibility bob/bob.extension 2. Checks the visibility of all packages in a file list: \b $ curl -o order.txt https://gitlab.idiap.ch/bob/bob.nightlies/raw/master/order.txt - $ bdt -vvv visibility order.txt + $ bdt visibility order.txt ''') @click.argument('target') @click.option('-g', '--group', default='bob', show_default=True, help='Gitlab default group name where packages are located (if not ' \ 'specified using a "/" on the package name - e.g. ' \ '"bob/bob.extension")') +@verbosity_option() @bdt.raise_on_error def visibility(target, group): """Checks if the gitlab repository is visible to the current user @@ -47,12 +52,12 @@ def visibility(target, group): # reads package list or considers name to be a package name if os.path.exists(target) and os.path.isfile(target): - bdt.logger.info('Reading package names from file %s...', target) + logger.info('Reading package names from file %s...', target) with open(target, 'rt') as f: packages = [k.strip() for k in f.readlines() if k.strip() and not \ k.strip().startswith('#')] else: - bdt.logger.info('Assuming %s is a package name (file does not ' \ + logger.info('Assuming %s is a package name (file does not ' \ 'exist)...', target) packages = [target] @@ -65,11 +70,11 @@ def visibility(target, group): # retrieves the gitlab package object try: use_package = gl.projects.get(package) - bdt.logger.info('Found gitlab project %s (id=%d)', + logger.info('Found gitlab project %s (id=%d)', use_package.attributes['path_with_namespace'], use_package.id) click.echo('%s: %s' % (package, use_package.attributes['visibility'].lower())) except gitlab.GitlabGetError as e: - bdt.logger.warn('Gitlab access error - package %s does not exist?', + logger.warn('Gitlab access error - package %s does not exist?', package) click.echo('%s: unknown' % (package,)) diff --git a/doc/api.rst b/doc/api.rst index 6ca5b6c2..5e0e6b1a 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -6,6 +6,7 @@ ============ .. autosummary:: + bob.devtools.log bob.devtools.conda bob.devtools.release bob.devtools.changelog @@ -14,6 +15,8 @@ Detailed Information -------------------- +.. automodule:: bob.devtools.log + .. automodule:: bob.devtools.conda .. automodule:: bob.devtools.release -- GitLab