Skip to content

Commit

Permalink
no op trace id generation (aws#293)
Browse files Browse the repository at this point in the history
* no op trace id generation

* added clean up in tests and minor changes

* modified typo

* added checking false only conditions
  • Loading branch information
bhautikpip authored and Tyler Hargraves committed Mar 22, 2022
1 parent 48dfb37 commit 5df17d3
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 9 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ if xray_recorder.is_sampled():
xray_recorder.put_metadata('mykey', metadata)
```

### Generate NoOp Trace and Entity Id
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.

### Disabling X-Ray
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.

Expand Down
17 changes: 14 additions & 3 deletions aws_xray_sdk/core/models/dummy_entities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os
from .noop_traceid import NoOpTraceId
from .traceid import TraceId
from .segment import Segment
from .subsegment import Subsegment
Expand All @@ -12,9 +14,13 @@ class DummySegment(Segment):
A dummy segment will not be sent to the X-Ray daemon. Manually create
dummy segments is not recommended.
"""
def __init__(self, name='dummy'):

super(DummySegment, self).__init__(name=name, traceid=TraceId().to_id())
def __init__(self, name='dummy'):
no_op_id = os.getenv('AWS_XRAY_NOOP_ID')
if no_op_id and no_op_id.lower() == 'false':
super(DummySegment, self).__init__(name=name, traceid=TraceId().to_id())
else:
super(DummySegment, self).__init__(name=name, traceid=NoOpTraceId().to_id(), entityid='0000000000000000')
self.sampled = False

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

def __init__(self, segment, name='dummy'):
super(DummySubsegment, self).__init__(name, 'dummy', segment)
no_op_id = os.getenv('AWS_XRAY_NOOP_ID')
if no_op_id and no_op_id.lower() == 'false':
super(Subsegment, self).__init__(name)
else:
super(Subsegment, self).__init__(name, entity_id='0000000000000000')
self.sampled = False

def set_aws(self, aws_meta):
Expand Down
15 changes: 9 additions & 6 deletions aws_xray_sdk/core/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from . import http
from ..exceptions.exceptions import AlreadyEndedException


log = logging.getLogger(__name__)

# Valid characters can be found at http://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html
Expand All @@ -27,10 +26,14 @@ class Entity(object):
The parent class for segment/subsegment. It holds common properties
and methods on segment and subsegment.
"""
def __init__(self, name):

def __init__(self, name, entity_id=None):
if not entity_id:
self.id = self._generate_random_id()
else:
self.id = entity_id

# required attributes
self.id = self._generate_random_id()
self.name = name
self.name = ''.join([c for c in name if c not in _common_invalid_name_characters])
self.start_time = time.time()
Expand Down Expand Up @@ -267,7 +270,7 @@ def to_dict(self):
with required properties that have non-empty values.
"""
entity_dict = {}

for key, value in vars(self).items():
if isinstance(value, bool) or value:
if key == 'subsegments':
Expand All @@ -290,8 +293,8 @@ def to_dict(self):
elif key == 'metadata':
entity_dict[key] = metadata_to_dict(value)
elif key != 'sampled' and key != ORIGIN_TRACE_HEADER_ATTR_KEY:
entity_dict[key] = value
entity_dict[key] = value

return entity_dict

def _check_ended(self):
Expand Down
23 changes: 23 additions & 0 deletions aws_xray_sdk/core/models/noop_traceid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class NoOpTraceId:
"""
A trace ID tracks the path of a request through your application.
A trace collects all the segments generated by a single request.
A trace ID is required for a segment.
"""
VERSION = '1'
DELIMITER = '-'

def __init__(self):
"""
Generate a no-op trace id.
"""
self.start_time = '00000000'
self.__number = '000000000000000000000000'

def to_id(self):
"""
Convert TraceId object to a string.
"""
return "%s%s%s%s%s" % (NoOpTraceId.VERSION, NoOpTraceId.DELIMITER,
self.start_time,
NoOpTraceId.DELIMITER, self.__number)
85 changes: 85 additions & 0 deletions tests/test_traceid.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import os
import pytest
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core.models.traceid import TraceId


@pytest.fixture(autouse=True)
def cleanup():
"""
Clean up Environmental Variable for enable before and after tests
"""
if 'AWS_XRAY_NOOP_ID' in os.environ:
del os.environ['AWS_XRAY_NOOP_ID']
yield
if 'AWS_XRAY_NOOP_ID' in os.environ:
del os.environ['AWS_XRAY_NOOP_ID']


def test_id_format():
trace_id = TraceId().to_id()
assert len(trace_id) == 35
Expand All @@ -9,3 +24,73 @@ def test_id_format():
assert parts[0] == '1'
int(parts[1], 16)
int(parts[2], 16)


def test_id_generation_default_sampling_false():
segment = xray_recorder.begin_segment('segment_name', sampling=False)

# Start and end a subsegment
subsegment = xray_recorder.begin_subsegment('subsegment_name')
xray_recorder.end_subsegment()

# Close the segment
xray_recorder.end_segment()

assert segment.id == '0000000000000000'
assert segment.trace_id == '1-00000000-000000000000000000000000'
assert subsegment.id == '0000000000000000'
assert subsegment.trace_id == '1-00000000-000000000000000000000000'
assert subsegment.parent_id == '0000000000000000'


def test_id_generation_default_sampling_true():
segment = xray_recorder.begin_segment('segment_name', sampling=True)

# Start and end a subsegment
subsegment = xray_recorder.begin_subsegment('subsegment_name')
xray_recorder.end_subsegment()

# Close the segment
xray_recorder.end_segment()

assert segment.id != '0000000000000000'
assert segment.trace_id != '1-00000000-000000000000000000000000'
assert subsegment.id != '0000000000000000'
assert subsegment.trace_id != '1-00000000-000000000000000000000000'
assert subsegment.parent_id != '0000000000000000'


def test_id_generation_noop_true():
os.environ['AWS_XRAY_NOOP_ID'] = 'True'
segment = xray_recorder.begin_segment('segment_name', sampling=False)

# Start and end a subsegment
subsegment = xray_recorder.begin_subsegment('subsegment_name')
xray_recorder.end_subsegment()

# Close the segment
xray_recorder.end_segment()

assert segment.id == '0000000000000000'
assert segment.trace_id == '1-00000000-000000000000000000000000'
assert subsegment.id == '0000000000000000'
assert subsegment.trace_id == '1-00000000-000000000000000000000000'
assert subsegment.parent_id == '0000000000000000'


def test_id_generation_noop_false():
os.environ['AWS_XRAY_NOOP_ID'] = 'FALSE'
segment = xray_recorder.begin_segment('segment_name', sampling=False)

# Start and end a subsegment
subsegment = xray_recorder.begin_subsegment('subsegment_name')
xray_recorder.end_subsegment()

# Close the segment
xray_recorder.end_segment()

assert segment.id != '0000000000000000'
assert segment.trace_id != '1-00000000-000000000000000000000000'
assert subsegment.id != '0000000000000000'
assert subsegment.trace_id != '1-00000000-000000000000000000000000'
assert subsegment.parent_id != '0000000000000000'

0 comments on commit 5df17d3

Please sign in to comment.