Added traceback dump

This commit is contained in:
Brad Warren
2015-06-30 12:57:51 -07:00
parent 85b5bc0cb2
commit 13913fd8e0
3 changed files with 64 additions and 33 deletions
+1
View File
@@ -5,6 +5,7 @@ build/
dist/ dist/
/venv/ /venv/
/.tox/ /.tox/
letsencrypt.log
# coverage # coverage
.coverage .coverage
+32 -7
View File
@@ -645,18 +645,43 @@ def _setup_logging(args):
def _handle_exception(exc_type, exc_value, trace, args): def _handle_exception(exc_type, exc_value, trace, args):
"""Logs exceptions and reports them to the user.
Args is used to determine how to display exceptions to the user. In
general, if args.debug is True, then the full exception and traceback is
shown to the user, otherwise it is suppressed. If args itself is None,
then the traceback and exception is attempted to be written to a logfile.
If this is successful, the traceback is suppressed, otherwise it is shown
to the user. sys.exit is always called with a nonzero status.
"""
logger.debug( logger.debug(
"Exiting abnormally:\n%s", "Exiting abnormally:\n%s",
"".join(traceback.format_exception(exc_type, exc_value, trace))) "".join(traceback.format_exception(exc_type, exc_value, trace)))
if issubclass(exc_type, errors.Error) and (not args or not args.debug): if issubclass(exc_type, Exception) and (args is None or not args.debug):
sys.exit(exc_value) if args is None:
elif issubclass(exc_type, Exception) and args and not args.debug: try:
sys.exit( with open("letsencrypt.log", "w") as logfile:
"An unexpected error occurred. Please see the logfiles in {0} for " traceback.print_exception(
"more details.".format(args.logs_dir)) exc_type, exc_value, trace, file=logfile)
except: # pylint: disable=bare-except
sys.exit("".join(
traceback.format_exception(exc_type, exc_value, trace)))
if issubclass(exc_type, errors.Error):
sys.exit(exc_value)
elif args is None:
sys.exit(
"An unexpected error occurred. Please see the logfile '{0}' "
"for more details.".format(os.path.abspath("letsencrypt.log")))
else:
sys.exit(
"An unexpected error occurred. Please see the logfiles in {0} "
"for more details.".format(args.logs_dir))
else: else:
traceback.print_exception(exc_type, exc_value, trace, file=sys.stderr) sys.exit("".join(
traceback.format_exception(exc_type, exc_value, trace)))
def main(cli_args=sys.argv[1:]): def main(cli_args=sys.argv[1:]):
+31 -26
View File
@@ -2,11 +2,14 @@
import itertools import itertools
import os import os
import shutil import shutil
import traceback
import tempfile import tempfile
import unittest import unittest
import mock import mock
from letsencrypt import errors
class CLITest(unittest.TestCase): class CLITest(unittest.TestCase):
"""Tests for different commands.""" """Tests for different commands."""
@@ -20,16 +23,13 @@ class CLITest(unittest.TestCase):
def tearDown(self): def tearDown(self):
shutil.rmtree(self.tmp_dir) shutil.rmtree(self.tmp_dir)
def _call(self, args, client_mock_attrs=None): def _call(self, args):
from letsencrypt import cli from letsencrypt import cli
args = ['--text', '--config-dir', self.config_dir, args = ['--text', '--config-dir', self.config_dir,
'--work-dir', self.work_dir, '--logs-dir', self.logs_dir] + args '--work-dir', self.work_dir, '--logs-dir', self.logs_dir] + args
with mock.patch('letsencrypt.cli.sys.stdout') as stdout: with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
with mock.patch('letsencrypt.cli.sys.stderr') as stderr: with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client: with mock.patch('letsencrypt.cli.client') as client:
if client_mock_attrs:
# pylint: disable=star-args
client.configure_mock(**client_mock_attrs)
ret = cli.main(args) ret = cli.main(args)
return ret, stdout, stderr, client return ret, stdout, stderr, client
@@ -59,36 +59,41 @@ class CLITest(unittest.TestCase):
for r in xrange(len(flags)))): for r in xrange(len(flags)))):
self._call(['plugins',] + list(args)) self._call(['plugins',] + list(args))
def test_exceptions(self):
from letsencrypt import errors
cmd_arg = ['config_changes']
error = [errors.Error('problem')]
attrs = {'view_config_changes.side_effect' : error}
self.assertRaises(
errors.Error, self._call, ['--debug'] + cmd_arg, attrs)
attrs['view_config_changes.side_effect'] = [ValueError]
self.assertRaises(
ValueError, self._call, ['--debug'] + cmd_arg, attrs)
@mock.patch("letsencrypt.cli.sys") @mock.patch("letsencrypt.cli.sys")
def test_handle_exception(self, mock_sys): def test_handle_exception(self, mock_sys):
# pylint: disable=protected-access # pylint: disable=protected-access
import StringIO
from letsencrypt import cli from letsencrypt import cli
from letsencrypt import errors
cli._handle_exception(errors.Error, "detail", None, None) mock_open = mock.mock_open()
mock_sys.exit.assert_called_once_with("detail") with mock.patch("letsencrypt.cli.open", mock_open, create=True):
exception = Exception("detail")
cli._handle_exception(
Exception, exc_value=exception, trace=None, args=None)
mock_open().write.assert_called_once_with("".join(
traceback.format_exception_only(Exception, exception)))
error_msg = mock_sys.exit.call_args_list[0][0][0]
self.assertTrue("unexpected error" in error_msg)
with mock.patch("letsencrypt.cli.open", mock_open, create=True):
mock_open.side_effect = [KeyboardInterrupt]
error = errors.Error("detail")
cli._handle_exception(
errors.Error, exc_value=error, trace=None, args=None)
# assert_any_call used because sys.exit doesn't exit in cli.py
mock_sys.exit.assert_any_call("".join(
traceback.format_exception_only(errors.Error, error)))
args = mock.MagicMock(debug=False) args = mock.MagicMock(debug=False)
cli._handle_exception(ValueError, "detail", None, args) cli._handle_exception(
self.assertTrue("logfile" in mock_sys.exit.call_args_list[1][0][0]) Exception, exc_value=Exception("detail"), trace=None, args=args)
error_msg = mock_sys.exit.call_args_list[-1][0][0]
self.assertTrue("unexpected error" in error_msg)
mock_sys.stderr = StringIO.StringIO() interrupt = KeyboardInterrupt("detail")
exc_value = "A very specific string" cli._handle_exception(
cli._handle_exception(KeyboardInterrupt, exc_value, None, None) KeyboardInterrupt, exc_value=interrupt, trace=None, args=None)
self.assertTrue(exc_value in mock_sys.stderr.getvalue()) mock_sys.exit.assert_called_with("".join(
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
if __name__ == '__main__': if __name__ == '__main__':