Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no op trace id generation #293

Merged
merged 6 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'