Merge pull request #7 from sprockets/add-traceback

Add traceback
This commit is contained in:
dave-shawley 2015-08-28 17:24:55 -04:00
commit 16a57b6484
4 changed files with 81 additions and 7 deletions

View file

@ -1,6 +1,10 @@
Version History Version History
=============== ===============
`1.3.0`_ Aug 28, 2015
---------------------
- Add the traceback and environment if set
`1.2.1`_ Jun 24, 2015 `1.2.1`_ Jun 24, 2015
--------------------- ---------------------
- Fix a potential ``KeyError`` when a HTTP request object is not present. - Fix a potential ``KeyError`` when a HTTP request object is not present.

View file

@ -13,14 +13,16 @@ from __future__ import absolute_import
from logging import config from logging import config
import json import json
import logging import logging
import os
import sys import sys
import traceback
try: try:
from tornado import log from tornado import log
except ImportError: except ImportError:
log = None log = None
version_info = (1, 2, 1) version_info = (1, 3, 0)
__version__ = '.'.join(str(v) for v in version_info) __version__ = '.'.join(str(v) for v in version_info)
# Shortcut methods and constants to avoid needing to import logging directly # Shortcut methods and constants to avoid needing to import logging directly
@ -65,6 +67,28 @@ class JSONRequestFormatter(logging.Formatter):
the log data as JSON. the log data as JSON.
""" """
def extract_exc_record(self, typ, val, tb):
"""Create a JSON representation of the traceback given the records
exc_info
:param `Exception` typ: Exception type of the exception being handled
:param `Exception` instance val: instance of the Exception class
:param `traceback` tb: traceback object with the call stack
:rtype: dict
"""
exc_record = {'type': typ.__name__,
'message': str(val),
'stack': []}
for file_name, line_no, func_name, txt in traceback.extract_tb(tb):
exc_record['stack'].append({'file': file_name,
'line': str(line_no),
'func': func_name,
'text': txt})
return exc_record
def format(self, record): def format(self, record):
"""Return the log data as JSON """Return the log data as JSON
@ -72,6 +96,12 @@ class JSONRequestFormatter(logging.Formatter):
:rtype: str :rtype: str
""" """
if hasattr(record, 'exc_info'):
try:
traceback = self.extract_exc_record(*record.exc_info)
except:
traceback = None
output = {'name': record.name, output = {'name': record.name,
'module': record.module, 'module': record.module,
'message': record.msg % record.args, 'message': record.msg % record.args,
@ -81,7 +111,8 @@ class JSONRequestFormatter(logging.Formatter):
'timestamp': self.formatTime(record), 'timestamp': self.formatTime(record),
'thread': record.threadName, 'thread': record.threadName,
'file': record.filename, 'file': record.filename,
'request': record.args} 'request': record.args,
'traceback': traceback}
for key, value in list(output.items()): for key, value in list(output.items()):
if not value: if not value:
del output[key] del output[key]
@ -119,7 +150,8 @@ def tornado_log_function(handler):
'protocol': handler.request.protocol, 'protocol': handler.request.protocol,
'query_args': handler.request.query_arguments, 'query_args': handler.request.query_arguments,
'remote_ip': handler.request.remote_ip, 'remote_ip': handler.request.remote_ip,
'status_code': status_code}) 'status_code': status_code,
'environment': os.environ.get('ENVIRONMENT')})
def currentframe(): def currentframe():

View file

@ -1,5 +1,6 @@
import json import json
import logging import logging
import os
import random import random
import unittest import unittest
import uuid import uuid
@ -7,9 +8,10 @@ import uuid
import mock import mock
import sprockets.logging import sprockets.logging
from tornado import web, testing
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
os.environ['ENVIRONMENT'] = 'testing'
class Prototype(object): class Prototype(object):
pass pass
@ -91,9 +93,11 @@ class TornadoLogFunctionTestCase(unittest.TestCase):
'protocol': handler.request.protocol, 'protocol': handler.request.protocol,
'query_args': handler.request.query_arguments, 'query_args': handler.request.query_arguments,
'remote_ip': handler.request.remote_ip, 'remote_ip': handler.request.remote_ip,
'status_code': handler.status_code}) 'status_code': handler.status_code,
'environment': os.environ['ENVIRONMENT']})
sprockets.logging.tornado_log_function(handler) sprockets.logging.tornado_log_function(handler)
access_log.assertCalledOnceWith(expectation) access_log.info.assert_called_once_with(*expectation)
class JSONRequestHandlerTestCase(unittest.TestCase): class JSONRequestHandlerTestCase(unittest.TestCase):
@ -135,3 +139,34 @@ class JSONRequestHandlerTestCase(unittest.TestCase):
value = json.loads(result) value = json.loads(result)
for key in keys: for key in keys:
self.assertIn(key, value) self.assertIn(key, value)
class JSONRequestFormatterTestCase(testing.AsyncHTTPTestCase):
def setUp(self):
super(JSONRequestFormatterTestCase, self).setUp()
self.recorder = RecordingHandler()
self.formatter = sprockets.logging.JSONRequestFormatter()
self.recorder.setFormatter(self.formatter)
web.app_log.addHandler(self.recorder)
def tearDown(self):
super(JSONRequestFormatterTestCase, self).tearDown()
web.app_log.removeHandler(self.recorder)
def get_app(self):
class JustFail(web.RequestHandler):
def get(self):
raise RuntimeError('something busted')
return web.Application([web.url('/', JustFail)])
def test_that_things_happen(self):
self.fetch('/')
self.assertEqual(len(self.recorder.log_lines), 1)
failure_info = json.loads(self.recorder.log_lines[0])
self.assertEqual(failure_info['traceback']['type'], 'RuntimeError')
self.assertEqual(failure_info['traceback']['message'],
'something busted')
self.assertEqual(len(failure_info['traceback']['stack']), 2)

View file

@ -7,4 +7,7 @@ skip_missing_interpreters = true
[testenv] [testenv]
commands = nosetests [] commands = nosetests []
deps = nose deps =
nose
mock
tornado