Skip to content

Commit 894b419

Browse files
authored
no op trace id generation (#293)
* no op trace id generation * added clean up in tests and minor changes * modified typo * added checking false only conditions
1 parent 1294dee commit 894b419

File tree

5 files changed

+134
-9
lines changed

5 files changed

+134
-9
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ if xray_recorder.is_sampled():
192192
xray_recorder.put_metadata('mykey', metadata)
193193
```
194194

195+
### Generate NoOp Trace and Entity Id
196+
X-Ray Python SDK will by default generate no-op trace and entity id for unsampled requests and secure random trace and entity id for sampled requests. If customer wants to enable generating secure random trace and entity id for all the (sampled/unsampled) requests (this is applicable for trace id injection into logs use case) then they should set the `AWS_XRAY_NOOP_ID` environment variable as False.
197+
195198
### Disabling X-Ray
196199
Often times, it may be useful to be able to disable X-Ray for specific use cases, whether to stop X-Ray from sending traces at any moment, or to test code functionality that originally depended on X-Ray instrumented packages to begin segments prior to the code call. For example, if your application relied on an XRayMiddleware to instrument incoming web requests, and you have a method which begins subsegments based on the segment generated by that middleware, it would be useful to be able to disable X-Ray for your unit tests so that `SegmentNotFound` exceptions are not thrown when you need to test your method.
197200

aws_xray_sdk/core/models/dummy_entities.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
from .noop_traceid import NoOpTraceId
13
from .traceid import TraceId
24
from .segment import Segment
35
from .subsegment import Subsegment
@@ -12,9 +14,13 @@ class DummySegment(Segment):
1214
A dummy segment will not be sent to the X-Ray daemon. Manually create
1315
dummy segments is not recommended.
1416
"""
15-
def __init__(self, name='dummy'):
1617

17-
super(DummySegment, self).__init__(name=name, traceid=TraceId().to_id())
18+
def __init__(self, name='dummy'):
19+
no_op_id = os.getenv('AWS_XRAY_NOOP_ID')
20+
if no_op_id and no_op_id.lower() == 'false':
21+
super(DummySegment, self).__init__(name=name, traceid=TraceId().to_id())
22+
else:
23+
super(DummySegment, self).__init__(name=name, traceid=NoOpTraceId().to_id(), entityid='0000000000000000')
1824
self.sampled = False
1925

2026
def set_aws(self, aws_meta):
@@ -79,9 +85,14 @@ class DummySubsegment(Subsegment):
7985
to a dummy subsegment becomes no-op. Dummy subsegment will not
8086
be sent to the X-Ray daemon.
8187
"""
82-
def __init__(self, segment, name='dummy'):
8388

89+
def __init__(self, segment, name='dummy'):
8490
super(DummySubsegment, self).__init__(name, 'dummy', segment)
91+
no_op_id = os.getenv('AWS_XRAY_NOOP_ID')
92+
if no_op_id and no_op_id.lower() == 'false':
93+
super(Subsegment, self).__init__(name)
94+
else:
95+
super(Subsegment, self).__init__(name, entity_id='0000000000000000')
8596
self.sampled = False
8697

8798
def set_aws(self, aws_meta):

aws_xray_sdk/core/models/entity.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from . import http
1313
from ..exceptions.exceptions import AlreadyEndedException
1414

15-
1615
log = logging.getLogger(__name__)
1716

1817
# Valid characters can be found at http://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html
@@ -27,10 +26,14 @@ class Entity(object):
2726
The parent class for segment/subsegment. It holds common properties
2827
and methods on segment and subsegment.
2928
"""
30-
def __init__(self, name):
29+
30+
def __init__(self, name, entity_id=None):
31+
if not entity_id:
32+
self.id = self._generate_random_id()
33+
else:
34+
self.id = entity_id
3135

3236
# required attributes
33-
self.id = self._generate_random_id()
3437
self.name = name
3538
self.name = ''.join([c for c in name if c not in _common_invalid_name_characters])
3639
self.start_time = time.time()
@@ -267,7 +270,7 @@ def to_dict(self):
267270
with required properties that have non-empty values.
268271
"""
269272
entity_dict = {}
270-
273+
271274
for key, value in vars(self).items():
272275
if isinstance(value, bool) or value:
273276
if key == 'subsegments':
@@ -290,8 +293,8 @@ def to_dict(self):
290293
elif key == 'metadata':
291294
entity_dict[key] = metadata_to_dict(value)
292295
elif key != 'sampled' and key != ORIGIN_TRACE_HEADER_ATTR_KEY:
293-
entity_dict[key] = value
294-
296+
entity_dict[key] = value
297+
295298
return entity_dict
296299

297300
def _check_ended(self):
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class NoOpTraceId:
2+
"""
3+
A trace ID tracks the path of a request through your application.
4+
A trace collects all the segments generated by a single request.
5+
A trace ID is required for a segment.
6+
"""
7+
VERSION = '1'
8+
DELIMITER = '-'
9+
10+
def __init__(self):
11+
"""
12+
Generate a no-op trace id.
13+
"""
14+
self.start_time = '00000000'
15+
self.__number = '000000000000000000000000'
16+
17+
def to_id(self):
18+
"""
19+
Convert TraceId object to a string.
20+
"""
21+
return "%s%s%s%s%s" % (NoOpTraceId.VERSION, NoOpTraceId.DELIMITER,
22+
self.start_time,
23+
NoOpTraceId.DELIMITER, self.__number)

tests/test_traceid.py

+85
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
import os
2+
import pytest
3+
from aws_xray_sdk.core import xray_recorder
14
from aws_xray_sdk.core.models.traceid import TraceId
25

36

7+
@pytest.fixture(autouse=True)
8+
def cleanup():
9+
"""
10+
Clean up Environmental Variable for enable before and after tests
11+
"""
12+
if 'AWS_XRAY_NOOP_ID' in os.environ:
13+
del os.environ['AWS_XRAY_NOOP_ID']
14+
yield
15+
if 'AWS_XRAY_NOOP_ID' in os.environ:
16+
del os.environ['AWS_XRAY_NOOP_ID']
17+
18+
419
def test_id_format():
520
trace_id = TraceId().to_id()
621
assert len(trace_id) == 35
@@ -9,3 +24,73 @@ def test_id_format():
924
assert parts[0] == '1'
1025
int(parts[1], 16)
1126
int(parts[2], 16)
27+
28+
29+
def test_id_generation_default_sampling_false():
30+
segment = xray_recorder.begin_segment('segment_name', sampling=False)
31+
32+
# Start and end a subsegment
33+
subsegment = xray_recorder.begin_subsegment('subsegment_name')
34+
xray_recorder.end_subsegment()
35+
36+
# Close the segment
37+
xray_recorder.end_segment()
38+
39+
assert segment.id == '0000000000000000'
40+
assert segment.trace_id == '1-00000000-000000000000000000000000'
41+
assert subsegment.id == '0000000000000000'
42+
assert subsegment.trace_id == '1-00000000-000000000000000000000000'
43+
assert subsegment.parent_id == '0000000000000000'
44+
45+
46+
def test_id_generation_default_sampling_true():
47+
segment = xray_recorder.begin_segment('segment_name', sampling=True)
48+
49+
# Start and end a subsegment
50+
subsegment = xray_recorder.begin_subsegment('subsegment_name')
51+
xray_recorder.end_subsegment()
52+
53+
# Close the segment
54+
xray_recorder.end_segment()
55+
56+
assert segment.id != '0000000000000000'
57+
assert segment.trace_id != '1-00000000-000000000000000000000000'
58+
assert subsegment.id != '0000000000000000'
59+
assert subsegment.trace_id != '1-00000000-000000000000000000000000'
60+
assert subsegment.parent_id != '0000000000000000'
61+
62+
63+
def test_id_generation_noop_true():
64+
os.environ['AWS_XRAY_NOOP_ID'] = 'True'
65+
segment = xray_recorder.begin_segment('segment_name', sampling=False)
66+
67+
# Start and end a subsegment
68+
subsegment = xray_recorder.begin_subsegment('subsegment_name')
69+
xray_recorder.end_subsegment()
70+
71+
# Close the segment
72+
xray_recorder.end_segment()
73+
74+
assert segment.id == '0000000000000000'
75+
assert segment.trace_id == '1-00000000-000000000000000000000000'
76+
assert subsegment.id == '0000000000000000'
77+
assert subsegment.trace_id == '1-00000000-000000000000000000000000'
78+
assert subsegment.parent_id == '0000000000000000'
79+
80+
81+
def test_id_generation_noop_false():
82+
os.environ['AWS_XRAY_NOOP_ID'] = 'FALSE'
83+
segment = xray_recorder.begin_segment('segment_name', sampling=False)
84+
85+
# Start and end a subsegment
86+
subsegment = xray_recorder.begin_subsegment('subsegment_name')
87+
xray_recorder.end_subsegment()
88+
89+
# Close the segment
90+
xray_recorder.end_segment()
91+
92+
assert segment.id != '0000000000000000'
93+
assert segment.trace_id != '1-00000000-000000000000000000000000'
94+
assert subsegment.id != '0000000000000000'
95+
assert subsegment.trace_id != '1-00000000-000000000000000000000000'
96+
assert subsegment.parent_id != '0000000000000000'

0 commit comments

Comments
 (0)