fix(Request): UTF-8 logging

This commit is contained in:
Kurt Griffiths
2013-02-04 12:27:59 -05:00
parent 74ebc91429
commit a1d33a69cf
5 changed files with 58 additions and 31 deletions

View File

@@ -16,12 +16,15 @@ limitations under the License.
""" """
import sys
from datetime import datetime from datetime import datetime
import six
from falcon.request_helpers import * from falcon.request_helpers import *
from falcon.exceptions import * from falcon.exceptions import *
import six
DEFAULT_ERROR_LOG_FORMAT = ('{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR]'
' {1} {2}?{3} => {4}\n')
class Request(object): class Request(object):
@@ -50,21 +53,23 @@ class Request(object):
""" """
self.app = env['SCRIPT_NAME'] self._wsgierrors = env['wsgi.errors']
self.body = env['wsgi.input'] self.body = env['wsgi.input']
self.protocol = env['wsgi.url_scheme']
self.app = env['SCRIPT_NAME']
self.method = env['REQUEST_METHOD'] self.method = env['REQUEST_METHOD']
self.path = env['PATH_INFO'] or '/' self.path = env['PATH_INFO'] or '/'
self.protocol = env['wsgi.url_scheme']
self.query_string = query_string = env['QUERY_STRING'] self.query_string = query_string = env['QUERY_STRING']
self._params = parse_query_string(query_string) self._params = parse_query_string(query_string)
self._headers = parse_headers(env) self._headers = parse_headers(env)
self._wsgierrors = env['wsgi.errors']
def log_error(self, message): def log_error(self, message):
"""Log an error to wsgi.error """Log an error to wsgi.error
Prepends timestamp and request info to message, and writes the result Prepends timestamp and request info to message, and writes the
out to the WSGI server's error stream (wsgi.error). result out to the WSGI server's error stream (wsgi.error).
Args: Args:
message: A string describing the problem. If a byte-string and message: A string describing the problem. If a byte-string and
@@ -72,11 +77,13 @@ class Request(object):
as UTF-8. as UTF-8.
""" """
u = six.text_type if not six.PY3 and isinstance(message, unicode):
message = message.encode('utf-8')
log_line = ( log_line = (
u('{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR] {1} {2}?{3} => {4}\n'). DEFAULT_ERROR_LOG_FORMAT.
format(datetime.now(), self.method, self.path, self.query_string, format(datetime.now(), self.method, self.path,
message) self.query_string, message)
) )
self._wsgierrors.write(log_line) self._wsgierrors.write(log_line)

View File

@@ -1,4 +1,11 @@
import pdb
from wsgiref.simple_server import make_server
def application(environ, start_response): def application(environ, start_response):
wsgi_errors = environ['wsgi.errors']
pdb.set_trace()
start_response("200 OK", [ start_response("200 OK", [
('Content-Type', 'text/plain')]) ('Content-Type', 'text/plain')])
@@ -13,9 +20,7 @@ def application(environ, start_response):
app = application app = application
if __name__ == '__main__':
import wsgiref
from wsgiref.simple_server import make_server
if __name__ == '__main__':
server = make_server('localhost', 8000, application) server = make_server('localhost', 8000, application)
server.serve_forever() server.serve_forever()

View File

@@ -1,14 +1,13 @@
import io import io
import sys import six
from . import helpers from . import helpers
unicode_message = u'Unicode: \x80' unicode_message = u'Unicode: \x80'
if sys.version_info[0] == 2: if six.PY3:
str_message = 'UTF-8: \xc2\x80'
else:
str_message = 'Unicode all the way: \x80' str_message = 'Unicode all the way: \x80'
else:
str_message = 'UTF-8: \xc2\x80'
class BombResource: class BombResource:
@@ -37,31 +36,46 @@ class TestWSGIError(helpers.TestSuite):
self.api.add_route('/bomb', self.tehbomb) self.api.add_route('/bomb', self.tehbomb)
self.api.add_route('/logger', self.tehlogger) self.api.add_route('/logger', self.tehlogger)
self.wsgierrors = io.StringIO()
self.wsgierrors_buffer = io.BytesIO()
if six.PY3:
# Simulate Gunicorn's behavior under Python 3
self.wsgierrors = io.TextIOWrapper(self.wsgierrors_buffer,
line_buffering=True)
else:
# WSGI servers typically present an open file object,
# with undefined encoding, so do the encoding manually.
self.wsgierrors = self.wsgierrors_buffer
def test_exception_logged(self): def test_exception_logged(self):
self._simulate_request('/bomb', wsgierrors=self.wsgierrors) self._simulate_request('/bomb', wsgierrors=self.wsgierrors)
log = self.wsgierrors.getvalue()
self.assertIn('IOError', log) log = self.wsgierrors_buffer.getvalue()
self.assertIn(b'IOError', log)
def test_exception_logged_with_details(self): def test_exception_logged_with_details(self):
self._simulate_request('/bomb', wsgierrors=self.wsgierrors, self._simulate_request('/bomb', wsgierrors=self.wsgierrors,
method='HEAD') method='HEAD')
log = self.wsgierrors.getvalue()
self.assertIn('MemoryError', log) log = self.wsgierrors_buffer.getvalue()
self.assertIn('remember a thing', log)
self.assertIn(b'MemoryError', log)
self.assertIn(b'remember a thing', log)
def test_responder_logged_unicode(self): def test_responder_logged_unicode(self):
self._simulate_request('/logger', wsgierrors=self.wsgierrors) self._simulate_request('/logger', wsgierrors=self.wsgierrors)
log = self.wsgierrors.getvalue() log = self.wsgierrors_buffer.getvalue()
self.assertIn(unicode_message, log) self.assertIn(unicode_message.encode('utf-8'), log)
def test_responder_logged_str(self): def test_responder_logged_str(self):
self._simulate_request('/logger', wsgierrors=self.wsgierrors, self._simulate_request('/logger', wsgierrors=self.wsgierrors,
method='HEAD') method='HEAD')
log = self.wsgierrors.getvalue() log = self.wsgierrors_buffer.getvalue()
self.assertIn(str_message, log)
if six.PY3:
self.assertIn(str_message.encode('utf-8'), log)
else:
self.assertIn(str_message, log)

View File

@@ -1,7 +1,8 @@
[tox] [tox]
envlist = py27,py32 envlist = py27,py33
deps = -r{toxinidir}/tests/requirements.txt
[testenv] [testenv]
deps = -r{toxinidir}/requirements.txt deps = -r{toxinidir}/tests/requirements.txt
commands = nosetests --with-progressive commands = nosetests --with-progressive