Skip to content

Commit 8bd71c8

Browse files
authored
Flask: Path Templates Support (#212)
* Flask: Path Templates Support * Py 2.7 unicode string
1 parent ee13429 commit 8bd71c8

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

instana/instrumentation/flask/vanilla.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import
22

3+
import re
34
import flask
45

56
import opentracing
@@ -10,6 +11,8 @@
1011
from ...singletons import agent, tracer
1112
from ...util import strip_secrets
1213

14+
path_tpl_re = re.compile('<.*>')
15+
1316

1417
def before_request_with_instana(*argv, **kwargs):
1518
try:
@@ -37,6 +40,12 @@ def before_request_with_instana(*argv, **kwargs):
3740
span.set_tag("http.params", scrubbed_params)
3841
if 'HTTP_HOST' in env:
3942
span.set_tag("http.host", env['HTTP_HOST'])
43+
44+
if hasattr(flask.request.url_rule, 'rule') and \
45+
path_tpl_re.search(flask.request.url_rule.rule) is not None:
46+
path_tpl = flask.request.url_rule.rule.replace("<", "{")
47+
path_tpl = path_tpl.replace(">", "}")
48+
span.set_tag("http.path_tpl", path_tpl)
4049
except:
4150
logger.debug("Flask before_request", exc_info=True)
4251
finally:

instana/instrumentation/flask/with_blinker.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import
22

3+
import re
34
import opentracing
45
import opentracing.ext.tags as ext
56
import wrapt
@@ -11,6 +12,8 @@
1112
import flask
1213
from flask import request_started, request_finished, got_request_exception
1314

15+
path_tpl_re = re.compile('<.*>')
16+
1417

1518
def request_started_with_instana(sender, **extra):
1619
try:
@@ -38,6 +41,12 @@ def request_started_with_instana(sender, **extra):
3841
span.set_tag("http.params", scrubbed_params)
3942
if 'HTTP_HOST' in env:
4043
span.set_tag("http.host", env['HTTP_HOST'])
44+
45+
if hasattr(flask.request.url_rule, 'rule') and \
46+
path_tpl_re.search(flask.request.url_rule.rule) is not None:
47+
path_tpl = flask.request.url_rule.rule.replace("<", "{")
48+
path_tpl = path_tpl.replace(">", "}")
49+
span.set_tag("http.path_tpl", path_tpl)
4150
except:
4251
logger.debug("Flask before_request", exc_info=True)
4352

tests/apps/flaskalino.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ def hello():
4444
return "<center><h1>🐍 Hello Stan! 🦄</h1></center>"
4545

4646

47+
@app.route("/users/<username>/sayhello")
48+
def username_hello(username):
49+
return u"<center><h1>🐍 Hello %s! 🦄</h1></center>" % username
50+
51+
4752
@app.route("/complex")
4853
def gen_opentracing():
4954
with tracer.start_active_span('asteroid') as pscope:

tests/test_flask.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def test_get_request(self):
9494
self.assertTrue(type(urllib3_span.stack) is list)
9595
self.assertTrue(len(urllib3_span.stack) > 1)
9696

97+
# We should NOT have a path template for this route
98+
self.assertIsNone(wsgi_span.data.http.path_tpl)
99+
97100
def test_render_template(self):
98101
with tracer.start_active_span('test'):
99102
response = self.http.request('GET', testenv["wsgi_server"] + '/render')
@@ -174,6 +177,9 @@ def test_render_template(self):
174177
self.assertTrue(type(urllib3_span.stack) is list)
175178
self.assertTrue(len(urllib3_span.stack) > 1)
176179

180+
# We should NOT have a path template for this route
181+
self.assertIsNone(wsgi_span.data.http.path_tpl)
182+
177183
def test_render_template_string(self):
178184
with tracer.start_active_span('test'):
179185
response = self.http.request('GET', testenv["wsgi_server"] + '/render_string')
@@ -254,6 +260,9 @@ def test_render_template_string(self):
254260
self.assertTrue(type(urllib3_span.stack) is list)
255261
self.assertTrue(len(urllib3_span.stack) > 1)
256262

263+
# We should NOT have a path template for this route
264+
self.assertIsNone(wsgi_span.data.http.path_tpl)
265+
257266
def test_301(self):
258267
with tracer.start_active_span('test'):
259268
response = self.http.request('GET', testenv["wsgi_server"] + '/301', redirect=False)
@@ -322,6 +331,9 @@ def test_301(self):
322331
self.assertTrue(type(urllib3_span.stack) is list)
323332
self.assertTrue(len(urllib3_span.stack) > 1)
324333

334+
# We should NOT have a path template for this route
335+
self.assertIsNone(wsgi_span.data.http.path_tpl)
336+
325337
def test_404(self):
326338
with tracer.start_active_span('test'):
327339
response = self.http.request('GET', testenv["wsgi_server"] + '/11111111111')
@@ -390,6 +402,9 @@ def test_404(self):
390402
self.assertTrue(type(urllib3_span.stack) is list)
391403
self.assertTrue(len(urllib3_span.stack) > 1)
392404

405+
# We should NOT have a path template for this route
406+
self.assertIsNone(wsgi_span.data.http.path_tpl)
407+
393408
def test_500(self):
394409
with tracer.start_active_span('test'):
395410
response = self.http.request('GET', testenv["wsgi_server"] + '/500')
@@ -458,6 +473,9 @@ def test_500(self):
458473
self.assertTrue(type(urllib3_span.stack) is list)
459474
self.assertTrue(len(urllib3_span.stack) > 1)
460475

476+
# We should NOT have a path template for this route
477+
self.assertIsNone(wsgi_span.data.http.path_tpl)
478+
461479
def test_render_error(self):
462480
if signals_available is True:
463481
raise unittest.SkipTest("Exceptions without handlers vary with blinker")
@@ -535,6 +553,9 @@ def test_render_error(self):
535553
self.assertTrue(type(urllib3_span.stack) is list)
536554
self.assertTrue(len(urllib3_span.stack) > 1)
537555

556+
# We should NOT have a path template for this route
557+
self.assertIsNone(wsgi_span.data.http.path_tpl)
558+
538559
def test_exception(self):
539560
if signals_available is True:
540561
raise unittest.SkipTest("Exceptions without handlers vary with blinker")
@@ -604,6 +625,9 @@ def test_exception(self):
604625
self.assertTrue(type(urllib3_span.stack) is list)
605626
self.assertTrue(len(urllib3_span.stack) > 1)
606627

628+
# We should NOT have a path template for this route
629+
self.assertIsNone(wsgi_span.data.http.path_tpl)
630+
607631
def test_custom_exception_with_log(self):
608632
with tracer.start_active_span('test'):
609633
response = self.http.request('GET', testenv["wsgi_server"] + '/exception-invalid-usage')
@@ -679,3 +703,77 @@ def test_custom_exception_with_log(self):
679703
self.assertIsNotNone(urllib3_span.stack)
680704
self.assertTrue(type(urllib3_span.stack) is list)
681705
self.assertTrue(len(urllib3_span.stack) > 1)
706+
707+
# We should NOT have a path template for this route
708+
self.assertIsNone(wsgi_span.data.http.path_tpl)
709+
710+
def test_path_templates(self):
711+
with tracer.start_active_span('test'):
712+
response = self.http.request('GET', testenv["wsgi_server"] + '/users/Ricky/sayhello')
713+
714+
spans = self.recorder.queued_spans()
715+
self.assertEqual(3, len(spans))
716+
717+
wsgi_span = spans[0]
718+
urllib3_span = spans[1]
719+
test_span = spans[2]
720+
721+
assert response
722+
self.assertEqual(200, response.status)
723+
724+
assert('X-Instana-T' in response.headers)
725+
assert(int(response.headers['X-Instana-T'], 16))
726+
self.assertEqual(response.headers['X-Instana-T'], wsgi_span.t)
727+
728+
assert('X-Instana-S' in response.headers)
729+
assert(int(response.headers['X-Instana-S'], 16))
730+
self.assertEqual(response.headers['X-Instana-S'], wsgi_span.s)
731+
732+
assert('X-Instana-L' in response.headers)
733+
self.assertEqual(response.headers['X-Instana-L'], '1')
734+
735+
assert('Server-Timing' in response.headers)
736+
server_timing_value = "intid;desc=%s" % wsgi_span.t
737+
self.assertEqual(response.headers['Server-Timing'], server_timing_value)
738+
739+
self.assertIsNone(tracer.active_span)
740+
741+
# Same traceId
742+
self.assertEqual(test_span.t, urllib3_span.t)
743+
self.assertEqual(urllib3_span.t, wsgi_span.t)
744+
745+
# Parent relationships
746+
self.assertEqual(urllib3_span.p, test_span.s)
747+
self.assertEqual(wsgi_span.p, urllib3_span.s)
748+
749+
# Error logging
750+
self.assertFalse(test_span.error)
751+
self.assertIsNone(test_span.ec)
752+
self.assertFalse(urllib3_span.error)
753+
self.assertIsNone(urllib3_span.ec)
754+
self.assertFalse(wsgi_span.error)
755+
self.assertIsNone(wsgi_span.ec)
756+
757+
# wsgi
758+
self.assertEqual("wsgi", wsgi_span.n)
759+
self.assertEqual('127.0.0.1:' + str(testenv['wsgi_port']), wsgi_span.data.http.host)
760+
self.assertEqual('/users/Ricky/sayhello', wsgi_span.data.http.url)
761+
self.assertEqual('GET', wsgi_span.data.http.method)
762+
self.assertEqual(200, wsgi_span.data.http.status)
763+
self.assertIsNone(wsgi_span.data.http.error)
764+
self.assertIsNotNone(wsgi_span.stack)
765+
self.assertEqual(2, len(wsgi_span.stack))
766+
767+
# urllib3
768+
self.assertEqual("test", test_span.data.sdk.name)
769+
self.assertEqual("urllib3", urllib3_span.n)
770+
self.assertEqual(200, urllib3_span.data.http.status)
771+
self.assertEqual(testenv["wsgi_server"] + '/users/Ricky/sayhello', urllib3_span.data.http.url)
772+
self.assertEqual("GET", urllib3_span.data.http.method)
773+
self.assertIsNotNone(urllib3_span.stack)
774+
self.assertTrue(type(urllib3_span.stack) is list)
775+
self.assertTrue(len(urllib3_span.stack) > 1)
776+
777+
# We should have a reported path template for this route
778+
self.assertEqual("/users/{username}/sayhello", wsgi_span.data.http.path_tpl)
779+

0 commit comments

Comments
 (0)