Add support for Tornado >= 4.5

Tornado 4.5 changed request routing in a breaking way. This adds support
for the new behavior while preserving existing behavior for older
versions of Tornado.
This commit is contained in:
Andrew Rabert 2018-01-22 23:53:56 -05:00
parent 26ff5b3363
commit 662263e48d
2 changed files with 111 additions and 8 deletions

View file

@ -30,6 +30,12 @@ except ImportError: # pragma: no cover
logging.critical('Could not import Tornado') logging.critical('Could not import Tornado')
concurrent, httpclient, ioloop = None, None, None concurrent, httpclient, ioloop = None, None, None
try:
from tornado import routing
except ImportError: # Not needed for Tornado<4.5
pass
version_info = (2, 1, 0) version_info = (2, 1, 0)
__version__ = '.'.join(str(v) for v in version_info) __version__ = '.'.join(str(v) for v in version_info)
__all__ = ['__version__', 'version_info', 'add_measurement', 'flush', __all__ = ['__version__', 'version_info', 'add_measurement', 'flush',
@ -100,15 +106,46 @@ class InfluxDBMixin(object):
handler = '{}.{}'.format(self.__module__, self.__class__.__name__) handler = '{}.{}'.format(self.__module__, self.__class__.__name__)
self.influxdb.set_tags({'handler': handler, self.influxdb.set_tags({'handler': handler,
'method': request.method}) 'method': request.method})
for host, handlers in application.handlers:
if not host.match(request.host): pattern = None
continue if hasattr(application, 'handlers'):
pattern = self._get_path_pattern_tornado4()
else:
pattern = self._get_path_pattern_tornado45()
if pattern:
endpoint = pattern.rstrip('$')
else:
LOGGER.warning('Unable to determine routing pattern')
endpoint = request.path
self.influxdb.set_tags({'endpoint': endpoint})
def _get_path_pattern_tornado4(self):
"""Return the path pattern used when routing a request. (Tornado<4.5)
:rtype: str
"""
for host, handlers in self.application.handlers:
if host.match(self.request.host):
for handler in handlers: for handler in handlers:
match = handler.regex.match(request.path) if handler.regex.match(self.request.path):
if match: return handler.regex.pattern
self.influxdb.set_tag(
'endpoint', handler.regex.pattern.rstrip('$')) def _get_path_pattern_tornado45(self, router=None):
break """Return the path pattern used when routing a request. (Tornado>=4.5)
:param tornado.routing.Router router: (Optional) The router to scan.
Defaults to the application's router.
:rtype: str
"""
if router is None:
router = self.application.default_router
for rule in router.rules:
if rule.matcher.match(self.request) is not None:
if isinstance(rule.matcher, routing.PathMatches):
return rule.matcher.regex.pattern
elif isinstance(rule.target, routing.Router):
return self._get_path_pattern_tornado45(rule.target)
def on_finish(self): def on_finish(self):
if _enabled: if _enabled:

View file

@ -1,5 +1,9 @@
import mock
import socket import socket
import time import time
import unittest
import tornado
from . import base from . import base
@ -66,3 +70,65 @@ class MeasurementTestCase(base.AsyncServerTestCase):
self.assertEqual(measurement.tags['method'], 'GET') self.assertEqual(measurement.tags['method'], 'GET')
self.assertEqual(measurement.tags['endpoint'], '/param/(?P<id>\d+)') self.assertEqual(measurement.tags['endpoint'], '/param/(?P<id>\d+)')
self.assertEqual(measurement.fields['content_length'], 13) self.assertEqual(measurement.fields['content_length'], 13)
def test_measurement_with_specific_host(self):
self.application.add_handlers(
'some_host', [('/host/(?P<id>\d+)', base.ParamRequestHandler)])
result = self.fetch('/host/100', headers={'Host': 'some_host'})
self.assertEqual(result.code, 200)
measurement = self.get_measurement()
self.assertIsNotNone(measurement)
self.assertEqual(measurement.db, 'database-name')
self.assertEqual(measurement.name, 'my-service')
self.assertEqual(measurement.tags['status_code'], '200')
self.assertEqual(measurement.tags['method'], 'GET')
self.assertEqual(measurement.tags['endpoint'], '/host/(?P<id>\d+)')
self.assertEqual(measurement.fields['content_length'], 13)
@unittest.skipIf(tornado.version_info >= (4, 5),
'legacy routing removed in 4.5')
@mock.patch(
'sprockets_influxdb.InfluxDBMixin._get_path_pattern_tornado45')
@mock.patch(
'sprockets_influxdb.InfluxDBMixin._get_path_pattern_tornado4')
def test_mesurement_with_ambiguous_route_4(self, mock_4, mock_45):
mock_4.return_value = None
mock_45.return_value = None
result = self.fetch('/param/100')
self.assertEqual(result.code, 200)
measurement = self.get_measurement()
self.assertIsNotNone(measurement)
self.assertEqual(measurement.db, 'database-name')
self.assertEqual(measurement.name, 'my-service')
self.assertEqual(measurement.tags['status_code'], '200')
self.assertEqual(measurement.tags['method'], 'GET')
self.assertEqual(measurement.tags['endpoint'], '/param/100')
self.assertEqual(measurement.fields['content_length'], 13)
self.assertEqual(1, mock_4.call_count)
self.assertEqual(0, mock_45.call_count)
@unittest.skipIf(tornado.version_info < (4, 5),
'routing module introduced in tornado 4.5')
@mock.patch(
'sprockets_influxdb.InfluxDBMixin._get_path_pattern_tornado45')
@mock.patch(
'sprockets_influxdb.InfluxDBMixin._get_path_pattern_tornado4')
def test_mesurement_with_ambiguous_route_45(self, mock_4, mock_45):
mock_4.return_value = None
mock_45.return_value = None
result = self.fetch('/param/100')
self.assertEqual(result.code, 200)
measurement = self.get_measurement()
self.assertIsNotNone(measurement)
self.assertEqual(measurement.db, 'database-name')
self.assertEqual(measurement.name, 'my-service')
self.assertEqual(measurement.tags['status_code'], '200')
self.assertEqual(measurement.tags['method'], 'GET')
self.assertEqual(measurement.tags['endpoint'], '/param/100')
self.assertEqual(measurement.fields['content_length'], 13)
self.assertEqual(0, mock_4.call_count)
self.assertEqual(1, mock_45.call_count)