diff --git a/instana/instrumentation/flask/vanilla.py b/instana/instrumentation/flask/vanilla.py
index 6a2dee5c..aeb5d3df 100644
--- a/instana/instrumentation/flask/vanilla.py
+++ b/instana/instrumentation/flask/vanilla.py
@@ -1,5 +1,6 @@
from __future__ import absolute_import
+import re
import flask
import opentracing
@@ -10,6 +11,8 @@
from ...singletons import agent, tracer
from ...util import strip_secrets
+path_tpl_re = re.compile('<.*>')
+
def before_request_with_instana(*argv, **kwargs):
try:
@@ -37,6 +40,12 @@ def before_request_with_instana(*argv, **kwargs):
span.set_tag("http.params", scrubbed_params)
if 'HTTP_HOST' in env:
span.set_tag("http.host", env['HTTP_HOST'])
+
+ if hasattr(flask.request.url_rule, 'rule') and \
+ path_tpl_re.search(flask.request.url_rule.rule) is not None:
+ path_tpl = flask.request.url_rule.rule.replace("<", "{")
+ path_tpl = path_tpl.replace(">", "}")
+ span.set_tag("http.path_tpl", path_tpl)
except:
logger.debug("Flask before_request", exc_info=True)
finally:
diff --git a/instana/instrumentation/flask/with_blinker.py b/instana/instrumentation/flask/with_blinker.py
index 5d3e68a3..b9f0a906 100644
--- a/instana/instrumentation/flask/with_blinker.py
+++ b/instana/instrumentation/flask/with_blinker.py
@@ -1,5 +1,6 @@
from __future__ import absolute_import
+import re
import opentracing
import opentracing.ext.tags as ext
import wrapt
@@ -11,6 +12,8 @@
import flask
from flask import request_started, request_finished, got_request_exception
+path_tpl_re = re.compile('<.*>')
+
def request_started_with_instana(sender, **extra):
try:
@@ -38,6 +41,12 @@ def request_started_with_instana(sender, **extra):
span.set_tag("http.params", scrubbed_params)
if 'HTTP_HOST' in env:
span.set_tag("http.host", env['HTTP_HOST'])
+
+ if hasattr(flask.request.url_rule, 'rule') and \
+ path_tpl_re.search(flask.request.url_rule.rule) is not None:
+ path_tpl = flask.request.url_rule.rule.replace("<", "{")
+ path_tpl = path_tpl.replace(">", "}")
+ span.set_tag("http.path_tpl", path_tpl)
except:
logger.debug("Flask before_request", exc_info=True)
diff --git a/tests/apps/flaskalino.py b/tests/apps/flaskalino.py
index f883cbe7..0669ef8a 100644
--- a/tests/apps/flaskalino.py
+++ b/tests/apps/flaskalino.py
@@ -44,6 +44,11 @@ def hello():
return "
🐍 Hello Stan! 🦄
"
+@app.route("/users//sayhello")
+def username_hello(username):
+ return u"🐍 Hello %s! 🦄
" % username
+
+
@app.route("/complex")
def gen_opentracing():
with tracer.start_active_span('asteroid') as pscope:
diff --git a/tests/test_flask.py b/tests/test_flask.py
index 98a61bcf..66bdd932 100644
--- a/tests/test_flask.py
+++ b/tests/test_flask.py
@@ -94,6 +94,9 @@ def test_get_request(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_render_template(self):
with tracer.start_active_span('test'):
response = self.http.request('GET', testenv["wsgi_server"] + '/render')
@@ -174,6 +177,9 @@ def test_render_template(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_render_template_string(self):
with tracer.start_active_span('test'):
response = self.http.request('GET', testenv["wsgi_server"] + '/render_string')
@@ -254,6 +260,9 @@ def test_render_template_string(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_301(self):
with tracer.start_active_span('test'):
response = self.http.request('GET', testenv["wsgi_server"] + '/301', redirect=False)
@@ -322,6 +331,9 @@ def test_301(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_404(self):
with tracer.start_active_span('test'):
response = self.http.request('GET', testenv["wsgi_server"] + '/11111111111')
@@ -390,6 +402,9 @@ def test_404(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_500(self):
with tracer.start_active_span('test'):
response = self.http.request('GET', testenv["wsgi_server"] + '/500')
@@ -458,6 +473,9 @@ def test_500(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_render_error(self):
if signals_available is True:
raise unittest.SkipTest("Exceptions without handlers vary with blinker")
@@ -535,6 +553,9 @@ def test_render_error(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_exception(self):
if signals_available is True:
raise unittest.SkipTest("Exceptions without handlers vary with blinker")
@@ -604,6 +625,9 @@ def test_exception(self):
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
def test_custom_exception_with_log(self):
with tracer.start_active_span('test'):
response = self.http.request('GET', testenv["wsgi_server"] + '/exception-invalid-usage')
@@ -679,3 +703,77 @@ def test_custom_exception_with_log(self):
self.assertIsNotNone(urllib3_span.stack)
self.assertTrue(type(urllib3_span.stack) is list)
self.assertTrue(len(urllib3_span.stack) > 1)
+
+ # We should NOT have a path template for this route
+ self.assertIsNone(wsgi_span.data.http.path_tpl)
+
+ def test_path_templates(self):
+ with tracer.start_active_span('test'):
+ response = self.http.request('GET', testenv["wsgi_server"] + '/users/Ricky/sayhello')
+
+ spans = self.recorder.queued_spans()
+ self.assertEqual(3, len(spans))
+
+ wsgi_span = spans[0]
+ urllib3_span = spans[1]
+ test_span = spans[2]
+
+ assert response
+ self.assertEqual(200, response.status)
+
+ assert('X-Instana-T' in response.headers)
+ assert(int(response.headers['X-Instana-T'], 16))
+ self.assertEqual(response.headers['X-Instana-T'], wsgi_span.t)
+
+ assert('X-Instana-S' in response.headers)
+ assert(int(response.headers['X-Instana-S'], 16))
+ self.assertEqual(response.headers['X-Instana-S'], wsgi_span.s)
+
+ assert('X-Instana-L' in response.headers)
+ self.assertEqual(response.headers['X-Instana-L'], '1')
+
+ assert('Server-Timing' in response.headers)
+ server_timing_value = "intid;desc=%s" % wsgi_span.t
+ self.assertEqual(response.headers['Server-Timing'], server_timing_value)
+
+ self.assertIsNone(tracer.active_span)
+
+ # Same traceId
+ self.assertEqual(test_span.t, urllib3_span.t)
+ self.assertEqual(urllib3_span.t, wsgi_span.t)
+
+ # Parent relationships
+ self.assertEqual(urllib3_span.p, test_span.s)
+ self.assertEqual(wsgi_span.p, urllib3_span.s)
+
+ # Error logging
+ self.assertFalse(test_span.error)
+ self.assertIsNone(test_span.ec)
+ self.assertFalse(urllib3_span.error)
+ self.assertIsNone(urllib3_span.ec)
+ self.assertFalse(wsgi_span.error)
+ self.assertIsNone(wsgi_span.ec)
+
+ # wsgi
+ self.assertEqual("wsgi", wsgi_span.n)
+ self.assertEqual('127.0.0.1:' + str(testenv['wsgi_port']), wsgi_span.data.http.host)
+ self.assertEqual('/users/Ricky/sayhello', wsgi_span.data.http.url)
+ self.assertEqual('GET', wsgi_span.data.http.method)
+ self.assertEqual(200, wsgi_span.data.http.status)
+ self.assertIsNone(wsgi_span.data.http.error)
+ self.assertIsNotNone(wsgi_span.stack)
+ self.assertEqual(2, len(wsgi_span.stack))
+
+ # urllib3
+ self.assertEqual("test", test_span.data.sdk.name)
+ self.assertEqual("urllib3", urllib3_span.n)
+ self.assertEqual(200, urllib3_span.data.http.status)
+ self.assertEqual(testenv["wsgi_server"] + '/users/Ricky/sayhello', urllib3_span.data.http.url)
+ self.assertEqual("GET", urllib3_span.data.http.method)
+ self.assertIsNotNone(urllib3_span.stack)
+ self.assertTrue(type(urllib3_span.stack) is list)
+ self.assertTrue(len(urllib3_span.stack) > 1)
+
+ # We should have a reported path template for this route
+ self.assertEqual("/users/{username}/sayhello", wsgi_span.data.http.path_tpl)
+