diff --git a/dnadna/utils/cli.py b/dnadna/utils/cli.py index 5cc2e7a861e81a3f07256eed1ddf0dd5e300685a..51da4579da40a16dd4782915ace96b3ee71018cb 100644 --- a/dnadna/utils/cli.py +++ b/dnadna/utils/cli.py @@ -9,6 +9,7 @@ import os.path as pth import shutil import sys import textwrap +import traceback try: import coloredlogs @@ -19,6 +20,9 @@ from .misc import unique, stdio_redirect_tqdm from .plugins import load_plugins +log = logging.getLogger(__name__) + + # Log filter for filtering out uninteresting messages from other libraries class DNADNAFilter(logging.Filter): def __init__(self, my_level): @@ -167,11 +171,15 @@ class Command(metaclass=abc.ABCMeta): # a command that does not normally configure logging log_file = getattr(args, 'log_file', None) + # Delete any existing handlers on the root logger, so if this is called + # multiple times it will reset the root logger configuration. + del logging.getLogger().handlers[:] + # stdio_redirect_tqdm is required so that stdout logging will cooperate # with any commands that use a tqdm progress bar; if they don't use # tqdm this will just write through to stdout as normal with stdio_redirect_tqdm(): - if coloredlogs: + if coloredlogs and not getattr(args, 'log_nc', False): # Annoying inconsistency between coloredlogs.install and # logging.basicConfig logging_config = dict(cls.logging_config) @@ -218,7 +226,7 @@ class Command(metaclass=abc.ABCMeta): f'unknown subcommand "{command[0]}"; known subcommands ' f'are:\n {subcommands}') - return command_cls.main(command[1:], namespace=args, is_subcommand=True) + return command_cls.main(command[1:], namespace=args) @classmethod def exit_error(cls, msg='', exit_code=1): @@ -229,7 +237,7 @@ class Command(metaclass=abc.ABCMeta): sys.exit(exit_code) @classmethod - def main(cls, argv=None, namespace=None, is_subcommand=False): + def main(cls, argv=None, namespace=None): args, argv = cls.preparse_args(argv, namespace=namespace) # Special handling of program name if the __main__ module is run: @@ -256,16 +264,33 @@ class Command(metaclass=abc.ABCMeta): ret = ret2 return ret except Exception as exc: - if not is_subcommand: - print(f'an unexpected error occurred: {exc}; run again with ' - f'--debug to view the full traceback or with --pdb to ' - f'drop into a debugger', file=sys.stderr) + msgs = [f'{type(exc).__name__}: {exc}'] + + if not args.debug: + msgs.append( + 'run again with --debug to view the full traceback, ' + 'or with --pdb to drop into a debugger') + + if cls.logging: + if args.debug: + log_method = log.exception + else: + log_method = log.error + + for msg in msgs: + log_method(msg) + else: + msgs.insert(0, 'error:') + for msg in msgs: + print(msg, '\n', file=sys.stderr) + + if args.debug: + print(file=sys.stderr) + traceback.print_exc() if args.pdb: import pdb pdb.post_mortem() - elif args.debug or is_subcommand: - raise exc errno = getattr(exc, 'errno', 1) return errno diff --git a/tests/test_cli.rst b/tests/test_cli.rst index 9f97bcdf9e249767aeed4ac7b1a553150766a3fb..5d9cef079f3e659f5df073a5f9bb7e77d1ddc0a9 100644 --- a/tests/test_cli.rst +++ b/tests/test_cli.rst @@ -172,8 +172,18 @@ Now same test but with the ``one_event`` simulator, which does require the ... >>> monkeypatch.setattr(SimulationCommand, 'run', raise_exception) >>> run_main(['dnadna', 'simulation']) - an unexpected error occurred: test exception; run again with --debug to view the full traceback or with --pdb to drop into a debugger + error: + <BLANKLINE> + RuntimeError: test exception + <BLANKLINE> + run again with --debug to view the full traceback, or with --pdb to drop + into a debugger >>> run_main(['dnadna', 'simulation', '--debug']) + error: + <BLANKLINE> + RuntimeError: test exception + <BLANKLINE> + <BLANKLINE> Traceback (most recent call last): ... RuntimeError: test exception @@ -184,8 +194,18 @@ the code):: >>> monkeypatch.setattr(SimulationInitCommand, 'run', raise_exception) >>> run_main(['dnadna', 'simulation', 'init']) - an unexpected error occurred: test exception; run again with --debug to view the full traceback or with --pdb to drop into a debugger + error: + <BLANKLINE> + RuntimeError: test exception + <BLANKLINE> + run again with --debug to view the full traceback, or with --pdb to drop + into a debugger >>> run_main(['dnadna', 'simulation', 'init', '--debug']) + error: + <BLANKLINE> + RuntimeError: test exception + <BLANKLINE> + <BLANKLINE> Traceback (most recent call last): ... RuntimeError: test exception