Skip to content

Commit f393063

Browse files
committed
Sanic: capture responseHeadersOnEntrySpans
Signed-off-by: Varsha GS <[email protected]>
1 parent a8aad4d commit f393063

File tree

5 files changed

+143
-82
lines changed

5 files changed

+143
-82
lines changed

instana/instrumentation/sanic_inst.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def response_details(span, response):
4444
span.set_tag('http.status_code', status_code)
4545

4646
if response.headers is not None:
47+
extract_custom_headers(span, response.headers)
4748
async_tracer.inject(span.context, opentracing.Format.HTTP_HEADERS, response.headers)
4849
response.headers['Server-Timing'] = "intid;desc=%s" % span.context.trace_id
4950
except Exception:
@@ -124,7 +125,7 @@ async def handle_request_with_instana(wrapped, instance, args, kwargs):
124125
scope.span.set_tag("http.params", scrubbed_params)
125126

126127
if agent.options.extra_http_headers is not None:
127-
extract_custom_headers(scope, headers)
128+
extract_custom_headers(scope.span, headers)
128129
await wrapped(*args, **kwargs)
129130
if hasattr(request, "uri_template") and request.uri_template:
130131
scope.span.set_tag("http.path_tpl", request.uri_template)

instana/util/traceutils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from ..log import logger
66

77

8-
def extract_custom_headers(tracing_scope, headers):
8+
def extract_custom_headers(tracing_span, headers):
99
try:
1010
for custom_header in agent.options.extra_http_headers:
1111
# Headers are in the following format: b'x-header-1'
1212
for header_key, value in headers.items():
1313
if header_key.lower() == custom_header.lower():
14-
tracing_scope.span.set_tag("http.header.%s" % custom_header, value)
14+
tracing_span.set_tag("http.header.%s" % custom_header, value)
1515
except Exception:
1616
logger.debug("extract_custom_headers: ", exc_info=True)
1717

tests/apps/sanic_app/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44

55
import uvicorn
6+
67
from ...helpers import testenv
78
from instana.log import logger
89

@@ -15,6 +16,16 @@ def launch_sanic():
1516
from instana.singletons import agent
1617

1718
# Hack together a manual custom headers list; We'll use this in tests
18-
agent.options.extra_http_headers = [u'X-Capture-This', u'X-Capture-That']
19+
agent.options.extra_http_headers = [
20+
"X-Capture-This",
21+
"X-Capture-That",
22+
"X-Capture-This-Too",
23+
"X-Capture-That-Too",
24+
]
1925

20-
uvicorn.run(app, host='127.0.0.1', port=testenv['sanic_port'], log_level="critical")
26+
uvicorn.run(
27+
app,
28+
host="127.0.0.1",
29+
port=testenv["sanic_port"],
30+
log_level="critical",
31+
)

tests/apps/sanic_app/server.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@
55

66
from sanic import Sanic
77
from sanic.exceptions import SanicException
8+
from sanic.response import text
9+
810
from tests.apps.sanic_app.simpleview import SimpleView
911
from tests.apps.sanic_app.name import NameView
10-
from sanic.response import text
1112

1213
app = Sanic('test')
1314

1415
@app.get("/foo/<foo_id:int>")
1516
async def uuid_handler(request, foo_id: int):
1617
return text("INT - {}".format(foo_id))
1718

19+
@app.route("/response_headers")
20+
async def response_headers(request):
21+
headers = {
22+
'X-Capture-This-Too': 'this too',
23+
'X-Capture-That-Too': 'that too'
24+
}
25+
return text("Stan wuz here with headers!", headers=headers)
1826

1927
@app.route("/test_request_args")
2028
async def test_request_args(request):

tests/frameworks/test_sanic.py

Lines changed: 117 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def test_vanilla_get(self):
3737
self.assertEqual(spans[0].n, 'asgi')
3838

3939
def test_basic_get(self):
40-
result = None
4140
with tracer.start_active_span('test'):
4241
result = requests.get(testenv["sanic_server"] + '/')
4342

@@ -71,16 +70,15 @@ def test_basic_get(self):
7170
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
7271

7372
self.assertIsNone(asgi_span.ec)
74-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
75-
assert (asgi_span.data['http']['path'] == '/')
76-
assert (asgi_span.data['http']['path_tpl'] == '/')
77-
assert (asgi_span.data['http']['method'] == 'GET')
78-
assert (asgi_span.data['http']['status'] == 200)
79-
assert (asgi_span.data['http']['error'] is None)
80-
assert (asgi_span.data['http']['params'] is None)
73+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
74+
self.assertEqual(asgi_span.data['http']['path'], '/')
75+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/')
76+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
77+
self.assertEqual(asgi_span.data['http']['status'], 200)
78+
self.assertIsNone(asgi_span.data['http']['error'])
79+
self.assertIsNone(asgi_span.data['http']['params'])
8180

8281
def test_404(self):
83-
result = None
8482
with tracer.start_active_span('test'):
8583
result = requests.get(testenv["sanic_server"] + '/foo/not_an_int')
8684

@@ -114,16 +112,15 @@ def test_404(self):
114112
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
115113

116114
self.assertIsNone(asgi_span.ec)
117-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
118-
assert (asgi_span.data['http']['path'] == '/foo/not_an_int')
119-
assert (asgi_span.data['http']['path_tpl'] is None)
120-
assert (asgi_span.data['http']['method'] == 'GET')
121-
assert (asgi_span.data['http']['status'] == 404)
122-
assert (asgi_span.data['http']['error'] is None)
123-
assert (asgi_span.data['http']['params'] is None)
115+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
116+
self.assertEqual(asgi_span.data['http']['path'], '/foo/not_an_int')
117+
self.assertIsNone(asgi_span.data['http']['path_tpl'])
118+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
119+
self.assertEqual(asgi_span.data['http']['status'], 404)
120+
self.assertIsNone(asgi_span.data['http']['error'])
121+
self.assertIsNone(asgi_span.data['http']['params'])
124122

125123
def test_sanic_exception(self):
126-
result = None
127124
with tracer.start_active_span('test'):
128125
result = requests.get(testenv["sanic_server"] + '/wrong')
129126

@@ -157,16 +154,15 @@ def test_sanic_exception(self):
157154
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
158155

159156
self.assertIsNone(asgi_span.ec)
160-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
161-
assert (asgi_span.data['http']['path'] == '/wrong')
162-
assert (asgi_span.data['http']['path_tpl'] == '/wrong')
163-
assert (asgi_span.data['http']['method'] == 'GET')
164-
assert (asgi_span.data['http']['status'] == 400)
165-
assert (asgi_span.data['http']['error'] is None)
166-
assert (asgi_span.data['http']['params'] is None)
157+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
158+
self.assertEqual(asgi_span.data['http']['path'], '/wrong')
159+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/wrong')
160+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
161+
self.assertEqual(asgi_span.data['http']['status'], 400)
162+
self.assertIsNone(asgi_span.data['http']['error'])
163+
self.assertIsNone(asgi_span.data['http']['params'])
167164

168165
def test_500_instana_exception(self):
169-
result = None
170166
with tracer.start_active_span('test'):
171167
result = requests.get(testenv["sanic_server"] + '/instana_exception')
172168

@@ -200,16 +196,15 @@ def test_500_instana_exception(self):
200196
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
201197

202198
self.assertEqual(asgi_span.ec, 1)
203-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
204-
assert (asgi_span.data['http']['path'] == '/instana_exception')
205-
assert (asgi_span.data['http']['path_tpl'] == '/instana_exception')
206-
assert (asgi_span.data['http']['method'] == 'GET')
207-
assert (asgi_span.data['http']['status'] == 500)
208-
assert (asgi_span.data['http']['error'] is None)
209-
assert (asgi_span.data['http']['params'] is None)
199+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
200+
self.assertEqual(asgi_span.data['http']['path'], '/instana_exception')
201+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/instana_exception')
202+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
203+
self.assertEqual(asgi_span.data['http']['status'], 500)
204+
self.assertIsNone(asgi_span.data['http']['error'])
205+
self.assertIsNone(asgi_span.data['http']['params'])
210206

211207
def test_500(self):
212-
result = None
213208
with tracer.start_active_span('test'):
214209
result = requests.get(testenv["sanic_server"] + '/test_request_args')
215210

@@ -243,16 +238,15 @@ def test_500(self):
243238
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
244239

245240
self.assertEqual(asgi_span.ec, 1)
246-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
247-
assert (asgi_span.data['http']['path'] == '/test_request_args')
248-
assert (asgi_span.data['http']['path_tpl'] == '/test_request_args')
249-
assert (asgi_span.data['http']['method'] == 'GET')
250-
assert (asgi_span.data['http']['status'] == 500)
251-
assert (asgi_span.data['http']['error'] == 'Something went wrong.')
252-
assert (asgi_span.data['http']['params'] is None)
241+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
242+
self.assertEqual(asgi_span.data['http']['path'], '/test_request_args')
243+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/test_request_args')
244+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
245+
self.assertEqual(asgi_span.data['http']['status'], 500)
246+
self.assertEqual(asgi_span.data['http']['error'], 'Something went wrong.')
247+
self.assertIsNone(asgi_span.data['http']['params'])
253248

254249
def test_path_templates(self):
255-
result = None
256250
with tracer.start_active_span('test'):
257251
result = requests.get(testenv["sanic_server"] + '/foo/1')
258252

@@ -286,16 +280,15 @@ def test_path_templates(self):
286280
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
287281

288282
self.assertIsNone(asgi_span.ec)
289-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
290-
assert (asgi_span.data['http']['path'] == '/foo/1')
291-
assert (asgi_span.data['http']['path_tpl'] == '/foo/<foo_id:int>')
292-
assert (asgi_span.data['http']['method'] == 'GET')
293-
assert (asgi_span.data['http']['status'] == 200)
294-
assert (asgi_span.data['http']['error'] is None)
295-
assert (asgi_span.data['http']['params'] is None)
283+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
284+
self.assertEqual(asgi_span.data['http']['path'], '/foo/1')
285+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/foo/<foo_id:int>')
286+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
287+
self.assertEqual(asgi_span.data['http']['status'], 200)
288+
self.assertIsNone(asgi_span.data['http']['error'])
289+
self.assertIsNone(asgi_span.data['http']['params'])
296290

297291
def test_secret_scrubbing(self):
298-
result = None
299292
with tracer.start_active_span('test'):
300293
result = requests.get(testenv["sanic_server"] + '/?secret=shhh')
301294

@@ -329,13 +322,13 @@ def test_secret_scrubbing(self):
329322
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
330323

331324
self.assertIsNone(asgi_span.ec)
332-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
333-
assert (asgi_span.data['http']['path'] == '/')
334-
assert (asgi_span.data['http']['path_tpl'] == '/')
335-
assert (asgi_span.data['http']['method'] == 'GET')
336-
assert (asgi_span.data['http']['status'] == 200)
337-
assert (asgi_span.data['http']['error'] is None)
338-
assert (asgi_span.data['http']['params'] == 'secret=<redacted>')
325+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
326+
self.assertEqual(asgi_span.data['http']['path'], '/')
327+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/')
328+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
329+
self.assertEqual(asgi_span.data['http']['status'], 200)
330+
self.assertIsNone(asgi_span.data['http']['error'])
331+
self.assertEqual(asgi_span.data['http']['params'], 'secret=<redacted>')
339332

340333
def test_synthetic_request(self):
341334
request_headers = {
@@ -374,19 +367,19 @@ def test_synthetic_request(self):
374367
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
375368

376369
self.assertIsNone(asgi_span.ec)
377-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
378-
assert (asgi_span.data['http']['path'] == '/')
379-
assert (asgi_span.data['http']['path_tpl'] == '/')
380-
assert (asgi_span.data['http']['method'] == 'GET')
381-
assert (asgi_span.data['http']['status'] == 200)
382-
assert (asgi_span.data['http']['error'] is None)
383-
assert (asgi_span.data['http']['params'] is None)
370+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
371+
self.assertEqual(asgi_span.data['http']['path'], '/')
372+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/')
373+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
374+
self.assertEqual(asgi_span.data['http']['status'], 200)
375+
self.assertIsNone(asgi_span.data['http']['error'])
376+
self.assertIsNone(asgi_span.data['http']['params'])
384377

385378
self.assertIsNotNone(asgi_span.sy)
386379
self.assertIsNone(urllib3_span.sy)
387380
self.assertIsNone(test_span.sy)
388381

389-
def test_custom_header_capture(self):
382+
def test_request_header_capture(self):
390383
request_headers = {
391384
'X-Capture-This': 'this',
392385
'X-Capture-That': 'that'
@@ -424,15 +417,63 @@ def test_custom_header_capture(self):
424417
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
425418

426419
self.assertIsNone(asgi_span.ec)
427-
assert (asgi_span.data['http']['host'] == '127.0.0.1:1337')
428-
assert (asgi_span.data['http']['path'] == '/')
429-
assert (asgi_span.data['http']['path_tpl'] == '/')
430-
assert (asgi_span.data['http']['method'] == 'GET')
431-
assert (asgi_span.data['http']['status'] == 200)
432-
assert (asgi_span.data['http']['error'] is None)
433-
assert (asgi_span.data['http']['params'] is None)
434-
435-
assert ("X-Capture-This" in asgi_span.data["http"]["header"])
436-
assert ("this" == asgi_span.data["http"]["header"]["X-Capture-This"])
437-
assert ("X-Capture-That" in asgi_span.data["http"]["header"])
438-
assert ("that" == asgi_span.data["http"]["header"]["X-Capture-That"])
420+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
421+
self.assertEqual(asgi_span.data['http']['path'], '/')
422+
self.assertEqual(asgi_span.data['http']['path_tpl'], '/')
423+
self.assertEqual(asgi_span.data['http']['method'], 'GET')
424+
self.assertEqual(asgi_span.data['http']['status'], 200)
425+
self.assertIsNone(asgi_span.data['http']['error'])
426+
self.assertIsNone(asgi_span.data['http']['params'])
427+
428+
self.assertIn("X-Capture-This", asgi_span.data["http"]["header"])
429+
self.assertEqual("this", asgi_span.data["http"]["header"]["X-Capture-This"])
430+
self.assertIn("X-Capture-That", asgi_span.data["http"]["header"])
431+
self.assertEqual("that", asgi_span.data["http"]["header"]["X-Capture-That"])
432+
433+
def test_response_header_capture(self):
434+
with tracer.start_active_span("test"):
435+
result = requests.get(testenv["sanic_server"] + "/response_headers")
436+
437+
self.assertEqual(result.status_code, 200)
438+
439+
spans = tracer.recorder.queued_spans()
440+
self.assertEqual(len(spans), 3)
441+
442+
span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test'
443+
test_span = get_first_span_by_filter(spans, span_filter)
444+
self.assertIsNotNone(test_span)
445+
446+
span_filter = lambda span: span.n == "urllib3"
447+
urllib3_span = get_first_span_by_filter(spans, span_filter)
448+
self.assertIsNotNone(urllib3_span)
449+
450+
span_filter = lambda span: span.n == 'asgi'
451+
asgi_span = get_first_span_by_filter(spans, span_filter)
452+
self.assertIsNotNone(asgi_span)
453+
454+
self.assertTraceContextPropagated(test_span, urllib3_span)
455+
self.assertTraceContextPropagated(urllib3_span, asgi_span)
456+
457+
self.assertIn("X-INSTANA-T", result.headers)
458+
self.assertEqual(result.headers["X-INSTANA-T"], asgi_span.t)
459+
self.assertIn("X-INSTANA-S", result.headers)
460+
self.assertEqual(result.headers["X-INSTANA-S"], asgi_span.s)
461+
self.assertIn("X-INSTANA-L", result.headers)
462+
self.assertEqual(result.headers["X-INSTANA-L"], '1')
463+
self.assertIn("Server-Timing", result.headers)
464+
self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t))
465+
466+
self.assertIsNone(asgi_span.ec)
467+
self.assertEqual(asgi_span.data['http']['host'], '127.0.0.1:1337')
468+
self.assertEqual(asgi_span.data["http"]["path"], "/response_headers")
469+
self.assertEqual(asgi_span.data["http"]["path_tpl"], "/response_headers")
470+
self.assertEqual(asgi_span.data["http"]["method"], "GET")
471+
self.assertEqual(asgi_span.data["http"]["status"], 200)
472+
473+
self.assertIsNone(asgi_span.data["http"]["error"])
474+
self.assertIsNone(asgi_span.data["http"]["params"])
475+
476+
self.assertIn("X-Capture-This-Too", asgi_span.data["http"]["header"])
477+
self.assertEqual("this too", asgi_span.data["http"]["header"]["X-Capture-This-Too"])
478+
self.assertIn("X-Capture-That-Too", asgi_span.data["http"]["header"])
479+
self.assertEqual("that too", asgi_span.data["http"]["header"]["X-Capture-That-Too"])

0 commit comments

Comments
 (0)