Skip to content

Commit b149928

Browse files
author
Peter Giacomo Lombardo
authored
OpenTracing: Tag Validation (#261)
* Move SpanContext into own module * Use isinstance instead of type * Pylint * Tag validation * Change value type check * Better OT log support * Remove debug
1 parent 09565bf commit b149928

File tree

8 files changed

+182
-120
lines changed

8 files changed

+182
-120
lines changed

instana/binary_propagator.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .log import logger
66
from .util import header_to_id
7-
from .span import SpanContext
7+
from .span_context import SpanContext
88

99

1010
class BinaryPropagator():
@@ -21,15 +21,15 @@ def inject(self, span_context, carrier):
2121
span_id = str.encode(span_context.span_id)
2222
level = str.encode("1")
2323

24-
if type(carrier) is dict or hasattr(carrier, "__dict__"):
24+
if isinstance(carrier, dict) or hasattr(carrier, "__dict__"):
2525
carrier[self.HEADER_KEY_T] = trace_id
2626
carrier[self.HEADER_KEY_S] = span_id
2727
carrier[self.HEADER_KEY_L] = level
28-
elif type(carrier) is list:
28+
elif isinstance(carrier, list):
2929
carrier.append((self.HEADER_KEY_T, trace_id))
3030
carrier.append((self.HEADER_KEY_S, span_id))
3131
carrier.append((self.HEADER_KEY_L, level))
32-
elif type(carrier) is tuple:
32+
elif isinstance(carrier, tuple):
3333
carrier = carrier.__add__(((self.HEADER_KEY_T, trace_id),))
3434
carrier = carrier.__add__(((self.HEADER_KEY_S, span_id),))
3535
carrier = carrier.__add__(((self.HEADER_KEY_L, level),))
@@ -50,17 +50,17 @@ def extract(self, carrier): # noqa
5050
level = None
5151

5252
try:
53-
if type(carrier) is dict or hasattr(carrier, "__getitem__"):
53+
if isinstance(carrier, dict) or hasattr(carrier, "__getitem__"):
5454
dc = carrier
5555
elif hasattr(carrier, "__dict__"):
5656
dc = carrier.__dict__
57-
elif type(carrier) is list:
57+
elif isinstance(carrier, list):
5858
dc = dict(carrier)
5959
else:
6060
raise ot.SpanContextCorruptedException()
6161

6262
for key, value in dc.items():
63-
if type(key) is str:
63+
if isinstance(key, str):
6464
key = str.encode(key)
6565

6666
if self.HEADER_KEY_T == key:

instana/http_propagator.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import opentracing as ot
44

55
from .log import logger
6-
from .span import SpanContext
76
from .util import header_to_id
7+
from .span_context import SpanContext
88

99
# The carrier can be a dict or a list.
1010
# Using the trace header as an example, it can be in the following forms
@@ -43,11 +43,11 @@ def inject(self, span_context, carrier):
4343
trace_id = span_context.trace_id
4444
span_id = span_context.span_id
4545

46-
if type(carrier) is dict or hasattr(carrier, "__dict__"):
46+
if isinstance(carrier, dict) or hasattr(carrier, "__dict__"):
4747
carrier[self.HEADER_KEY_T] = trace_id
4848
carrier[self.HEADER_KEY_S] = span_id
4949
carrier[self.HEADER_KEY_L] = "1"
50-
elif type(carrier) is list:
50+
elif isinstance(carrier, list):
5151
carrier.append((self.HEADER_KEY_T, trace_id))
5252
carrier.append((self.HEADER_KEY_S, span_id))
5353
carrier.append((self.HEADER_KEY_L, "1"))
@@ -68,11 +68,11 @@ def extract(self, carrier): # noqa
6868
synthetic = False
6969

7070
try:
71-
if type(carrier) is dict or hasattr(carrier, "__getitem__"):
71+
if isinstance(carrier, dict) or hasattr(carrier, "__getitem__"):
7272
dc = carrier
7373
elif hasattr(carrier, "__dict__"):
7474
dc = carrier.__dict__
75-
elif type(carrier) is list:
75+
elif isinstance(carrier, list):
7676
dc = dict(carrier)
7777
else:
7878
raise ot.SpanContextCorruptedException()

instana/span.py

Lines changed: 102 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,102 @@
1+
"""
2+
This module contains the classes that represents spans.
3+
4+
InstanaSpan - the OpenTracing based span used during tracing
5+
6+
When an InstanaSpan is finished, it is converted into either an SDKSpan
7+
or RegisteredSpan depending on type.
8+
9+
BaseSpan: Base class containing the commonalities for the two descendants
10+
- SDKSpan: Class that represents an SDK type span
11+
- RegisteredSpan: Class that represents a Registered type span
12+
"""
113
import six
2-
import sys
3-
from .log import logger
4-
from .util import DictionaryOfStan
14+
515
from basictracer.span import BasicSpan
616
import opentracing.ext.tags as ot_tags
717

8-
9-
class SpanContext():
10-
def __init__(
11-
self,
12-
trace_id=None,
13-
span_id=None,
14-
baggage=None,
15-
sampled=True,
16-
level=1,
17-
synthetic=False):
18-
19-
self.level = level
20-
self.trace_id = trace_id
21-
self.span_id = span_id
22-
self.sampled = sampled
23-
self.synthetic = synthetic
24-
self._baggage = baggage or {}
25-
26-
@property
27-
def baggage(self):
28-
return self._baggage
29-
30-
def with_baggage_item(self, key, value):
31-
new_baggage = self._baggage.copy()
32-
new_baggage[key] = value
33-
return SpanContext(
34-
trace_id=self.trace_id,
35-
span_id=self.span_id,
36-
sampled=self.sampled,
37-
baggage=new_baggage)
18+
from .log import logger
19+
from .util import DictionaryOfStan
3820

3921

4022
class InstanaSpan(BasicSpan):
4123
stack = None
4224
synthetic = False
4325

44-
def finish(self, finish_time=None):
45-
super(InstanaSpan, self).finish(finish_time)
26+
def __init__(self, tracer, operation_name=None, context=None, parent_id=None, tags=None, start_time=None):
27+
# Tag validation
28+
filtered_tags = {}
29+
if tags is not None:
30+
for key in tags.keys():
31+
validated_key, validated_value = self._validate_tag(key, tags[key])
32+
if validated_key is not None:
33+
filtered_tags[validated_key] = validated_value
34+
35+
super(InstanaSpan, self).__init__(tracer, operation_name, context, parent_id, filtered_tags, start_time)
36+
37+
def _validate_tag(self, key, value):
38+
"""
39+
This method will assure that <key> and <value> are valid to set as a tag.
40+
If <value> fails the check, an attempt will be made to convert it into
41+
something useful.
42+
43+
On check failure, this method will return None values indicating that the tag is
44+
not valid and could not be converted into something useful
45+
46+
:param key: The tag key
47+
:param value: The tag value
48+
:return: Tuple (key, value)
49+
"""
50+
validated_key = None
51+
validated_value = None
52+
53+
try:
54+
# Tag keys must be some type of text or string type
55+
if isinstance(key, (six.text_type, six.string_types)):
56+
validated_key = key[0:1024] # Max key length of 1024 characters
57+
58+
if isinstance(value, (bool, float, int, list, dict, six.text_type, six.string_types)):
59+
validated_value = value
60+
else:
61+
validated_value = self._convert_tag_value(value)
62+
else:
63+
logger.debug("(non-fatal) tag names must be strings. tag discarded for %s", type(key))
64+
except Exception:
65+
logger.debug("instana.span._validate_tag: ", exc_info=True)
66+
67+
return (validated_key, validated_value)
68+
69+
def _convert_tag_value(self, value):
70+
final_value = None
71+
72+
try:
73+
final_value = repr(value)
74+
except Exception:
75+
final_value = "(non-fatal) span.set_tag: values must be one of these types: bool, float, int, list, " \
76+
"set, str or alternatively support 'repr'. tag discarded"
77+
logger.debug(final_value, exc_info=True)
78+
return None
79+
return final_value
4680

4781
def set_tag(self, key, value):
48-
# Key validation
49-
if not isinstance(key, six.text_type) and not isinstance(key, six.string_types) :
50-
logger.debug("(non-fatal) span.set_tag: tag names must be strings. tag discarded for %s", type(key))
51-
return self
82+
validated_key, validated_value = self._validate_tag(key, value)
5283

53-
final_value = value
54-
value_type = type(value)
84+
if validated_key is not None and validated_value is not None:
85+
return super(InstanaSpan, self).set_tag(validated_key, validated_value)
5586

56-
# Value validation
57-
if value_type in [bool, float, int, list, str]:
58-
return super(InstanaSpan, self).set_tag(key, final_value)
87+
return self
5988

60-
elif isinstance(value, six.text_type):
61-
final_value = str(value)
89+
def log_kv(self, key_values, timestamp=None):
90+
validated_key = None
91+
validated_value = None
6292

63-
else:
64-
try:
65-
final_value = repr(value)
66-
except:
67-
final_value = "(non-fatal) span.set_tag: values must be one of these types: bool, float, int, list, " \
68-
"set, str or alternatively support 'repr'. tag discarded"
69-
logger.debug(final_value, exc_info=True)
70-
return self
93+
for key in key_values.keys():
94+
validated_key, validated_value = self._validate_tag(key, key_values[key])
95+
96+
if validated_key is not None and validated_value is not None:
97+
return super(InstanaSpan, self).log_kv({validated_key: validated_value}, timestamp)
7198

72-
return super(InstanaSpan, self).set_tag(key, final_value)
99+
return self
73100

74101
def mark_as_errored(self, tags = None):
75102
"""
@@ -81,7 +108,7 @@ def mark_as_errored(self, tags = None):
81108
ec = self.tags.get('ec', 0)
82109
self.set_tag('ec', ec + 1)
83110

84-
if tags is not None and type(tags) is dict:
111+
if tags is not None and isinstance(tags, dict):
85112
for key in tags:
86113
self.set_tag(key, tags[key])
87114
except Exception:
@@ -99,7 +126,7 @@ def assure_errored(self):
99126
except Exception:
100127
logger.debug('span.assure_errored', exc_info=True)
101128

102-
def log_exception(self, e):
129+
def log_exception(self, exc):
103130
"""
104131
Log an exception onto this span. This will log pertinent info from the exception and
105132
assure that this span is marked as errored.
@@ -110,12 +137,12 @@ def log_exception(self, e):
110137
message = ""
111138
self.mark_as_errored()
112139

113-
if hasattr(e, '__str__') and len(str(e)) > 0:
114-
message = str(e)
115-
elif hasattr(e, 'message') and e.message is not None:
116-
message = e.message
140+
if hasattr(exc, '__str__') and len(str(exc)) > 0:
141+
message = str(exc)
142+
elif hasattr(exc, 'message') and exc.message is not None:
143+
message = exc.message
117144
else:
118-
message = repr(e)
145+
message = repr(exc)
119146

120147
if self.operation_name in ['rpc-server', 'rpc-client']:
121148
self.set_tag('rpc.error', message)
@@ -133,39 +160,18 @@ def log_exception(self, e):
133160
logger.debug("span.log_exception", exc_info=True)
134161
raise
135162

136-
def collect_logs(self):
137-
"""
138-
Collect up log data and feed it to the Instana brain.
139-
140-
:param span: The span to search for logs in
141-
:return: Logs ready for consumption by the Instana brain.
142-
"""
143-
logs = {}
144-
for log in self.logs:
145-
ts = int(round(log.timestamp * 1000))
146-
if ts not in logs:
147-
logs[ts] = {}
148-
149-
if 'message' in log.key_values:
150-
logs[ts]['message'] = log.key_values['message']
151-
if 'event' in log.key_values:
152-
logs[ts]['event'] = log.key_values['event']
153-
if 'parameters' in log.key_values:
154-
logs[ts]['parameters'] = log.key_values['parameters']
155-
156-
return logs
157-
158163

159164
class BaseSpan(object):
160165
sy = None
161-
166+
162167
def __str__(self):
163168
return "BaseSpan(%s)" % self.__dict__.__str__()
164169

165170
def __repr__(self):
166171
return self.__dict__.__str__()
167172

168173
def __init__(self, span, source, service_name, **kwargs):
174+
# pylint: disable=invalid-name
169175
self.t = span.context.trace_id
170176
self.p = span.parent_id
171177
self.s = span.context.span_id
@@ -189,6 +195,7 @@ class SDKSpan(BaseSpan):
189195
EXIT_KIND = ["exit", "client", "producer"]
190196

191197
def __init__(self, span, source, service_name, **kwargs):
198+
# pylint: disable=invalid-name
192199
super(SDKSpan, self).__init__(span, source, service_name, **kwargs)
193200

194201
span_kind = self.get_span_kind(span)
@@ -202,13 +209,18 @@ def __init__(self, span, source, service_name, **kwargs):
202209
self.data["sdk"]["name"] = span.operation_name
203210
self.data["sdk"]["type"] = span_kind[0]
204211
self.data["sdk"]["custom"]["tags"] = span.tags
205-
self.data["sdk"]["custom"]["logs"] = span.logs
212+
213+
if span.logs is not None and len(span.logs) > 0:
214+
logs = DictionaryOfStan()
215+
for log in span.logs:
216+
logs[repr(log.timestamp)] = log.key_values
217+
self.data["sdk"]["custom"]["logs"] = logs
206218

207219
if "arguments" in span.tags:
208-
self.data.sdk.arguments = span.tags["arguments"]
220+
self.data['sdk']['arguments'] = span.tags["arguments"]
209221

210222
if "return" in span.tags:
211-
self.data.sdk.Return = span.tags["return"]
223+
self.data['sdk']['return'] = span.tags["return"]
212224

213225
if len(span.context.baggage) > 0:
214226
self.data["baggage"] = span.context.baggage
@@ -244,6 +256,7 @@ class RegisteredSpan(BaseSpan):
244256
LOCAL_SPANS = ("render")
245257

246258
def __init__(self, span, source, service_name, **kwargs):
259+
# pylint: disable=invalid-name
247260
super(RegisteredSpan, self).__init__(span, source, service_name, **kwargs)
248261
self.n = span.operation_name
249262

@@ -263,7 +276,7 @@ def __init__(self, span, source, service_name, **kwargs):
263276
self.k = 1 # entry
264277

265278
# Store any leftover tags in the custom section
266-
if len(span.tags):
279+
if len(span.tags) > 0:
267280
self.data["custom"]["tags"] = span.tags
268281

269282
def _populate_entry_span_data(self, span):

0 commit comments

Comments
 (0)