From fe810020c490d2c1bd8b32f806d28d80ad537ab1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 25 Sep 2015 13:26:45 -0700 Subject: [PATCH 1/3] Made error logging entries red in the terminal --- letsencrypt/cli.py | 3 +- letsencrypt/colored_logging.py | 47 +++++++++++++++++++++++ letsencrypt/le_util.py | 4 ++ letsencrypt/reporter.py | 6 +-- letsencrypt/tests/colored_logging_test.py | 39 +++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 letsencrypt/colored_logging.py create mode 100644 letsencrypt/tests/colored_logging_test.py diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index e4787a849..7cb4a0458 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -24,6 +24,7 @@ from acme import jose import letsencrypt from letsencrypt import account +from letsencrypt import colored_logging from letsencrypt import configuration from letsencrypt import constants from letsencrypt import client @@ -786,7 +787,7 @@ def _setup_logging(args): level = -args.verbose_count * 10 fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" if args.text_mode: - handler = logging.StreamHandler() + handler = colored_logging.StreamHandler() handler.setFormatter(logging.Formatter(fmt)) else: handler = log.DialogHandler() diff --git a/letsencrypt/colored_logging.py b/letsencrypt/colored_logging.py new file mode 100644 index 000000000..89239e2c7 --- /dev/null +++ b/letsencrypt/colored_logging.py @@ -0,0 +1,47 @@ +"""A formatter and StreamHandler for colorizing logging output.""" +import logging +import sys + +from letsencrypt import le_util + + +class StreamHandler(logging.StreamHandler): + """Sends colored logging output to a stream. + + If the specified stream is not a tty, the class works like the + standard logging.StreamHandler. Default red_level is logging.WARNING. + + :ivar bool colored: True if output should be colored + :ivar bool red_level: The level at which to output + + """ + _RED = '\033[31m' + + def __init__(self, stream=None): + super(StreamHandler, self).__init__(stream) + self.colored = (sys.stderr.isatty() if stream is None else + stream.isatty()) + self.set_red_level(logging.WARNING) + + def format(self, record): + """Formats the string representation of record. + + :param logging.LogRecord record: Record to be formatted + + :returns: Formatted, string representation of record + :rtype: str + + """ + output = super(StreamHandler, self).format(record) + if self.colored and record.levelno >= self.red_level: + return ''.join((self._RED, output, le_util.ANSI_SGR_RESET)) + else: + return output + + def set_red_level(self, red_level): + """Sets the level necessary to display output in red. + + :param int red_level: Minimum log level for displaying red text + + """ + self.red_level = red_level diff --git a/letsencrypt/le_util.py b/letsencrypt/le_util.py index ffc7da190..74e03d8a1 100644 --- a/letsencrypt/le_util.py +++ b/letsencrypt/le_util.py @@ -18,6 +18,10 @@ Key = collections.namedtuple("Key", "file pem") CSR = collections.namedtuple("CSR", "file data form") +# ANSI escape code for resetting output format +ANSI_SGR_RESET = "\033[0m" + + def run_script(params): """Run the script with the given params. diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 0c4a7b378..86413053e 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -9,6 +9,7 @@ import textwrap import zope.interface from letsencrypt import interfaces +from letsencrypt import le_util logger = logging.getLogger(__name__) @@ -30,7 +31,6 @@ class Reporter(object): LOW_PRIORITY = 2 """Low priority constant. See `add_message`.""" - _RESET = '\033[0m' _BOLD = '\033[1m' _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') @@ -87,7 +87,7 @@ class Reporter(object): msg = self.messages.get() if no_exception or msg.on_crash: if bold_on and msg.priority > self.HIGH_PRIORITY: - sys.stdout.write(self._RESET) + sys.stdout.write(le_util.ANSI_SGR_RESET) bold_on = False lines = msg.text.splitlines() print first_wrapper.fill(lines[0]) @@ -95,4 +95,4 @@ class Reporter(object): print "\n".join( next_wrapper.fill(line) for line in lines[1:]) if bold_on: - sys.stdout.write(self._RESET) + sys.stdout.write(le_util.ANSI_SGR_RESET) diff --git a/letsencrypt/tests/colored_logging_test.py b/letsencrypt/tests/colored_logging_test.py new file mode 100644 index 000000000..fc97b2a49 --- /dev/null +++ b/letsencrypt/tests/colored_logging_test.py @@ -0,0 +1,39 @@ +"""Tests for letsencrypt.colored_logging.""" +import logging +import StringIO +import unittest + +from letsencrypt import le_util + + +class StreamHandlerTest(unittest.TestCase): + + def setUp(self): + from letsencrypt import colored_logging + + self.stream = StringIO.StringIO() + self.stream.isatty = lambda: True + self.handler = colored_logging.StreamHandler(self.stream) + + self.logger = logging.getLogger() + self.logger.setLevel(logging.DEBUG) + self.logger.addHandler(self.handler) + + def test_format(self): + msg = 'I did a thing' + self.logger.debug(msg) + self.assertEqual(self.stream.getvalue(), '{0}\n'.format(msg)) + + def test_format_and_red_level(self): + msg = 'I did another thing' + self.handler.set_red_level(logging.DEBUG) + self.logger.debug(msg) + + # pylint: disable=protected-access + expected = '{0}{1}{2}\n'.format(self.handler._RED, msg, + le_util.ANSI_SGR_RESET) + self.assertEqual(self.stream.getvalue(), expected) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover From 817aadae6abd2c8e7ed4c3d038ba7cfe18f93be6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 25 Sep 2015 13:27:19 -0700 Subject: [PATCH 2/3] Fixed indentation --- letsencrypt/colored_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/colored_logging.py b/letsencrypt/colored_logging.py index 89239e2c7..f5750870e 100644 --- a/letsencrypt/colored_logging.py +++ b/letsencrypt/colored_logging.py @@ -26,7 +26,7 @@ class StreamHandler(logging.StreamHandler): def format(self, record): """Formats the string representation of record. - :param logging.LogRecord record: Record to be formatted + :param logging.LogRecord record: Record to be formatted :returns: Formatted, string representation of record :rtype: str From 2015811a6c84682466005566afd795ea4c03f10f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Sat, 26 Sep 2015 12:18:32 -0700 Subject: [PATCH 3/3] Incorporated Kuba's feedback --- letsencrypt/colored_logging.py | 15 ++++----------- letsencrypt/le_util.py | 7 ++++++- letsencrypt/reporter.py | 3 +-- letsencrypt/tests/colored_logging_test.py | 11 ++++++----- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/letsencrypt/colored_logging.py b/letsencrypt/colored_logging.py index f5750870e..170da0b38 100644 --- a/letsencrypt/colored_logging.py +++ b/letsencrypt/colored_logging.py @@ -15,13 +15,12 @@ class StreamHandler(logging.StreamHandler): :ivar bool red_level: The level at which to output """ - _RED = '\033[31m' def __init__(self, stream=None): super(StreamHandler, self).__init__(stream) self.colored = (sys.stderr.isatty() if stream is None else stream.isatty()) - self.set_red_level(logging.WARNING) + self.red_level = logging.WARNING def format(self, record): """Formats the string representation of record. @@ -34,14 +33,8 @@ class StreamHandler(logging.StreamHandler): """ output = super(StreamHandler, self).format(record) if self.colored and record.levelno >= self.red_level: - return ''.join((self._RED, output, le_util.ANSI_SGR_RESET)) + return ''.join((le_util.ANSI_SGR_RED, + output, + le_util.ANSI_SGR_RESET)) else: return output - - def set_red_level(self, red_level): - """Sets the level necessary to display output in red. - - :param int red_level: Minimum log level for displaying red text - - """ - self.red_level = red_level diff --git a/letsencrypt/le_util.py b/letsencrypt/le_util.py index 74e03d8a1..5626902ef 100644 --- a/letsencrypt/le_util.py +++ b/letsencrypt/le_util.py @@ -18,7 +18,12 @@ Key = collections.namedtuple("Key", "file pem") CSR = collections.namedtuple("CSR", "file data form") -# ANSI escape code for resetting output format +# ANSI SGR escape codes +# Formats text as bold or with increased intensity +ANSI_SGR_BOLD = '\033[1m' +# Colors text red +ANSI_SGR_RED = "\033[31m" +# Resets output format ANSI_SGR_RESET = "\033[0m" diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 86413053e..482305838 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -31,7 +31,6 @@ class Reporter(object): LOW_PRIORITY = 2 """Low priority constant. See `add_message`.""" - _BOLD = '\033[1m' _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') def __init__(self): @@ -76,7 +75,7 @@ class Reporter(object): no_exception = sys.exc_info()[0] is None bold_on = sys.stdout.isatty() if bold_on: - print self._BOLD + print le_util.ANSI_SGR_BOLD print 'IMPORTANT NOTES:' first_wrapper = textwrap.TextWrapper( initial_indent=' - ', subsequent_indent=(' ' * 3)) diff --git a/letsencrypt/tests/colored_logging_test.py b/letsencrypt/tests/colored_logging_test.py index fc97b2a49..5b49ec820 100644 --- a/letsencrypt/tests/colored_logging_test.py +++ b/letsencrypt/tests/colored_logging_test.py @@ -7,6 +7,7 @@ from letsencrypt import le_util class StreamHandlerTest(unittest.TestCase): + """Tests for letsencrypt.colored_logging.""" def setUp(self): from letsencrypt import colored_logging @@ -26,13 +27,13 @@ class StreamHandlerTest(unittest.TestCase): def test_format_and_red_level(self): msg = 'I did another thing' - self.handler.set_red_level(logging.DEBUG) + self.handler.red_level = logging.DEBUG self.logger.debug(msg) - # pylint: disable=protected-access - expected = '{0}{1}{2}\n'.format(self.handler._RED, msg, - le_util.ANSI_SGR_RESET) - self.assertEqual(self.stream.getvalue(), expected) + self.assertEqual(self.stream.getvalue(), + '{0}{1}{2}\n'.format(le_util.ANSI_SGR_RED, + msg, + le_util.ANSI_SGR_RESET)) if __name__ == "__main__":