Skip to content

Commit 854291e

Browse files
authored
Better context management & support of deeply nested spans (#63)
* Rename InstanaSpan to JsonSpan * Rename span to a more appropriate name * Update import to follow rename * Introduce InstanaSpan so that we can override interesting points. * Better context management & support of deeply nested spans * Use InstanaSpan.finish to track span closures * Tests: add validations to assure span relationships * Tests: add tests to validate urllib3 redirects * In case of ID errors, return 0xBADDCAFE (indicating where I was when I wrote the code)
1 parent 1acee9a commit 854291e

File tree

8 files changed

+177
-81
lines changed

8 files changed

+177
-81
lines changed

instana/json_span.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
class JsonSpan(object):
2+
t = 0
3+
p = None
4+
s = 0
5+
ts = 0
6+
ta = "py"
7+
d = 0
8+
n = None
9+
f = None
10+
ec = 0
11+
error = None
12+
data = None
13+
14+
def __init__(self, **kwds):
15+
for key in kwds:
16+
self.__dict__[key] = kwds[key]
17+
18+
19+
class Data(object):
20+
service = None
21+
http = None
22+
baggage = None
23+
custom = None
24+
sdk = None
25+
26+
def __init__(self, **kwds):
27+
self.__dict__.update(kwds)
28+
29+
30+
class HttpData(object):
31+
host = None
32+
url = None
33+
status = 0
34+
method = None
35+
36+
def __init__(self, **kwds):
37+
self.__dict__.update(kwds)
38+
39+
40+
class CustomData(object):
41+
tags = None
42+
logs = None
43+
44+
def __init__(self, **kwds):
45+
self.__dict__.update(kwds)
46+
47+
48+
class SDKData(object):
49+
name = None
50+
Type = None
51+
arguments = None
52+
Return = None
53+
custom = None
54+
55+
def __init__(self, **kwds):
56+
self.__dict__.update(kwds)

instana/recorder.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import opentracing.ext.tags as ext
88
from basictracer import Sampler, SpanRecorder
9-
from .span import CustomData, Data, HttpData, InstanaSpan, SDKData
9+
from .json_span import CustomData, Data, HttpData, JsonSpan, SDKData
1010
from .agent_const import AGENT_TRACES_URL
1111

1212
import sys
@@ -65,21 +65,21 @@ def clear_spans(self):
6565

6666
def record_span(self, span):
6767
"""
68-
Convert the passed BasicSpan into an InstanaSpan and
68+
Convert the passed BasicSpan into an JsonSpan and
6969
add it to the span queue
7070
"""
7171
if self.sensor.agent.can_send() or "INSTANA_TEST" in os.environ:
72-
instana_span = None
72+
json_span = None
7373

7474
if span.operation_name in self.registered_spans:
75-
instana_span = self.build_registered_span(span)
75+
json_span = self.build_registered_span(span)
7676
else:
77-
instana_span = self.build_sdk_span(span)
77+
json_span = self.build_sdk_span(span)
7878

79-
self.queue.put(instana_span)
79+
self.queue.put(json_span)
8080

8181
def build_registered_span(self, span):
82-
""" Takes a BasicSpan and converts it into a registered InstanaSpan """
82+
""" Takes a BasicSpan and converts it into a registered JsonSpan """
8383
data = Data(http=HttpData(host=self.get_host_name(span),
8484
url=self.get_string_tag(span, ext.HTTP_URL),
8585
method=self.get_string_tag(span, ext.HTTP_METHOD),
@@ -90,7 +90,7 @@ def build_registered_span(self, span):
9090
entityFrom = {'e': self.sensor.agent.from_.pid,
9191
'h': self.sensor.agent.from_.agentUuid}
9292

93-
return InstanaSpan(
93+
return JsonSpan(
9494
n=span.operation_name,
9595
t=span.context.trace_id,
9696
p=span.parent_id,
@@ -103,7 +103,7 @@ def build_registered_span(self, span):
103103
data=data)
104104

105105
def build_sdk_span(self, span):
106-
""" Takes a BasicSpan and converts into an SDK type InstanaSpan """
106+
""" Takes a BasicSpan and converts into an SDK type JsonSpan """
107107

108108
custom_data = CustomData(tags=span.tags,
109109
logs=self.collect_logs(span))
@@ -116,7 +116,7 @@ def build_sdk_span(self, span):
116116
entityFrom = {'e': self.sensor.agent.from_.pid,
117117
'h': self.sensor.agent.from_.agentUuid}
118118

119-
return InstanaSpan(
119+
return JsonSpan(
120120
t=span.context.trace_id,
121121
p=span.parent_id,
122122
s=span.context.span_id,

instana/span.py

Lines changed: 16 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,16 @@
1-
class InstanaSpan(object):
2-
t = 0
3-
p = None
4-
s = 0
5-
ts = 0
6-
ta = "py"
7-
d = 0
8-
n = None
9-
f = None
10-
ec = 0
11-
error = None
12-
data = None
13-
14-
def __init__(self, **kwds):
15-
for key in kwds:
16-
self.__dict__[key] = kwds[key]
17-
18-
19-
class Data(object):
20-
service = None
21-
http = None
22-
baggage = None
23-
custom = None
24-
sdk = None
25-
26-
def __init__(self, **kwds):
27-
self.__dict__.update(kwds)
28-
29-
30-
class HttpData(object):
31-
host = None
32-
url = None
33-
status = 0
34-
method = None
35-
36-
def __init__(self, **kwds):
37-
self.__dict__.update(kwds)
38-
39-
40-
class CustomData(object):
41-
tags = None
42-
logs = None
43-
44-
def __init__(self, **kwds):
45-
self.__dict__.update(kwds)
46-
47-
48-
class SDKData(object):
49-
name = None
50-
Type = None
51-
arguments = None
52-
Return = None
53-
custom = None
54-
55-
def __init__(self, **kwds):
56-
self.__dict__.update(kwds)
1+
from basictracer.span import BasicSpan
2+
from basictracer.context import SpanContext
3+
4+
5+
class InstanaSpan(BasicSpan):
6+
def finish(self, finish_time=None):
7+
if self.parent_id is None:
8+
self.tracer.cur_ctx = None
9+
else:
10+
# Set tracer context to the parent span
11+
pctx = SpanContext(span_id=self.parent_id,
12+
trace_id=self.context.trace_id,
13+
baggage={},
14+
sampled=True)
15+
self.tracer.cur_ctx = pctx
16+
super(InstanaSpan, self).finish(finish_time)

instana/tracer.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
import instana
66
import instana.options as o
77

8+
from instana.util import generate_id
9+
from instana.span import InstanaSpan
810
from basictracer.context import SpanContext
9-
from basictracer.span import BasicSpan
1011
from instana.http_propagator import HTTPPropagator
1112
from instana.text_propagator import TextPropagator
12-
from instana.util import generate_id
1313

1414

1515
class InstanaTracer(BasicTracer):
1616
sensor = None
17-
current_span = None
17+
cur_ctx = None
1818

1919
def __init__(self, options=o.Options()):
2020
self.sensor = instana.global_sensor
@@ -58,20 +58,21 @@ def start_span(
5858
ctx.sampled = self.sampler.sampled(ctx.trace_id)
5959

6060
# Tie it all together
61-
self.current_span = BasicSpan(
61+
span = InstanaSpan(
6262
self,
6363
operation_name=operation_name,
6464
context=ctx,
6565
parent_id=(None if parent_ctx is None else parent_ctx.span_id),
6666
tags=tags,
6767
start_time=start_time)
6868

69-
return self.current_span
69+
self.cur_ctx = span.context
70+
return span
7071

7172
def current_context(self):
7273
context = None
73-
if self.current_span:
74-
context = self.current_span.context
74+
if self.cur_ctx is not None:
75+
context = self.cur_ctx
7576
return context
7677

7778
def inject(self, span_context, format, carrier):

instana/util.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import random
22
import os
3+
import json
34
import time
45
import struct
56
import binascii
@@ -14,8 +15,9 @@
1415
_rnd = random.Random()
1516
_current_pid = 0
1617

17-
BAD_ID_LONG = 3135097598 # Bad Cafe in base 10
18-
BAD_ID_HEADER = "BADDCAFE" # Bad Cafe
18+
BAD_ID_LONG = 3135097598 # Bad Cafe in base 10
19+
BAD_ID_HEADER = "BADDCAFE" # Bad Cafe
20+
1921

2022
def generate_id():
2123
""" Generate a 64bit signed integer for use as a Span or Trace ID """
@@ -58,3 +60,11 @@ def header_to_id(header):
5860
return struct.unpack('>q', r)[0]
5961
except ValueError:
6062
return BAD_ID_LONG
63+
64+
65+
def to_json(obj):
66+
try:
67+
return json.dumps(obj, default=lambda obj: {k.lower(): v for k, v in obj.__dict__.items()},
68+
sort_keys=False, separators=(',', ':')).encode()
69+
except Exception as e:
70+
log.info("to_json: ", e, obj)

tests/apps/flaskalino.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3-
from flask import Flask
3+
from flask import Flask, redirect
44
app = Flask(__name__)
55
app.debug = False
66
app.use_reloader = False
@@ -11,24 +11,34 @@ def hello():
1111
return "<center><h1>🐍 Hello Stan! 🦄</h1></center>"
1212

1313

14+
@app.route("/301")
15+
def threehundredone():
16+
return redirect('/', code=301)
17+
18+
19+
@app.route("/302")
20+
def threehundredtwo():
21+
return redirect('/', code=302)
22+
23+
1424
@app.route("/400")
1525
def fourhundred():
16-
return "Bad Request", 400
26+
return "Simulated Bad Request", 400
1727

1828

1929
@app.route("/405")
2030
def fourhundredfive():
21-
return "Method not allowed", 405
31+
return "Simulated Method not allowed", 405
2232

2333

2434
@app.route("/500")
2535
def fivehundred():
26-
return "Internal Server Error", 500
36+
return "Simulated Internal Server Error", 500
2737

2838

2939
@app.route("/504")
3040
def fivehundredfour():
31-
return "Gateway Timeout", 504
41+
return "Simulated Gateway Timeout", 504
3242

3343

3444
@app.route("/exception")

tests/test_helpers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def test_vanilla_eum_snippet():
1919
assert eum_string.find(trace_id) != -1
2020
assert eum_string.find(eum_api_key) != -1
2121

22+
2223
def test_eum_snippet_with_meta():
2324
meta_kvs = {}
2425
meta_kvs['meta1'] = meta1
@@ -34,6 +35,7 @@ def test_eum_snippet_with_meta():
3435
assert eum_string.find(meta2) != -1
3536
assert eum_string.find(meta3) != -1
3637

38+
3739
def test_eum_snippet_error():
3840
meta_kvs = {}
3941
meta_kvs['meta1'] = meta1
@@ -44,6 +46,7 @@ def test_eum_snippet_error():
4446
eum_string = eum_snippet(eum_api_key=eum_api_key, meta=meta_kvs)
4547
assert_equals('', eum_string)
4648

49+
4750
def test_vanilla_eum_test_snippet():
4851
eum_string = eum_test_snippet(trace_id=trace_id, eum_api_key=eum_api_key)
4952
assert type(eum_string) is str
@@ -53,6 +56,7 @@ def test_vanilla_eum_test_snippet():
5356
assert eum_string.find('reportingUrl') != -1
5457
assert eum_string.find('//eum-test-fullstack-0-us-west-2.instana.io') != -1
5558

59+
5660
def test_eum_test_snippet_with_meta():
5761
meta_kvs = {}
5862
meta_kvs['meta1'] = meta1
@@ -70,6 +74,7 @@ def test_eum_test_snippet_with_meta():
7074
assert eum_string.find(meta2) != -1
7175
assert eum_string.find(meta3) != -1
7276

77+
7378
def test_eum_test_snippet_error():
7479
meta_kvs = {}
7580
meta_kvs['meta1'] = meta1

0 commit comments

Comments
 (0)