diff --git a/README.md b/README.md index 2eaa5e37..b4328eb8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/aws_xray_sdk/core/models/dummy_entities.py b/aws_xray_sdk/core/models/dummy_entities.py index 14642402..9e4a0379 100644 --- a/aws_xray_sdk/core/models/dummy_entities.py +++ b/aws_xray_sdk/core/models/dummy_entities.py @@ -1,3 +1,5 @@ +import os +from .noop_traceid import NoOpTraceId from .traceid import TraceId from .segment import Segment from .subsegment import Subsegment @@ -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): @@ -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): diff --git a/aws_xray_sdk/core/models/entity.py b/aws_xray_sdk/core/models/entity.py index 38fbe10b..41ef3893 100644 --- a/aws_xray_sdk/core/models/entity.py +++ b/aws_xray_sdk/core/models/entity.py @@ -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 @@ -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() @@ -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': @@ -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): diff --git a/aws_xray_sdk/core/models/noop_traceid.py b/aws_xray_sdk/core/models/noop_traceid.py new file mode 100644 index 00000000..e7496210 --- /dev/null +++ b/aws_xray_sdk/core/models/noop_traceid.py @@ -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) diff --git a/tests/test_traceid.py b/tests/test_traceid.py index 31361ad4..0b5878ba 100644 --- a/tests/test_traceid.py +++ b/tests/test_traceid.py @@ -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 @@ -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'