Repository: certbot/certbot

Issue:
log.DialogHandler.emit gets unexpected dict
```
# letsencrypt -ed $IP
Traceback (most recent call last):                                                                                                                                                                                   
  File "/root/lets-encrypt-preview/venv/bin/letsencrypt", line 9, in <module>
    load_entry_point('letsencrypt==0.1', 'console_scripts', 'letsencrypt')()
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/scripts/main.py", line 205, in main
    cert_file, chain_file = acme.obtain_certificate(doms)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/client.py", line 90, in obtain_certificate
    name, self.acme_challenge(name), self.authkey)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/client.py", line 120, in acme_challenge
    messages.Challenge)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/network.py", line 79, in send_and_receive_expected
    response = self.send(msg)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/network.py", line 64, in send
    logging.error(json_string)
  File "/usr/lib/python2.7/logging/__init__.py", line 1584, in error
    root.error(msg, *args, **kwargs)
  File "/usr/lib/python2.7/logging/__init__.py", line 1174, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/lib/python2.7/logging/__init__.py", line 1267, in _log
    self.handle(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 1277, in handle
    self.callHandlers(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 1317, in callHandlers
    hdlr.handle(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 748, in handle
    self.emit(record)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/log.py", line 40, in emit
    for line in (record.msg % record.args).splitlines():
TypeError: unsupported operand type(s) for %: 'dict' and 'tuple'
```

and in text mode:

```
# letsencrypt -ted $IP
INFO:root:Generating key (2048 bits): /etc/letsencrypt/keys/0007_key-letsencrypt.pem
ERROR:root:{u'message': u"Requested action would violate the server's policy", u'type': u'error', u'error': u'forbidden'}
Traceback (most recent call last):
  File "/root/lets-encrypt-preview/venv/bin/letsencrypt", line 9, in <module>
    load_entry_point('letsencrypt==0.1', 'console_scripts', 'letsencrypt')()
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/scripts/main.py", line 205, in main
    cert_file, chain_file = acme.obtain_certificate(doms)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/client.py", line 90, in obtain_certificate
    name, self.acme_challenge(name), self.authkey)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/client.py", line 120, in acme_challenge
    messages.Challenge)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/network.py", line 79, in send_and_receive_expected
    response = self.send(msg)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/client/network.py", line 62, in send
    return messages.Message.from_json(json_string)
  File "/root/lets-encrypt-preview/venv/local/lib/python2.7/site-packages/letsencrypt-0.1-py2.7.egg/letsencrypt/acme/messages.py", line 50, in from_json
    raise errors.SchemaValidationError(error)
letsencrypt.acme.errors.SchemaValidationError: u'forbidden' is not one of [u'malformed', u'unauthorized', u'serverInternal', u'nonSupported', u'unknown', u'badCSR']

Failed validating u'enum' in schema[u'properties'][u'error']:
    {u'enum': [u'malformed',
               u'unauthorized',
               u'serverInternal',
               u'nonSupported',
               u'unknown',
               u'badCSR']}

On instance[u'error']:
    u'forbidden'
```

`emit()` should probably call `str(record.msg)`...



Discussion:

Resolving patch:
diff --git a/letsencrypt/client/log.py b/letsencrypt/client/log.py
--- a/letsencrypt/client/log.py
+++ b/letsencrypt/client/log.py
@@ -37,7 +37,7 @@ def emit(self, record):
         lines.
 
         """
-        for line in (record.msg % record.args).splitlines():
+        for line in record.getMessage().splitlines():
             # check for lines that would wrap
             cur_out = line
             while len(cur_out) > self.width:


Test patch:
diff --git a/letsencrypt/client/tests/log_test.py b/letsencrypt/client/tests/log_test.py
--- a/letsencrypt/client/tests/log_test.py
+++ b/letsencrypt/client/tests/log_test.py
@@ -1,4 +1,5 @@
 """Tests for letsencrypt.client.log."""
+import logging
 import unittest
 
 import mock
@@ -15,29 +16,33 @@ def setUp(self):
         self.handler.PADDING_WIDTH = 4
 
     def test_adds_padding(self):
-        self.handler.emit(mock.MagicMock())
+        self.handler.emit(logging.makeLogRecord({}))
         self.d.infobox.assert_called_once_with(mock.ANY, 4, 10)
 
     def test_args_in_msg_get_replaced(self):
         assert len('123456') <= self.handler.width
-        self.handler.emit(mock.MagicMock(msg='123%s', args=(456,)))
+        self.handler.emit(logging.makeLogRecord(
+            {'msg': '123%s', 'args': (456,)}))
         self.d.infobox.assert_called_once_with('123456', mock.ANY, mock.ANY)
 
     def test_wraps_nospace_is_greedy(self):
         assert len('1234567') > self.handler.width
-        self.handler.emit(mock.MagicMock(msg='1234567'))
+        self.handler.emit(logging.makeLogRecord({'msg': '1234567'}))
         self.d.infobox.assert_called_once_with('123456\n7', mock.ANY, mock.ANY)
 
     def test_wraps_at_whitespace(self):
         assert len('123 567') > self.handler.width
-        self.handler.emit(mock.MagicMock(msg='123 567'))
+        self.handler.emit(logging.makeLogRecord({'msg': '123 567'}))
         self.d.infobox.assert_called_once_with('123\n567', mock.ANY, mock.ANY)
 
     def test_only_last_lines_are_printed(self):
         assert len('a\nb\nc'.split()) > self.handler.height
-        self.handler.emit(mock.MagicMock(msg='a\n\nb\nc'))
+        self.handler.emit(logging.makeLogRecord({'msg': 'a\n\nb\nc'}))
         self.d.infobox.assert_called_once_with('b\nc', mock.ANY, mock.ANY)
 
+    def test_non_str(self):
+        self.handler.emit(logging.makeLogRecord({'msg': {'foo': 'bar'}}))
+
 
 if __name__ == '__main__':
     unittest.main()