Skip to content

Commit

Permalink
GA 1.0 release commit
Browse files Browse the repository at this point in the history
1.0 release commit
  • Loading branch information
haotianw465 committed Apr 19, 2018
1 parent f8fb03b commit ac8eb47
Show file tree
Hide file tree
Showing 37 changed files with 1,549 additions and 448 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
CHANGELOG
=========

1.0
===
* Changed development status to `5 - Production/Stable` and removed beta tag.
* feature: Added S3 API parameters to the default whitelist.
* feature: Added new recorder APIs to add annotations/metadata.
* feature: The recorder now adds more runtime and version information to sampled segments.
* feature: Django, Flask and Aiohttp middleware now inject trace header to response headers.
* feature: Added a new API to configure maximum captured stack trace.
* feature: Modularized subsegments streaming logic and now it can be overriden with custom implementation.
* bugfix(**Breaking**): Subsegment `set_user` API is removed since this attribute is not supported by X-Ray back-end.
* bugfix: Fixed an issue where arbitrary fields in trace header being dropped when calling downstream.
* bugfix: Fixed a compatibility issue between botocore and httplib patcher. `ISSUE48 <https://github.com/aws/aws-xray-sdk-python/issues/48>`_.
* bugfix: Fixed a typo in sqlalchemy decorators. `PR50 <https://github.com/aws/aws-xray-sdk-python/pull/50>`_.
* Updated `README` with more usage examples.

0.97
====
* feature: Support aiohttp client tracing for aiohttp 3.x. `PR42 <https://github.com/aws/aws-xray-sdk-python/pull/42>`_.
Expand Down
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AWS X-Ray SDK for Python <sup><sup><sup>(beta)</sup></sup></sup>
# AWS X-Ray SDK for Python

![Screenshot of the AWS X-Ray console](/images/example_servicemap.png?raw=true)

Expand Down Expand Up @@ -104,6 +104,31 @@ async def main():
await myfunc()
```

### Adding annotations/metadata using recorder

```python
from aws_xray_sdk.core import xray_recorder

# Start a segment if no segment exist
segment1 = xray_recorder.begin_segment('segment_name')

# This will add the key value pair to segment1 as it is active
xray_recorder.put_annotation('key', 'value')

# Start a subsegment so it becomes the active trace entity
subsegment1 = xray_recorder.begin_subsegment('subsegment_name')

# This will add the key value pair to subsegment1 as it is active
xray_recorder.put_metadata('key', 'value')

if xray_recorder.is_sampled():
# some expensitve annotations/metadata generation code here
val = compute_annotation_val()
metadata = compute_metadata_body()
xray_recorder.put_annotation('mykey', val)
xray_recorder.put_metadata('mykey', metadata)
```

### Trace AWS Lambda functions

```python
Expand All @@ -123,6 +148,47 @@ def lambda_handler(event, context):
# ... some other code
```

### Trace ThreadPoolExecutor

```python
import concurrent.futures

import requests

from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch

patch(('requests',))

URLS = ['http://www.amazon.com/',
'http://aws.amazon.com/',
'http://example.com/',
'http://www.bilibili.com/',
'http://invalid-domain.com/']

def load_url(url, trace_entity):
# Set the parent X-Ray entity for the worker thread.
xray_recorder.set_trace_entity(trace_entity)
# Subsegment captured from the following HTTP GET will be
# a child of parent entity passed from the main thread.
resp = requests.get(url)
# prevent thread pollution
xray_recorder.clear_trace_entities()
return resp

# Get the current active segment or subsegment from the main thread.
current_entity = xray_recorder.get_trace_entity()
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Pass the active entity from main thread to worker threads.
future_to_url = {executor.submit(load_url, url, current_entity): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception:
pass
```

### Patch third-party libraries

```python
Expand Down
12 changes: 6 additions & 6 deletions aws_xray_sdk/core/models/dummy_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ def set_user(self, user):
"""
pass

def set_service(self, service_info):
"""
No-op
"""
pass

def apply_status_code(self, status_code):
"""
No-op
Expand Down Expand Up @@ -107,12 +113,6 @@ def set_sql(self, sql):
"""
pass

def set_user(self, user):
"""
No-op
"""
pass

def apply_status_code(self, status_code):
"""
No-op
Expand Down
15 changes: 0 additions & 15 deletions aws_xray_sdk/core/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class Entity(object):
The parent class for segment/subsegment. It holds common properties
and methods on segment and subsegment.
"""

def __init__(self, name):

# required attributes
Expand Down Expand Up @@ -54,8 +53,6 @@ def __init__(self, name):
# list is thread-safe
self.subsegments = []

self.user = None

def close(self, end_time=None):
"""
Close the trace entity by setting `end_time`
Expand Down Expand Up @@ -84,7 +81,6 @@ def remove_subsegment(self, subsegment):
"""
Remove input subsegment from child subsegments.
"""
self._check_ended()
self.subsegments.remove(subsegment)

def put_http_meta(self, key, value):
Expand Down Expand Up @@ -182,15 +178,6 @@ def set_aws(self, aws_meta):
self._check_ended()
self.aws = aws_meta

def set_user(self, user):
"""
set user of an segment or subsegment.
one segment or subsegment can only hold one user.
User is indexed and can be later queried.
"""
self._check_ended()
self.user = user

def add_throttle_flag(self):
self.throttle = True

Expand Down Expand Up @@ -271,8 +258,6 @@ def _delete_empty_properties(self, properties):
del properties['annotations']
if not self.metadata:
del properties['metadata']
if not self.user:
del properties['user']

del properties['sampled']

Expand Down
36 changes: 35 additions & 1 deletion aws_xray_sdk/core/models/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from ..utils.atomic_counter import AtomicCounter
from ..exceptions.exceptions import SegmentNameMissingException

ORIGIN_TRACE_HEADER_ATTR_KEY = '_origin_trace_header'


class Segment(Entity):
"""
Expand Down Expand Up @@ -39,6 +41,7 @@ def __init__(self, name, entityid=None, traceid=None,

self.in_progress = True
self.sampled = sampled
self.user = None
self.ref_counter = AtomicCounter()
self._subsegments_counter = AtomicCounter()

Expand Down Expand Up @@ -95,13 +98,44 @@ def remove_subsegment(self, subsegment):
super(Segment, self).remove_subsegment(subsegment)
self.decrement_subsegments_size()

def set_user(self, user):
"""
set user of a segment. One segment can only have one user.
User is indexed and can be later queried.
"""
super(Segment, self)._check_ended()
self.user = user

def set_service(self, service_info):
"""
Add python runtime and version info.
This method should be only used by the recorder.
"""
self.service = service_info

def save_origin_trace_header(self, trace_header):
"""
Temporarily store additional data fields in trace header
to the segment for later propagation. The data will be
cleaned up upon serilaization.
"""
setattr(self, ORIGIN_TRACE_HEADER_ATTR_KEY, trace_header)

def get_origin_trace_header(self):
"""
Retrieve saved trace header data.
"""
return getattr(self, ORIGIN_TRACE_HEADER_ATTR_KEY, None)

def __getstate__(self):
"""
Used by jsonpikle to remove unwanted fields.
"""
properties = copy.copy(self.__dict__)
super(Segment, self)._delete_empty_properties(properties)

if not self.user:
del properties['user']
del properties['ref_counter']
del properties['_subsegments_counter']
properties.pop(ORIGIN_TRACE_HEADER_ATTR_KEY, None)
return properties
43 changes: 34 additions & 9 deletions aws_xray_sdk/core/models/trace_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ROOT = 'Root'
PARENT = 'Parent'
SAMPLE = 'Sampled'
SELF = 'Self'

HEADER_DELIMITER = ";"

Expand All @@ -17,18 +18,25 @@ class TraceHeader(object):
by the X-Ray SDK and included in the response. Learn more about
`Tracing Header <http://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader>`_.
"""
def __init__(self, root=None, parent=None, sampled=None):
def __init__(self, root=None, parent=None, sampled=None, data=None):
"""
:param str root: trace id
:param str parent: parent id
:param int sampled: 0 means not sampled, 1 means sampled
:param dict data: arbitrary data fields
"""
self._root = root
self._parent = parent
self._sampled = None
self._data = data

if sampled is not None:
self._sampled = int(sampled)
if sampled == '?':
self._sampled = sampled
if sampled is True or sampled == '1' or sampled == 1:
self._sampled = 1
if sampled is False or sampled == '0' or sampled == 0:
self._sampled = 0

@classmethod
def from_header_str(cls, header):
Expand All @@ -42,15 +50,22 @@ def from_header_str(cls, header):
try:
params = header.strip().split(HEADER_DELIMITER)
header_dict = {}
data = {}

for param in params:
entry = param.split('=')
header_dict[entry[0]] = entry[1]
key = entry[0]
if key in (ROOT, PARENT, SAMPLE):
header_dict[key] = entry[1]
# Ignore any "Self=" trace ids injected from ALB.
elif key != SELF:
data[key] = entry[1]

return cls(
root=header_dict.get(ROOT, None),
parent=header_dict.get(PARENT, None),
sampled=header_dict.get(SAMPLE, None),
data=data,
)

except Exception:
Expand All @@ -62,15 +77,18 @@ def to_header_str(self):
Convert to a tracing header string that can be injected to
outgoing http request headers.
"""
h_str = ''
h_parts = []
if self.root:
h_str = ROOT + '=' + self.root
h_parts.append(ROOT + '=' + self.root)
if self.parent:
h_str = h_str + HEADER_DELIMITER + PARENT + '=' + self.parent
h_parts.append(PARENT + '=' + self.parent)
if self.sampled is not None:
h_str = h_str + HEADER_DELIMITER + SAMPLE + '=' + str(self.sampled)
h_parts.append(SAMPLE + '=' + str(self.sampled))
if self.data:
for key in self.data:
h_parts.append(key + '=' + self.data[key])

return h_str
return HEADER_DELIMITER.join(h_parts)

@property
def root(self):
Expand All @@ -90,6 +108,13 @@ def parent(self):
def sampled(self):
"""
Return the sampling decision in the header.
It's either 0 or 1.
It's 0 or 1 or '?'.
"""
return self._sampled

@property
def data(self):
"""
Return the arbitrary fields in the trace header.
"""
return self._data
Loading

0 comments on commit ac8eb47

Please sign in to comment.