Skip to content

Commit ac8eb47

Browse files
committed
GA 1.0 release commit
1.0 release commit
1 parent f8fb03b commit ac8eb47

37 files changed

+1549
-448
lines changed

CHANGELOG.rst

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
CHANGELOG
33
=========
44

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

README.md

+67-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# AWS X-Ray SDK for Python <sup><sup><sup>(beta)</sup></sup></sup>
1+
# AWS X-Ray SDK for Python
22

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

@@ -104,6 +104,31 @@ async def main():
104104
await myfunc()
105105
```
106106

107+
### Adding annotations/metadata using recorder
108+
109+
```python
110+
from aws_xray_sdk.core import xray_recorder
111+
112+
# Start a segment if no segment exist
113+
segment1 = xray_recorder.begin_segment('segment_name')
114+
115+
# This will add the key value pair to segment1 as it is active
116+
xray_recorder.put_annotation('key', 'value')
117+
118+
# Start a subsegment so it becomes the active trace entity
119+
subsegment1 = xray_recorder.begin_subsegment('subsegment_name')
120+
121+
# This will add the key value pair to subsegment1 as it is active
122+
xray_recorder.put_metadata('key', 'value')
123+
124+
if xray_recorder.is_sampled():
125+
# some expensitve annotations/metadata generation code here
126+
val = compute_annotation_val()
127+
metadata = compute_metadata_body()
128+
xray_recorder.put_annotation('mykey', val)
129+
xray_recorder.put_metadata('mykey', metadata)
130+
```
131+
107132
### Trace AWS Lambda functions
108133

109134
```python
@@ -123,6 +148,47 @@ def lambda_handler(event, context):
123148
# ... some other code
124149
```
125150

151+
### Trace ThreadPoolExecutor
152+
153+
```python
154+
import concurrent.futures
155+
156+
import requests
157+
158+
from aws_xray_sdk.core import xray_recorder
159+
from aws_xray_sdk.core import patch
160+
161+
patch(('requests',))
162+
163+
URLS = ['http://www.amazon.com/',
164+
'http://aws.amazon.com/',
165+
'http://example.com/',
166+
'http://www.bilibili.com/',
167+
'http://invalid-domain.com/']
168+
169+
def load_url(url, trace_entity):
170+
# Set the parent X-Ray entity for the worker thread.
171+
xray_recorder.set_trace_entity(trace_entity)
172+
# Subsegment captured from the following HTTP GET will be
173+
# a child of parent entity passed from the main thread.
174+
resp = requests.get(url)
175+
# prevent thread pollution
176+
xray_recorder.clear_trace_entities()
177+
return resp
178+
179+
# Get the current active segment or subsegment from the main thread.
180+
current_entity = xray_recorder.get_trace_entity()
181+
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
182+
# Pass the active entity from main thread to worker threads.
183+
future_to_url = {executor.submit(load_url, url, current_entity): url for url in URLS}
184+
for future in concurrent.futures.as_completed(future_to_url):
185+
url = future_to_url[future]
186+
try:
187+
data = future.result()
188+
except Exception:
189+
pass
190+
```
191+
126192
### Patch third-party libraries
127193

128194
```python

aws_xray_sdk/core/models/dummy_entities.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ def set_user(self, user):
4646
"""
4747
pass
4848

49+
def set_service(self, service_info):
50+
"""
51+
No-op
52+
"""
53+
pass
54+
4955
def apply_status_code(self, status_code):
5056
"""
5157
No-op
@@ -107,12 +113,6 @@ def set_sql(self, sql):
107113
"""
108114
pass
109115

110-
def set_user(self, user):
111-
"""
112-
No-op
113-
"""
114-
pass
115-
116116
def apply_status_code(self, status_code):
117117
"""
118118
No-op

aws_xray_sdk/core/models/entity.py

-15
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class Entity(object):
2424
The parent class for segment/subsegment. It holds common properties
2525
and methods on segment and subsegment.
2626
"""
27-
2827
def __init__(self, name):
2928

3029
# required attributes
@@ -54,8 +53,6 @@ def __init__(self, name):
5453
# list is thread-safe
5554
self.subsegments = []
5655

57-
self.user = None
58-
5956
def close(self, end_time=None):
6057
"""
6158
Close the trace entity by setting `end_time`
@@ -84,7 +81,6 @@ def remove_subsegment(self, subsegment):
8481
"""
8582
Remove input subsegment from child subsegments.
8683
"""
87-
self._check_ended()
8884
self.subsegments.remove(subsegment)
8985

9086
def put_http_meta(self, key, value):
@@ -182,15 +178,6 @@ def set_aws(self, aws_meta):
182178
self._check_ended()
183179
self.aws = aws_meta
184180

185-
def set_user(self, user):
186-
"""
187-
set user of an segment or subsegment.
188-
one segment or subsegment can only hold one user.
189-
User is indexed and can be later queried.
190-
"""
191-
self._check_ended()
192-
self.user = user
193-
194181
def add_throttle_flag(self):
195182
self.throttle = True
196183

@@ -271,8 +258,6 @@ def _delete_empty_properties(self, properties):
271258
del properties['annotations']
272259
if not self.metadata:
273260
del properties['metadata']
274-
if not self.user:
275-
del properties['user']
276261

277262
del properties['sampled']
278263

aws_xray_sdk/core/models/segment.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from ..utils.atomic_counter import AtomicCounter
66
from ..exceptions.exceptions import SegmentNameMissingException
77

8+
ORIGIN_TRACE_HEADER_ATTR_KEY = '_origin_trace_header'
9+
810

911
class Segment(Entity):
1012
"""
@@ -39,6 +41,7 @@ def __init__(self, name, entityid=None, traceid=None,
3941

4042
self.in_progress = True
4143
self.sampled = sampled
44+
self.user = None
4245
self.ref_counter = AtomicCounter()
4346
self._subsegments_counter = AtomicCounter()
4447

@@ -95,13 +98,44 @@ def remove_subsegment(self, subsegment):
9598
super(Segment, self).remove_subsegment(subsegment)
9699
self.decrement_subsegments_size()
97100

101+
def set_user(self, user):
102+
"""
103+
set user of a segment. One segment can only have one user.
104+
User is indexed and can be later queried.
105+
"""
106+
super(Segment, self)._check_ended()
107+
self.user = user
108+
109+
def set_service(self, service_info):
110+
"""
111+
Add python runtime and version info.
112+
This method should be only used by the recorder.
113+
"""
114+
self.service = service_info
115+
116+
def save_origin_trace_header(self, trace_header):
117+
"""
118+
Temporarily store additional data fields in trace header
119+
to the segment for later propagation. The data will be
120+
cleaned up upon serilaization.
121+
"""
122+
setattr(self, ORIGIN_TRACE_HEADER_ATTR_KEY, trace_header)
123+
124+
def get_origin_trace_header(self):
125+
"""
126+
Retrieve saved trace header data.
127+
"""
128+
return getattr(self, ORIGIN_TRACE_HEADER_ATTR_KEY, None)
129+
98130
def __getstate__(self):
99131
"""
100132
Used by jsonpikle to remove unwanted fields.
101133
"""
102134
properties = copy.copy(self.__dict__)
103135
super(Segment, self)._delete_empty_properties(properties)
104-
136+
if not self.user:
137+
del properties['user']
105138
del properties['ref_counter']
106139
del properties['_subsegments_counter']
140+
properties.pop(ORIGIN_TRACE_HEADER_ATTR_KEY, None)
107141
return properties

aws_xray_sdk/core/models/trace_header.py

+34-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
ROOT = 'Root'
66
PARENT = 'Parent'
77
SAMPLE = 'Sampled'
8+
SELF = 'Self'
89

910
HEADER_DELIMITER = ";"
1011

@@ -17,18 +18,25 @@ class TraceHeader(object):
1718
by the X-Ray SDK and included in the response. Learn more about
1819
`Tracing Header <http://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader>`_.
1920
"""
20-
def __init__(self, root=None, parent=None, sampled=None):
21+
def __init__(self, root=None, parent=None, sampled=None, data=None):
2122
"""
2223
:param str root: trace id
2324
:param str parent: parent id
2425
:param int sampled: 0 means not sampled, 1 means sampled
26+
:param dict data: arbitrary data fields
2527
"""
2628
self._root = root
2729
self._parent = parent
2830
self._sampled = None
31+
self._data = data
2932

3033
if sampled is not None:
31-
self._sampled = int(sampled)
34+
if sampled == '?':
35+
self._sampled = sampled
36+
if sampled is True or sampled == '1' or sampled == 1:
37+
self._sampled = 1
38+
if sampled is False or sampled == '0' or sampled == 0:
39+
self._sampled = 0
3240

3341
@classmethod
3442
def from_header_str(cls, header):
@@ -42,15 +50,22 @@ def from_header_str(cls, header):
4250
try:
4351
params = header.strip().split(HEADER_DELIMITER)
4452
header_dict = {}
53+
data = {}
4554

4655
for param in params:
4756
entry = param.split('=')
48-
header_dict[entry[0]] = entry[1]
57+
key = entry[0]
58+
if key in (ROOT, PARENT, SAMPLE):
59+
header_dict[key] = entry[1]
60+
# Ignore any "Self=" trace ids injected from ALB.
61+
elif key != SELF:
62+
data[key] = entry[1]
4963

5064
return cls(
5165
root=header_dict.get(ROOT, None),
5266
parent=header_dict.get(PARENT, None),
5367
sampled=header_dict.get(SAMPLE, None),
68+
data=data,
5469
)
5570

5671
except Exception:
@@ -62,15 +77,18 @@ def to_header_str(self):
6277
Convert to a tracing header string that can be injected to
6378
outgoing http request headers.
6479
"""
65-
h_str = ''
80+
h_parts = []
6681
if self.root:
67-
h_str = ROOT + '=' + self.root
82+
h_parts.append(ROOT + '=' + self.root)
6883
if self.parent:
69-
h_str = h_str + HEADER_DELIMITER + PARENT + '=' + self.parent
84+
h_parts.append(PARENT + '=' + self.parent)
7085
if self.sampled is not None:
71-
h_str = h_str + HEADER_DELIMITER + SAMPLE + '=' + str(self.sampled)
86+
h_parts.append(SAMPLE + '=' + str(self.sampled))
87+
if self.data:
88+
for key in self.data:
89+
h_parts.append(key + '=' + self.data[key])
7290

73-
return h_str
91+
return HEADER_DELIMITER.join(h_parts)
7492

7593
@property
7694
def root(self):
@@ -90,6 +108,13 @@ def parent(self):
90108
def sampled(self):
91109
"""
92110
Return the sampling decision in the header.
93-
It's either 0 or 1.
111+
It's 0 or 1 or '?'.
94112
"""
95113
return self._sampled
114+
115+
@property
116+
def data(self):
117+
"""
118+
Return the arbitrary fields in the trace header.
119+
"""
120+
return self._data

0 commit comments

Comments
 (0)