Skip to content

Commit

Permalink
Merge pull request #7 from sprockets/add-traceback
Browse files Browse the repository at this point in the history
Add traceback
  • Loading branch information
dave-shawley committed Aug 28, 2015
2 parents 2a92b3b + e9440f0 commit 16a57b6
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 7 deletions.
4 changes: 4 additions & 0 deletions docs/history.rst
@@ -1,6 +1,10 @@
Version History
===============

`1.3.0`_ Aug 28, 2015
---------------------
- Add the traceback and environment if set

`1.2.1`_ Jun 24, 2015
---------------------
- Fix a potential ``KeyError`` when a HTTP request object is not present.
Expand Down
38 changes: 35 additions & 3 deletions sprockets/logging.py
Expand Up @@ -13,14 +13,16 @@
from logging import config
import json
import logging
import os
import sys
import traceback

try:
from tornado import log
except ImportError:
log = None

version_info = (1, 2, 1)
version_info = (1, 3, 0)
__version__ = '.'.join(str(v) for v in version_info)

# Shortcut methods and constants to avoid needing to import logging directly
Expand Down Expand Up @@ -65,13 +67,41 @@ class JSONRequestFormatter(logging.Formatter):
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):
"""Return the log data as JSON
:param record logging.LogRecord: The record to format
:rtype: str
"""
if hasattr(record, 'exc_info'):
try:
traceback = self.extract_exc_record(*record.exc_info)
except:
traceback = None

output = {'name': record.name,
'module': record.module,
'message': record.msg % record.args,
Expand All @@ -81,7 +111,8 @@ def format(self, record):
'timestamp': self.formatTime(record),
'thread': record.threadName,
'file': record.filename,
'request': record.args}
'request': record.args,
'traceback': traceback}
for key, value in list(output.items()):
if not value:
del output[key]
Expand Down Expand Up @@ -119,7 +150,8 @@ def tornado_log_function(handler):
'protocol': handler.request.protocol,
'query_args': handler.request.query_arguments,
'remote_ip': handler.request.remote_ip,
'status_code': status_code})
'status_code': status_code,
'environment': os.environ.get('ENVIRONMENT')})


def currentframe():
Expand Down
41 changes: 38 additions & 3 deletions tests.py
@@ -1,15 +1,17 @@
import json
import logging
import os
import random
import unittest
import uuid

import mock

import sprockets.logging
from tornado import web, testing

LOGGER = logging.getLogger(__name__)

os.environ['ENVIRONMENT'] = 'testing'

class Prototype(object):
pass
Expand Down Expand Up @@ -91,9 +93,11 @@ def test_log_function_return_value(self, access_log):
'protocol': handler.request.protocol,
'query_args': handler.request.query_arguments,
'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)
access_log.assertCalledOnceWith(expectation)
access_log.info.assert_called_once_with(*expectation)



class JSONRequestHandlerTestCase(unittest.TestCase):
Expand Down Expand Up @@ -135,3 +139,34 @@ def handle(self, value):
value = json.loads(result)
for key in keys:
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)
5 changes: 4 additions & 1 deletion tox.ini
Expand Up @@ -7,4 +7,7 @@ skip_missing_interpreters = true

[testenv]
commands = nosetests []
deps = nose
deps =
nose
mock
tornado

0 comments on commit 16a57b6

Please sign in to comment.