Skip to content

Commit f0d4786

Browse files
committed
tornado_server: capture responseHeadersOnEntrySpans
Signed-off-by: Varsha GS <[email protected]>
1 parent ca4e60e commit f0d4786

File tree

4 files changed

+165
-72
lines changed

4 files changed

+165
-72
lines changed

instana/instrumentation/tornado/server.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323

2424
setup_tornado_tracer()
2525

26+
def extract_custom_headers(span, headers):
27+
if not agent.options.extra_http_headers or not headers:
28+
return
29+
try:
30+
for custom_header in agent.options.extra_http_headers:
31+
if custom_header in headers:
32+
span.set_tag("http.header.%s" % custom_header, headers[custom_header])
33+
34+
except Exception:
35+
logger.debug("extract_custom_headers: ", exc_info=True)
36+
37+
2638
@wrapt.patch_function_wrapper('tornado.web', 'RequestHandler._execute')
2739
def execute_with_instana(wrapped, instance, argv, kwargs):
2840
try:
@@ -45,12 +57,8 @@ def execute_with_instana(wrapped, instance, argv, kwargs):
4557

4658
scope.span.set_tag("handler", instance.__class__.__name__)
4759

48-
# Custom header tracking support
49-
if agent.options.extra_http_headers is not None:
50-
for custom_header in agent.options.extra_http_headers:
51-
if custom_header in instance.request.headers:
52-
scope.span.set_tag("http.header.%s" % custom_header,
53-
instance.request.headers[custom_header])
60+
# Request header tracking support
61+
extract_custom_headers(scope.span, instance.request.headers)
5462

5563
setattr(instance.request, "_instana", scope)
5664

@@ -80,15 +88,17 @@ def on_finish_with_instana(wrapped, instance, argv, kwargs):
8088
if not hasattr(instance.request, '_instana'):
8189
return wrapped(*argv, **kwargs)
8290

83-
scope = instance.request._instana
84-
status_code = instance.get_status()
91+
with instance.request._instana as scope:
92+
# Response header tracking support
93+
extract_custom_headers(scope.span, instance._headers)
94+
95+
status_code = instance.get_status()
8596

86-
# Mark 500 responses as errored
87-
if 500 <= status_code:
88-
scope.span.mark_as_errored()
97+
# Mark 500 responses as errored
98+
if 500 <= status_code:
99+
scope.span.mark_as_errored()
89100

90-
scope.span.set_tag("http.status_code", status_code)
91-
scope.close()
101+
scope.span.set_tag("http.status_code", status_code)
92102

93103
return wrapped(*argv, **kwargs)
94104
except Exception:

tests/apps/tornado_server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# (c) Copyright Instana Inc. 2020
33

44
import os
5-
import sys
5+
66
from ...helpers import testenv
77
from ..utils import launch_background_thread
88

tests/apps/tornado_server/app.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(self):
2525
(r"/405", R405Handler),
2626
(r"/500", R500Handler),
2727
(r"/504", R504Handler),
28+
(r"/response_headers", ResponseHeadersHandler),
2829
]
2930
settings = dict(
3031
cookie_secret="7FpA2}3dgri2GEDr",
@@ -67,6 +68,17 @@ def get(self):
6768
raise tornado.web.HTTPError(status_code=504, log_message="Simulated Internal Server Errors")
6869

6970

71+
class ResponseHeadersHandler(tornado.web.RequestHandler):
72+
def get(self):
73+
headers = {
74+
'X-Capture-This-Too': 'this too',
75+
'X-Capture-That-Too': 'that too'
76+
}
77+
for key, value in headers.items():
78+
self.set_header(key, value)
79+
self.write("Stan wuz here with headers!")
80+
81+
7082
def run_server():
7183
loop = asyncio.new_event_loop()
7284
asyncio.set_event_loop(loop)

tests/frameworks/test_tornado_server.py

Lines changed: 129 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,16 @@ async def test():
9494
self.assertEqual(testenv["tornado_server"] + "/", aiohttp_span.data["http"]["url"])
9595
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
9696
self.assertIsNotNone(aiohttp_span.stack)
97-
self.assertTrue(type(aiohttp_span.stack) is list)
98-
self.assertTrue(len(aiohttp_span.stack) > 1)
97+
self.assertIsInstance(aiohttp_span.stack, list)
98+
self.assertGreater(len(aiohttp_span.stack), 1)
9999

100-
self.assertTrue("X-INSTANA-T" in response.headers)
100+
self.assertIn("X-INSTANA-T", response.headers)
101101
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
102-
self.assertTrue("X-INSTANA-S" in response.headers)
102+
self.assertIn("X-INSTANA-S", response.headers)
103103
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
104-
self.assertTrue("X-INSTANA-L" in response.headers)
104+
self.assertIn("X-INSTANA-L", response.headers)
105105
self.assertEqual(response.headers["X-INSTANA-L"], '1')
106-
self.assertTrue("Server-Timing" in response.headers)
106+
self.assertIn("Server-Timing", response.headers)
107107
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
108108

109109
def test_post(self):
@@ -155,16 +155,16 @@ async def test():
155155
self.assertEqual(testenv["tornado_server"] + "/", aiohttp_span.data["http"]["url"])
156156
self.assertEqual("POST", aiohttp_span.data["http"]["method"])
157157
self.assertIsNotNone(aiohttp_span.stack)
158-
self.assertTrue(type(aiohttp_span.stack) is list)
159-
self.assertTrue(len(aiohttp_span.stack) > 1)
158+
self.assertIsInstance(aiohttp_span.stack, list)
159+
self.assertGreater(len(aiohttp_span.stack), 1)
160160

161-
self.assertTrue("X-INSTANA-T" in response.headers)
161+
self.assertIn("X-INSTANA-T", response.headers)
162162
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
163-
self.assertTrue("X-INSTANA-S" in response.headers)
163+
self.assertIn("X-INSTANA-S", response.headers)
164164
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
165-
self.assertTrue("X-INSTANA-L" in response.headers)
165+
self.assertIn("X-INSTANA-L", response.headers)
166166
self.assertEqual(response.headers["X-INSTANA-L"], '1')
167-
self.assertTrue("Server-Timing" in response.headers)
167+
self.assertIn("Server-Timing", response.headers)
168168
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
169169

170170
def test_synthetic_request(self):
@@ -177,7 +177,7 @@ async def test():
177177
async with aiohttp.ClientSession() as session:
178178
return await self.fetch(session, testenv["tornado_server"] + "/", headers=headers)
179179

180-
response = tornado.ioloop.IOLoop.current().run_sync(test)
180+
tornado.ioloop.IOLoop.current().run_sync(test)
181181

182182
spans = self.recorder.queued_spans()
183183
self.assertEqual(3, len(spans))
@@ -252,16 +252,16 @@ async def test():
252252
self.assertEqual(testenv["tornado_server"] + "/301", aiohttp_span.data["http"]["url"])
253253
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
254254
self.assertIsNotNone(aiohttp_span.stack)
255-
self.assertTrue(type(aiohttp_span.stack) is list)
256-
self.assertTrue(len(aiohttp_span.stack) > 1)
255+
self.assertIsInstance(aiohttp_span.stack, list)
256+
self.assertGreater(len(aiohttp_span.stack), 1)
257257

258-
self.assertTrue("X-INSTANA-T" in response.headers)
258+
self.assertIn("X-INSTANA-T", response.headers)
259259
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
260-
self.assertTrue("X-INSTANA-S" in response.headers)
260+
self.assertIn("X-INSTANA-S", response.headers)
261261
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
262-
self.assertTrue("X-INSTANA-L" in response.headers)
262+
self.assertIn("X-INSTANA-L", response.headers)
263263
self.assertEqual(response.headers["X-INSTANA-L"], '1')
264-
self.assertTrue("Server-Timing" in response.headers)
264+
self.assertIn("Server-Timing", response.headers)
265265
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
266266

267267
def test_get_405(self):
@@ -313,16 +313,16 @@ async def test():
313313
self.assertEqual(testenv["tornado_server"] + "/405", aiohttp_span.data["http"]["url"])
314314
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
315315
self.assertIsNotNone(aiohttp_span.stack)
316-
self.assertTrue(type(aiohttp_span.stack) is list)
317-
self.assertTrue(len(aiohttp_span.stack) > 1)
316+
self.assertIsInstance(aiohttp_span.stack, list)
317+
self.assertGreater(len(aiohttp_span.stack), 1)
318318

319-
self.assertTrue("X-INSTANA-T" in response.headers)
319+
self.assertIn("X-INSTANA-T", response.headers)
320320
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
321-
self.assertTrue("X-INSTANA-S" in response.headers)
321+
self.assertIn("X-INSTANA-S", response.headers)
322322
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
323-
self.assertTrue("X-INSTANA-L" in response.headers)
323+
self.assertIn("X-INSTANA-L", response.headers)
324324
self.assertEqual(response.headers["X-INSTANA-L"], '1')
325-
self.assertTrue("Server-Timing" in response.headers)
325+
self.assertIn("Server-Timing", response.headers)
326326
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
327327

328328
def test_get_500(self):
@@ -375,16 +375,16 @@ async def test():
375375
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
376376
self.assertEqual('Internal Server Error', aiohttp_span.data["http"]["error"])
377377
self.assertIsNotNone(aiohttp_span.stack)
378-
self.assertTrue(type(aiohttp_span.stack) is list)
379-
self.assertTrue(len(aiohttp_span.stack) > 1)
378+
self.assertIsInstance(aiohttp_span.stack, list)
379+
self.assertGreater(len(aiohttp_span.stack), 1)
380380

381-
self.assertTrue("X-INSTANA-T" in response.headers)
381+
self.assertIn("X-INSTANA-T", response.headers)
382382
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
383-
self.assertTrue("X-INSTANA-S" in response.headers)
383+
self.assertIn("X-INSTANA-S", response.headers)
384384
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
385-
self.assertTrue("X-INSTANA-L" in response.headers)
385+
self.assertIn("X-INSTANA-L", response.headers)
386386
self.assertEqual(response.headers["X-INSTANA-L"], '1')
387-
self.assertTrue("Server-Timing" in response.headers)
387+
self.assertIn("Server-Timing", response.headers)
388388
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
389389

390390
def test_get_504(self):
@@ -437,16 +437,16 @@ async def test():
437437
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
438438
self.assertEqual('Gateway Timeout', aiohttp_span.data["http"]["error"])
439439
self.assertIsNotNone(aiohttp_span.stack)
440-
self.assertTrue(type(aiohttp_span.stack) is list)
441-
self.assertTrue(len(aiohttp_span.stack) > 1)
440+
self.assertIsInstance(aiohttp_span.stack, list)
441+
self.assertGreater(len(aiohttp_span.stack), 1)
442442

443-
self.assertTrue("X-INSTANA-T" in response.headers)
443+
self.assertIn("X-INSTANA-T", response.headers)
444444
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
445-
self.assertTrue("X-INSTANA-S" in response.headers)
445+
self.assertIn("X-INSTANA-S", response.headers)
446446
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
447-
self.assertTrue("X-INSTANA-L" in response.headers)
447+
self.assertIn("X-INSTANA-L", response.headers)
448448
self.assertEqual(response.headers["X-INSTANA-L"], '1')
449-
self.assertTrue("Server-Timing" in response.headers)
449+
self.assertIn("Server-Timing", response.headers)
450450
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
451451

452452
def test_get_with_params_to_scrub(self):
@@ -499,30 +499,31 @@ async def test():
499499
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
500500
self.assertEqual("secret=<redacted>", aiohttp_span.data["http"]["params"])
501501
self.assertIsNotNone(aiohttp_span.stack)
502-
self.assertTrue(type(aiohttp_span.stack) is list)
503-
self.assertTrue(len(aiohttp_span.stack) > 1)
502+
self.assertIsInstance(aiohttp_span.stack, list)
503+
self.assertGreater(len(aiohttp_span.stack), 1)
504504

505-
self.assertTrue("X-INSTANA-T" in response.headers)
505+
self.assertIn("X-INSTANA-T", response.headers)
506506
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
507-
self.assertTrue("X-INSTANA-S" in response.headers)
507+
self.assertIn("X-INSTANA-S", response.headers)
508508
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
509-
self.assertTrue("X-INSTANA-L" in response.headers)
509+
self.assertIn("X-INSTANA-L", response.headers)
510510
self.assertEqual(response.headers["X-INSTANA-L"], '1')
511-
self.assertTrue("Server-Timing" in response.headers)
511+
self.assertIn("Server-Timing", response.headers)
512512
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
513513

514-
def test_custom_header_capture(self):
514+
def test_request_header_capture(self):
515515
async def test():
516516
with async_tracer.start_active_span('test'):
517517
async with aiohttp.ClientSession() as session:
518-
# Hack together a manual custom headers list
519-
agent.options.extra_http_headers = [u'X-Capture-This', u'X-Capture-That']
518+
# Hack together a manual custom request headers list
519+
agent.options.extra_http_headers = ["X-Capture-This", "X-Capture-That"]
520520

521-
headers = dict()
522-
headers['X-Capture-This'] = 'this'
523-
headers['X-Capture-That'] = 'that'
521+
request_headers = {
522+
"X-Capture-This": "this",
523+
"X-Capture-That": "that"
524+
}
524525

525-
return await self.fetch(session, testenv["tornado_server"], headers=headers, params={"secret": "iloveyou"})
526+
return await self.fetch(session, testenv["tornado_server"], headers=request_headers, params={"secret": "iloveyou"})
526527

527528
response = tornado.ioloop.IOLoop.current().run_sync(test)
528529

@@ -568,19 +569,89 @@ async def test():
568569
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
569570
self.assertEqual("secret=<redacted>", aiohttp_span.data["http"]["params"])
570571
self.assertIsNotNone(aiohttp_span.stack)
571-
self.assertTrue(type(aiohttp_span.stack) is list)
572-
self.assertTrue(len(aiohttp_span.stack) > 1)
572+
self.assertIsInstance(aiohttp_span.stack, list)
573+
self.assertGreater(len(aiohttp_span.stack), 1)
573574

574-
self.assertTrue("X-INSTANA-T" in response.headers)
575+
self.assertIn("X-INSTANA-T", response.headers)
575576
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
576-
self.assertTrue("X-INSTANA-S" in response.headers)
577+
self.assertIn("X-INSTANA-S", response.headers)
577578
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
578-
self.assertTrue("X-INSTANA-L" in response.headers)
579+
self.assertIn("X-INSTANA-L", response.headers)
579580
self.assertEqual(response.headers["X-INSTANA-L"], '1')
580-
self.assertTrue("Server-Timing" in response.headers)
581+
self.assertIn("Server-Timing", response.headers)
581582
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
582583

583-
assert "X-Capture-This" in tornado_span.data["http"]["header"]
584+
self.assertIn("X-Capture-This", tornado_span.data["http"]["header"])
584585
self.assertEqual("this", tornado_span.data["http"]["header"]["X-Capture-This"])
585-
assert "X-Capture-That" in tornado_span.data["http"]["header"]
586+
self.assertIn("X-Capture-That", tornado_span.data["http"]["header"])
586587
self.assertEqual("that", tornado_span.data["http"]["header"]["X-Capture-That"])
588+
589+
def test_response_header_capture(self):
590+
async def test():
591+
with async_tracer.start_active_span('test'):
592+
async with aiohttp.ClientSession() as session:
593+
# Hack together a manual custom response headers list
594+
agent.options.extra_http_headers = ["X-Capture-This-Too", "X-Capture-That-Too"]
595+
596+
return await self.fetch(session, testenv["tornado_server"] + "/response_headers", params={"secret": "itsasecret"})
597+
598+
response = tornado.ioloop.IOLoop.current().run_sync(test)
599+
600+
spans = self.recorder.queued_spans()
601+
self.assertEqual(3, len(spans))
602+
603+
tornado_span = get_first_span_by_name(spans, "tornado-server")
604+
aiohttp_span = get_first_span_by_name(spans, "aiohttp-client")
605+
test_span = get_first_span_by_name(spans, "sdk")
606+
607+
self.assertIsNotNone(tornado_span)
608+
self.assertIsNotNone(aiohttp_span)
609+
self.assertIsNotNone(test_span)
610+
611+
self.assertIsNone(async_tracer.active_span)
612+
613+
self.assertEqual("tornado-server", tornado_span.n)
614+
self.assertEqual("aiohttp-client", aiohttp_span.n)
615+
self.assertEqual("sdk", test_span.n)
616+
617+
# Same traceId
618+
traceId = test_span.t
619+
self.assertEqual(traceId, aiohttp_span.t)
620+
self.assertEqual(traceId, tornado_span.t)
621+
622+
# Parent relationships
623+
self.assertEqual(aiohttp_span.p, test_span.s)
624+
self.assertEqual(tornado_span.p, aiohttp_span.s)
625+
626+
# Error logging
627+
self.assertIsNone(test_span.ec)
628+
self.assertIsNone(aiohttp_span.ec)
629+
self.assertIsNone(tornado_span.ec)
630+
631+
self.assertEqual(200, tornado_span.data["http"]["status"])
632+
self.assertEqual(testenv["tornado_server"] + "/response_headers", tornado_span.data["http"]["url"])
633+
self.assertEqual("secret=<redacted>", tornado_span.data["http"]["params"])
634+
self.assertEqual("GET", tornado_span.data["http"]["method"])
635+
self.assertIsNone(tornado_span.stack)
636+
637+
self.assertEqual(200, aiohttp_span.data["http"]["status"])
638+
self.assertEqual(testenv["tornado_server"] + "/response_headers", aiohttp_span.data["http"]["url"])
639+
self.assertEqual("GET", aiohttp_span.data["http"]["method"])
640+
self.assertEqual("secret=<redacted>", aiohttp_span.data["http"]["params"])
641+
self.assertIsNotNone(aiohttp_span.stack)
642+
self.assertIsInstance(aiohttp_span.stack, list)
643+
self.assertGreater(len(aiohttp_span.stack), 1)
644+
645+
self.assertIn("X-INSTANA-T", response.headers)
646+
self.assertEqual(response.headers["X-INSTANA-T"], traceId)
647+
self.assertIn("X-INSTANA-S", response.headers)
648+
self.assertEqual(response.headers["X-INSTANA-S"], tornado_span.s)
649+
self.assertIn("X-INSTANA-L", response.headers)
650+
self.assertEqual(response.headers["X-INSTANA-L"], '1')
651+
self.assertIn("Server-Timing", response.headers)
652+
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
653+
654+
self.assertIn("X-Capture-This-Too", tornado_span.data["http"]["header"])
655+
self.assertEqual("this too", tornado_span.data["http"]["header"]["X-Capture-This-Too"])
656+
self.assertIn("X-Capture-That-Too", tornado_span.data["http"]["header"])
657+
self.assertEqual("that too", tornado_span.data["http"]["header"]["X-Capture-That-Too"])

0 commit comments

Comments
 (0)