Skip to content

Commit a824e65

Browse files
author
Andrew Slotin
authored
Add synthetic calls support (instana#249)
* Mark span context as synthetic if X-Instana-Synthetic is set to 1 * Mark span as synthetic if the X-Instana-Synthetic header is set to 1 * Populate span.sy from X-Instana-Synthetic * Return an empty synthetic span context even if there was no trace context found
1 parent eac3c15 commit a824e65

13 files changed

+219
-9
lines changed

instana/http_propagator.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ class HTTPPropagator():
2828
LC_HEADER_KEY_T = 'x-instana-t'
2929
LC_HEADER_KEY_S = 'x-instana-s'
3030
LC_HEADER_KEY_L = 'x-instana-l'
31+
LC_HEADER_KEY_SYNTHETIC = 'x-instana-synthetic'
3132

3233
ALT_HEADER_KEY_T = 'HTTP_X_INSTANA_T'
3334
ALT_HEADER_KEY_S = 'HTTP_X_INSTANA_S'
3435
ALT_HEADER_KEY_L = 'HTTP_X_INSTANA_L'
3536
ALT_LC_HEADER_KEY_T = 'http_x_instana_t'
3637
ALT_LC_HEADER_KEY_S = 'http_x_instana_s'
3738
ALT_LC_HEADER_KEY_L = 'http_x_instana_l'
39+
ALT_LC_HEADER_KEY_SYNTHETIC = 'http_x_instana_synthetic'
3840

3941
def inject(self, span_context, carrier):
4042
try:
@@ -63,6 +65,7 @@ def extract(self, carrier): # noqa
6365
trace_id = None
6466
span_id = None
6567
level = 1
68+
synthetic = False
6669

6770
try:
6871
if type(carrier) is dict or hasattr(carrier, "__getitem__"):
@@ -85,21 +88,29 @@ def extract(self, carrier): # noqa
8588
span_id = header_to_id(dc[key])
8689
elif self.LC_HEADER_KEY_L == lc_key:
8790
level = dc[key]
91+
elif self.LC_HEADER_KEY_SYNTHETIC == lc_key:
92+
synthetic = dc[key] == "1"
8893

8994
elif self.ALT_LC_HEADER_KEY_T == lc_key:
9095
trace_id = header_to_id(dc[key])
9196
elif self.ALT_LC_HEADER_KEY_S == lc_key:
9297
span_id = header_to_id(dc[key])
9398
elif self.ALT_LC_HEADER_KEY_L == lc_key:
9499
level = dc[key]
100+
elif self.ALT_LC_HEADER_KEY_SYNTHETIC == lc_key:
101+
synthetic = dc[key] == "1"
95102

96103
ctx = None
97104
if trace_id is not None and span_id is not None:
98105
ctx = SpanContext(span_id=span_id,
99-
trace_id=trace_id,
100-
level=level,
101-
baggage={},
102-
sampled=True)
106+
trace_id=trace_id,
107+
level=level,
108+
baggage={},
109+
sampled=True,
110+
synthetic=synthetic)
111+
elif synthetic:
112+
ctx = SpanContext(synthetic=synthetic)
113+
103114
return ctx
104115

105116
except Exception:

instana/span.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ def __init__(
1313
span_id=None,
1414
baggage=None,
1515
sampled=True,
16-
level=1):
16+
level=1,
17+
synthetic=False):
1718

1819
self.level = level
1920
self.trace_id = trace_id
2021
self.span_id = span_id
2122
self.sampled = sampled
23+
self.synthetic = synthetic
2224
self._baggage = baggage or {}
2325

2426
@property
@@ -37,6 +39,7 @@ def with_baggage_item(self, key, value):
3739

3840
class InstanaSpan(BasicSpan):
3941
stack = None
42+
synthetic = False
4043

4144
def finish(self, finish_time=None):
4245
super(InstanaSpan, self).finish(finish_time)
@@ -154,6 +157,8 @@ def collect_logs(self):
154157

155158

156159
class BaseSpan(object):
160+
sy = None
161+
157162
def __str__(self):
158163
return "BaseSpan(%s)" % self.__dict__.__str__()
159164

@@ -170,6 +175,9 @@ def __init__(self, span, source, service_name, **kwargs):
170175
self.ec = span.tags.pop('ec', None)
171176
self.data = DictionaryOfStan()
172177

178+
if span.synthetic:
179+
self.sy = True
180+
173181
if span.stack:
174182
self.stack = span.stack
175183

instana/tracer.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def start_span(self,
8484
# Assemble the child ctx
8585
gid = generate_id()
8686
ctx = SpanContext(span_id=gid)
87-
if parent_ctx is not None:
87+
if parent_ctx is not None and parent_ctx.trace_id is not None:
8888
if parent_ctx._baggage is not None:
8989
ctx._baggage = parent_ctx._baggage.copy()
9090
ctx.trace_id = parent_ctx.trace_id
@@ -101,6 +101,9 @@ def start_span(self,
101101
tags=tags,
102102
start_time=start_time)
103103

104+
if parent_ctx is not None:
105+
span.synthetic = parent_ctx.synthetic
106+
104107
if operation_name in RegisteredSpan.EXIT_SPANS:
105108
self.__add_stack(span)
106109

tests/clients/test_urllib3.py

+2
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ def test_client_error(self):
509509
self.assertEqual(1, urllib3_span.ec)
510510

511511
def test_requestspkg_get(self):
512+
self.recorder.clear_spans()
513+
512514
with tracer.start_active_span('test'):
513515
r = requests.get(testenv["wsgi_server"] + '/', timeout=2)
514516

tests/data/lambda/api_gateway_event.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"X-Forwarded-Proto": "https",
4040
"X-Instana-T": "d5cb361b256413a9",
4141
"X-Instana-S": "0901d8ae4fbf1529",
42-
"X-Instana-L": "1"
42+
"X-Instana-L": "1",
43+
"X-Instana-Synthetic": "1"
4344
},
4445
"multiValueHeaders": {
4546
"Accept": [
@@ -132,4 +133,4 @@
132133
"apiId": "1234567890",
133134
"protocol": "HTTP/1.1"
134135
}
135-
}
136+
}

tests/frameworks/test_aiohttp.py

+28
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ async def test():
448448
self.assertEqual(aioclient_span.p, test_span.s)
449449
self.assertEqual(aioserver_span.p, aioclient_span.s)
450450

451+
# Synthetic
452+
self.assertIsNone(test_span.sy)
453+
self.assertIsNone(aioclient_span.sy)
454+
self.assertIsNone(aioserver_span.sy)
455+
451456
# Error logging
452457
self.assertIsNone(test_span.ec)
453458
self.assertIsNone(aioclient_span.ec)
@@ -478,6 +483,29 @@ async def test():
478483
assert("Server-Timing" in response.headers)
479484
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
480485

486+
def test_server_synthetic_request(self):
487+
async def test():
488+
headers = {
489+
'X-Instana-Synthetic': '1'
490+
}
491+
492+
with async_tracer.start_active_span('test'):
493+
async with aiohttp.ClientSession() as session:
494+
return await self.fetch(session, testenv["aiohttp_server"] + "/", headers=headers)
495+
496+
response = self.loop.run_until_complete(test())
497+
498+
spans = self.recorder.queued_spans()
499+
self.assertEqual(3, len(spans))
500+
501+
aioserver_span = spans[0]
502+
aioclient_span = spans[1]
503+
test_span = spans[2]
504+
505+
self.assertTrue(aioserver_span.sy)
506+
self.assertIsNone(aioclient_span.sy)
507+
self.assertIsNone(test_span.sy)
508+
481509
def test_server_get_with_params_to_scrub(self):
482510
async def test():
483511
with async_tracer.start_active_span('test'):

tests/frameworks/test_django.py

+26
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ def test_basic_request(self):
6161
self.assertEqual(urllib3_span.p, test_span.s)
6262
self.assertEqual(django_span.p, urllib3_span.s)
6363

64+
self.assertIsNone(django_span.sy)
65+
self.assertIsNone(urllib3_span.sy)
66+
self.assertIsNone(test_span.sy)
67+
6468
self.assertEqual(None, django_span.ec)
6569

6670
self.assertEqual('/', django_span.data["http"]["url"])
@@ -69,6 +73,28 @@ def test_basic_request(self):
6973
assert django_span.stack
7074
self.assertEqual(2, len(django_span.stack))
7175

76+
def test_synthetic_request(self):
77+
headers = {
78+
'X-Instana-Synthetic': '1'
79+
}
80+
81+
with tracer.start_active_span('test'):
82+
response = self.http.request('GET', self.live_server_url + '/', headers=headers)
83+
84+
assert response
85+
self.assertEqual(200, response.status)
86+
87+
spans = self.recorder.queued_spans()
88+
self.assertEqual(3, len(spans))
89+
90+
test_span = spans[2]
91+
urllib3_span = spans[1]
92+
django_span = spans[0]
93+
94+
self.assertTrue(django_span.sy)
95+
self.assertIsNone(urllib3_span.sy)
96+
self.assertIsNone(test_span.sy)
97+
7298
def test_request_with_error(self):
7399
with tracer.start_active_span('test'):
74100
response = self.http.request('GET', self.live_server_url + '/cause_error')

tests/frameworks/test_flask.py

+24
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def test_get_request(self):
6767
self.assertEqual(urllib3_span.p, test_span.s)
6868
self.assertEqual(wsgi_span.p, urllib3_span.s)
6969

70+
# Synthetic
71+
self.assertIsNone(wsgi_span.sy)
72+
self.assertIsNone(urllib3_span.sy)
73+
self.assertIsNone(test_span.sy)
74+
7075
# Error logging
7176
self.assertIsNone(test_span.ec)
7277
self.assertIsNone(urllib3_span.ec)
@@ -96,6 +101,25 @@ def test_get_request(self):
96101
# We should NOT have a path template for this route
97102
self.assertIsNone(wsgi_span.data["http"]["path_tpl"])
98103

104+
def test_synthetic_request(self):
105+
headers = {
106+
'X-Instana-Synthetic': '1'
107+
}
108+
109+
with tracer.start_active_span('test'):
110+
response = self.http.request('GET', testenv["wsgi_server"] + '/', headers=headers)
111+
112+
spans = self.recorder.queued_spans()
113+
self.assertEqual(3, len(spans))
114+
115+
wsgi_span = spans[0]
116+
urllib3_span = spans[1]
117+
test_span = spans[2]
118+
119+
self.assertTrue(wsgi_span.sy)
120+
self.assertIsNone(urllib3_span.sy)
121+
self.assertIsNone(test_span.sy)
122+
99123
def test_render_template(self):
100124
with tracer.start_active_span('test'):
101125
response = self.http.request('GET', testenv["wsgi_server"] + '/render')

tests/frameworks/test_pyramid.py

+27
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ def test_get_request(self):
6565
self.assertEqual(urllib3_span.p, test_span.s)
6666
self.assertEqual(pyramid_span.p, urllib3_span.s)
6767

68+
# Synthetic
69+
self.assertIsNone(pyramid_span.sy)
70+
self.assertIsNone(urllib3_span.sy)
71+
self.assertIsNone(test_span.sy)
72+
6873
# Error logging
6974
self.assertIsNone(test_span.ec)
7075
self.assertIsNone(urllib3_span.ec)
@@ -95,6 +100,28 @@ def test_get_request(self):
95100
self.assertTrue(type(urllib3_span.stack) is list)
96101
self.assertTrue(len(urllib3_span.stack) > 1)
97102

103+
def test_synthetic_request(self):
104+
headers = {
105+
'X-Instana-Synthetic': '1'
106+
}
107+
108+
with tracer.start_active_span('test'):
109+
response = self.http.request('GET', testenv["pyramid_server"] + '/', headers=headers)
110+
111+
spans = self.recorder.queued_spans()
112+
self.assertEqual(3, len(spans))
113+
114+
pyramid_span = spans[0]
115+
urllib3_span = spans[1]
116+
test_span = spans[2]
117+
118+
assert response
119+
self.assertEqual(200, response.status)
120+
121+
self.assertTrue(pyramid_span.sy)
122+
self.assertIsNone(urllib3_span.sy)
123+
self.assertIsNone(test_span.sy)
124+
98125
def test_500(self):
99126
with tracer.start_active_span('test'):
100127
response = self.http.request('GET', testenv["pyramid_server"] + '/500')

tests/frameworks/test_tornado_server.py

+28
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ async def test():
7373
self.assertEqual(aiohttp_span.p, test_span.s)
7474
self.assertEqual(tornado_span.p, aiohttp_span.s)
7575

76+
# Synthetic
77+
self.assertIsNone(tornado_span.sy)
78+
self.assertIsNone(aiohttp_span.sy)
79+
self.assertIsNone(test_span.sy)
80+
7681
# Error logging
7782
self.assertIsNone(test_span.ec)
7883
self.assertIsNone(aiohttp_span.ec)
@@ -166,6 +171,29 @@ async def test():
166171
self.assertTrue("Server-Timing" in response.headers)
167172
self.assertEqual(response.headers["Server-Timing"], "intid;desc=%s" % traceId)
168173

174+
def test_synthetic_request(self):
175+
async def test():
176+
headers = {
177+
'X-Instana-Synthetic': '1'
178+
}
179+
180+
with async_tracer.start_active_span('test'):
181+
async with aiohttp.ClientSession() as session:
182+
return await self.fetch(session, testenv["tornado_server"] + "/", headers=headers)
183+
184+
response = tornado.ioloop.IOLoop.current().run_sync(test)
185+
186+
spans = self.recorder.queued_spans()
187+
self.assertEqual(3, len(spans))
188+
189+
tornado_span = get_first_span_by_name(spans, "tornado-server")
190+
aiohttp_span = get_first_span_by_name(spans, "aiohttp-client")
191+
test_span = get_first_span_by_name(spans, "sdk")
192+
193+
self.assertTrue(tornado_span.sy)
194+
self.assertIsNone(aiohttp_span.sy)
195+
self.assertIsNone(test_span.sy)
196+
169197
def test_get_301(self):
170198
async def test():
171199
with async_tracer.start_active_span('test'):

tests/frameworks/test_wsgi.py

+24
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ def test_get_request(self):
6868
self.assertEqual(urllib3_span.p, test_span.s)
6969
self.assertEqual(wsgi_span.p, urllib3_span.s)
7070

71+
self.assertIsNone(wsgi_span.sy)
72+
self.assertIsNone(urllib3_span.sy)
73+
self.assertIsNone(test_span.sy)
74+
7175
# Error logging
7276
self.assertIsNone(test_span.ec)
7377
self.assertIsNone(urllib3_span.ec)
@@ -83,6 +87,26 @@ def test_get_request(self):
8387
self.assertIsNotNone(wsgi_span.stack)
8488
self.assertEqual(2, len(wsgi_span.stack))
8589

90+
def test_synthetic_request(self):
91+
headers = {
92+
'X-Instana-Synthetic': '1'
93+
}
94+
with tracer.start_active_span('test'):
95+
response = self.http.request('GET', testenv["wsgi_server"] + '/', headers=headers)
96+
97+
spans = self.recorder.queued_spans()
98+
99+
self.assertEqual(3, len(spans))
100+
self.assertIsNone(tracer.active_span)
101+
102+
wsgi_span = spans[0]
103+
urllib3_span = spans[1]
104+
test_span = spans[2]
105+
106+
self.assertTrue(wsgi_span.sy)
107+
self.assertIsNone(urllib3_span.sy)
108+
self.assertIsNone(test_span.sy)
109+
86110
def test_complex_request(self):
87111
with tracer.start_active_span('test'):
88112
response = self.http.request('GET', testenv["wsgi_server"] + '/complex')

0 commit comments

Comments
 (0)