diff --git a/dvc/command/add.py b/dvc/command/add.py index 2846b21d6..ba6c81801 100644 --- a/dvc/command/add.py +++ b/dvc/command/add.py @@ -8,6 +8,6 @@ class CmdAdd(CmdBase): try: self.project.add(target) except DvcException as ex: - self.project.logger.error('Failed to add {}: {}'.format(target, str(ex))) + self.project.logger.error('Failed to add {}', ex) return 1 return 0 diff --git a/dvc/command/common/base.py b/dvc/command/common/base.py index 94a36a7ff..54ebe3c92 100644 --- a/dvc/command/common/base.py +++ b/dvc/command/common/base.py @@ -31,7 +31,7 @@ class CmdBase(object): with self.project.lock: return self.run() except LockError as ex: - self.project.logger.error(str(ex)) + self.project.logger.error('Failed to lock before running a command', ex) return 1 # Abstract methods that have to be implemented by any inheritance class diff --git a/dvc/command/remove.py b/dvc/command/remove.py index e2ea0d8fa..228e3a365 100644 --- a/dvc/command/remove.py +++ b/dvc/command/remove.py @@ -8,6 +8,6 @@ class CmdRemove(CmdBase): try: self.project.remove(target) except DvcException as ex: - self.project.logger.error('Failed to remove {}: {}'.format(target, str(ex))) + self.project.logger.error('Failed to remove {}', ex) return 1 return 0 diff --git a/dvc/command/repro.py b/dvc/command/repro.py index b049b8c8a..7a8c2b03a 100644 --- a/dvc/command/repro.py +++ b/dvc/command/repro.py @@ -11,10 +11,11 @@ class CmdRepro(CmdBase): recursive=recursive, force=self.args.force) except ReproductionError as ex: - self.project.logger.error(ex) + msg = 'Failed to reproduce \'{}\''.format(target) + self.project.logger.error(msg, ex) return 1 except Exception as ex: - msg = 'Failed to reproduce \'{}\' - unexpected error: {}'.format(target, ex) - self.project.logger.error(msg) + msg = 'Failed to reproduce \'{}\' - unexpected error'.format(target) + self.project.logger.error(msg, ex) return 1 return 0 diff --git a/dvc/command/run.py b/dvc/command/run.py index 1abfb5947..5c99296dd 100644 --- a/dvc/command/run.py +++ b/dvc/command/run.py @@ -19,7 +19,7 @@ class CmdRun(CmdBase): cwd=self.args.cwd, no_exec=self.args.no_exec) except DvcException as ex: - self.project.logger.error('Failed to run command: {}'.format(str(ex))) + self.project.logger.error('Failed to run command', ex) return 1 return 0 diff --git a/dvc/exceptions.py b/dvc/exceptions.py index f924677bc..ea55d451d 100644 --- a/dvc/exceptions.py +++ b/dvc/exceptions.py @@ -1,2 +1,8 @@ +import traceback + + class DvcException(Exception): - pass + def __init__(self, msg, cause=None): + self.cause = cause + self.cause_tb = traceback.format_exc() if cause else None + super(DvcException, self).__init__(msg) diff --git a/dvc/logger.py b/dvc/logger.py index 619146856..95a1b35ac 100644 --- a/dvc/logger.py +++ b/dvc/logger.py @@ -1,6 +1,7 @@ import sys import logging import colorama +import traceback from dvc.config import Config @@ -70,18 +71,34 @@ class Logger(object): return u'{}{}{}'.format(header, msg, footer) @staticmethod - def error(msg, **kwargs): - exc_info = Logger.logger().getEffectiveLevel() == logging.DEBUG - return Logger.logger().error(Logger.colorize(msg, 'error'), exc_info=exc_info, **kwargs) + def parse_exc(exc, tb=None): + str_tb = tb if tb else None + str_exc = ': {}'.format(str(exc)) if exc else "" + + if exc and hasattr(exc, 'cause') and exc.cause: + cause_str_exc, cause_str_tb = Logger.parse_exc(exc.cause, exc.cause_tb) + + str_tb = cause_str_tb + str_exc = '{}{}'.format(str_exc, cause_str_exc) + + return (str_exc, str_tb) @staticmethod - def warn(msg, **kwargs): - return Logger.logger().warn(Logger.colorize(msg, 'warn'), **kwargs) + def error(msg, exc=None): + str_exc, str_tb = Logger.parse_exc(exc) + if Logger.logger().getEffectiveLevel() == logging.DEBUG and exc: + str_tb = str_tb if str_tb else traceback.format_exc() + Logger.logger().error(str_tb) + return Logger.logger().error(Logger.colorize(msg + str_exc, 'error')) @staticmethod - def debug(msg, **kwargs): - return Logger.logger().debug(Logger.colorize(msg, 'debug'), **kwargs) + def warn(msg): + return Logger.logger().warn(Logger.colorize(msg, 'warn')) @staticmethod - def info(msg, **kwargs): - return Logger.logger().info(Logger.colorize(msg, 'info'), **kwargs) + def debug(msg): + return Logger.logger().debug(Logger.colorize(msg, 'debug')) + + @staticmethod + def info(msg): + return Logger.logger().info(Logger.colorize(msg, 'info')) diff --git a/dvc/main.py b/dvc/main.py index 534874ccd..b62ce0801 100644 --- a/dvc/main.py +++ b/dvc/main.py @@ -9,13 +9,13 @@ def main(argv=None): try: cmd = args.func(args) except Exception as ex: - Logger.error('Initialization error: {}'.format(str(ex))) + Logger.error('Initialization error', ex) return 255 try: ret = cmd.run_cmd() except Exception as ex: - Logger.error('Unexpected error: {}'.format(str(ex))) + Logger.error('Unexpected error', ex) return 254 return ret diff --git a/dvc/project.py b/dvc/project.py index 736a833bc..d1eb8d95c 100644 --- a/dvc/project.py +++ b/dvc/project.py @@ -28,10 +28,9 @@ class StageNotFoundError(DvcException): class ReproductionError(DvcException): - def __init__(self, dvc_file_name, msg): - self.dvc_file_name = dvc_file_name - self.msg = msg - super(ReproductionError, self).__init__(u'Failed to reproduce \'{}\': {}'.format(dvc_file_name, msg)) + def __init__(self, dvc_file_name, ex): + msg = 'Failed to reproduce \'{}\''.format(dvc_file_name) + super(ReproductionError, self).__init__(msg, cause=ex) class Project(object): @@ -161,7 +160,7 @@ class Project(object): try: result += self._reproduce_stage(stages, n, force) except Exception as ex: - raise ReproductionError(n, str(ex)) + raise ReproductionError(n, ex) return result def checkout(self): diff --git a/dvc/utils.py b/dvc/utils.py index 55b3ee95b..55e4c037a 100644 --- a/dvc/utils.py +++ b/dvc/utils.py @@ -83,7 +83,6 @@ def map_progress(func, targets, n_threads): try: ret = pool.map(func, targets) except Exception as exc: -# Logger.error('Unexpected exception while processing targets: {}'.format(exc), exc_info=True) raise finally: progress.finish()