Skip to content

Commit 8996e8b

Browse files
authored
Merge branch 'master' into fix-postgresql-register-type
2 parents 4060a56 + 41b776d commit 8996e8b

File tree

8 files changed

+265
-31
lines changed

8 files changed

+265
-31
lines changed

README.md

+60
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,40 @@ xray_recorder.configure(
6666

6767
### Start a custom segment/subsegment
6868

69+
Using context managers for implicit exceptions recording:
70+
71+
```python
72+
from aws_xray_sdk.core import xray_recorder
73+
74+
with xray_recorder.in_segment('segment_name') as segment:
75+
# Add metadata or annotation here if necessary
76+
segment.put_metadata('key', dict, 'namespace')
77+
with xray_recorder.in_subsegment('subsegment_name') as subsegment:
78+
subsegment.put_annotation('key', 'value')
79+
# Do something here
80+
with xray_recorder.in_subsegment('subsegment2') as subsegment:
81+
subsegment.put_annotation('key2', 'value2')
82+
# Do something else
83+
```
84+
85+
async versions of context managers:
86+
87+
```python
88+
from aws_xray_sdk.core import xray_recorder
89+
90+
async with xray_recorder.in_segment_async('segment_name') as segment:
91+
# Add metadata or annotation here if necessary
92+
segment.put_metadata('key', dict, 'namespace')
93+
async with xray_recorder.in_subsegment_async('subsegment_name') as subsegment:
94+
subsegment.put_annotation('key', 'value')
95+
# Do something here
96+
async with xray_recorder.in_subsegment_async('subsegment2') as subsegment:
97+
subsegment.put_annotation('key2', 'value2')
98+
# Do something else
99+
```
100+
101+
Default begin/end functions:
102+
69103
```python
70104
from aws_xray_sdk.core import xray_recorder
71105

@@ -85,6 +119,8 @@ xray_recorder.end_segment()
85119

86120
### Capture
87121

122+
As a decorator:
123+
88124
```python
89125
from aws_xray_sdk.core import xray_recorder
90126

@@ -95,6 +131,19 @@ def myfunc():
95131
myfunc()
96132
```
97133

134+
or as a context manager:
135+
136+
```python
137+
from aws_xray_sdk.core import xray_recorder
138+
139+
with xray_recorder.capture('subsegment_name') as subsegment:
140+
# Do something here
141+
subsegment.put_annotation('mykey', val)
142+
# Do something more
143+
```
144+
145+
Async capture as decorator:
146+
98147
```python
99148
from aws_xray_sdk.core import xray_recorder
100149

@@ -106,6 +155,17 @@ async def main():
106155
await myfunc()
107156
```
108157

158+
or as context manager:
159+
160+
```python
161+
from aws_xray_sdk.core import xray_recorder
162+
163+
async with xray_recorder.capture_async('subsegment_name') as subsegment:
164+
# Do something here
165+
subsegment.put_annotation('mykey', val)
166+
# Do something more
167+
```
168+
109169
### Adding annotations/metadata using recorder
110170

111171
```python

aws_xray_sdk/core/async_recorder.py

+46-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,37 @@
44

55
from aws_xray_sdk.core.recorder import AWSXRayRecorder
66
from aws_xray_sdk.core.utils import stacktrace
7+
from aws_xray_sdk.core.models.subsegment import SubsegmentContextManager
8+
from aws_xray_sdk.core.models.segment import SegmentContextManager
9+
10+
11+
class AsyncSegmentContextManager(SegmentContextManager):
12+
async def __aenter__(self):
13+
return self.__enter__()
14+
15+
async def __aexit__(self, exc_type, exc_val, exc_tb):
16+
return self.__exit__(exc_type, exc_val, exc_tb)
17+
18+
class AsyncSubsegmentContextManager(SubsegmentContextManager):
19+
20+
@wrapt.decorator
21+
async def __call__(self, wrapped, instance, args, kwargs):
22+
func_name = self.name
23+
if not func_name:
24+
func_name = wrapped.__name__
25+
26+
return await self.recorder.record_subsegment_async(
27+
wrapped, instance, args, kwargs,
28+
name=func_name,
29+
namespace='local',
30+
meta_processor=None,
31+
)
32+
33+
async def __aenter__(self):
34+
return self.__enter__()
35+
36+
async def __aexit__(self, exc_type, exc_val, exc_tb):
37+
return self.__exit__(exc_type, exc_val, exc_tb)
738

839

940
class AsyncAWSXRayRecorder(AWSXRayRecorder):
@@ -15,23 +46,25 @@ def capture_async(self, name=None):
1546
params str name: The name of the subsegment. If not specified
1647
the function name will be used.
1748
"""
49+
return self.in_subsegment_async(name=name)
1850

19-
@wrapt.decorator
20-
async def wrapper(wrapped, instance, args, kwargs):
21-
func_name = name
22-
if not func_name:
23-
func_name = wrapped.__name__
51+
def in_segment_async(self, name=None, **segment_kwargs):
52+
"""
53+
Return a segment async context manger.
2454
25-
result = await self.record_subsegment_async(
26-
wrapped, instance, args, kwargs,
27-
name=func_name,
28-
namespace='local',
29-
meta_processor=None,
30-
)
55+
:param str name: the name of the segment
56+
:param dict segment_kwargs: remaining arguments passed directly to `begin_segment`
57+
"""
58+
return AsyncSegmentContextManager(self, name=name, **segment_kwargs)
3159

32-
return result
60+
def in_subsegment_async(self, name=None, **subsegment_kwargs):
61+
"""
62+
Return a subsegment async context manger.
3363
34-
return wrapper
64+
:param str name: the name of the segment
65+
:param dict segment_kwargs: remaining arguments passed directly to `begin_segment`
66+
"""
67+
return AsyncSubsegmentContextManager(self, name=name, **subsegment_kwargs)
3568

3669
async def record_subsegment_async(self, wrapped, instance, args, kwargs, name,
3770
namespace, meta_processor):

aws_xray_sdk/core/models/segment.py

+32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import copy
2+
import traceback
23

34
from .entity import Entity
45
from .traceid import TraceId
@@ -8,6 +9,37 @@
89
ORIGIN_TRACE_HEADER_ATTR_KEY = '_origin_trace_header'
910

1011

12+
class SegmentContextManager:
13+
"""
14+
Wrapper for segment and recorder to provide segment context manager.
15+
"""
16+
17+
def __init__(self, recorder, name=None, **segment_kwargs):
18+
self.name = name
19+
self.segment_kwargs = segment_kwargs
20+
self.recorder = recorder
21+
self.segment = None
22+
23+
def __enter__(self):
24+
self.segment = self.recorder.begin_segment(
25+
name=self.name, **self.segment_kwargs)
26+
return self.segment
27+
28+
def __exit__(self, exc_type, exc_val, exc_tb):
29+
if self.segment is None:
30+
return
31+
32+
if exc_type is not None:
33+
self.segment.add_exception(
34+
exc_val,
35+
traceback.extract_tb(
36+
exc_tb,
37+
limit=self.recorder.max_trace_back,
38+
)
39+
)
40+
self.recorder.end_segment()
41+
42+
1143
class Segment(Entity):
1244
"""
1345
The compute resources running your application logic send data

aws_xray_sdk/core/models/subsegment.py

+47
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,56 @@
11
import copy
2+
import traceback
3+
4+
import wrapt
25

36
from .entity import Entity
47
from ..exceptions.exceptions import SegmentNotFoundException
58

69

10+
class SubsegmentContextManager:
11+
"""
12+
Wrapper for segment and recorder to provide segment context manager.
13+
"""
14+
15+
def __init__(self, recorder, name=None, **subsegment_kwargs):
16+
self.name = name
17+
self.subsegment_kwargs = subsegment_kwargs
18+
self.recorder = recorder
19+
self.subsegment = None
20+
21+
@wrapt.decorator
22+
def __call__(self, wrapped, instance, args, kwargs):
23+
func_name = self.name
24+
if not func_name:
25+
func_name = wrapped.__name__
26+
27+
return self.recorder.record_subsegment(
28+
wrapped, instance, args, kwargs,
29+
name=func_name,
30+
namespace='local',
31+
meta_processor=None,
32+
)
33+
34+
def __enter__(self):
35+
self.subsegment = self.recorder.begin_subsegment(
36+
name=self.name, **self.subsegment_kwargs)
37+
return self.subsegment
38+
39+
def __exit__(self, exc_type, exc_val, exc_tb):
40+
if self.subsegment is None:
41+
return
42+
43+
if exc_type is not None:
44+
self.subsegment.add_exception(
45+
exc_val,
46+
traceback.extract_tb(
47+
exc_tb,
48+
limit=self.recorder.max_trace_back,
49+
)
50+
)
51+
self.recorder.end_subsegment()
52+
53+
754
class Subsegment(Entity):
855
"""
956
The work done in a single segment can be broke down into subsegments.

aws_xray_sdk/core/recorder.py

+21-18
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
import platform
66
import time
77

8-
import wrapt
9-
108
from aws_xray_sdk.version import VERSION
11-
from .models.segment import Segment
12-
from .models.subsegment import Subsegment
9+
from .models.segment import Segment, SegmentContextManager
10+
from .models.subsegment import Subsegment, SubsegmentContextManager
1311
from .models.default_dynamic_naming import DefaultDynamicNaming
1412
from .models.dummy_entities import DummySegment, DummySubsegment
1513
from .emitters.udp_emitter import UDPEmitter
@@ -178,6 +176,24 @@ class to have your own implementation of the streaming process.
178176
self.sampler.load_settings(DaemonConfig(daemon_address),
179177
self.context, self._origin)
180178

179+
def in_segment(self, name=None, **segment_kwargs):
180+
"""
181+
Return a segment context manger.
182+
183+
:param str name: the name of the segment
184+
:param dict segment_kwargs: remaining arguments passed directly to `begin_segment`
185+
"""
186+
return SegmentContextManager(self, name=name, **segment_kwargs)
187+
188+
def in_subsegment(self, name=None, **subsegment_kwargs):
189+
"""
190+
Return a subsegment context manger.
191+
192+
:param str name: the name of the subsegment
193+
:param dict segment_kwargs: remaining arguments passed directly to `begin_subsegment`
194+
"""
195+
return SubsegmentContextManager(self, name=name, **subsegment_kwargs)
196+
181197
def begin_segment(self, name=None, traceid=None,
182198
parent_id=None, sampling=None):
183199
"""
@@ -369,20 +385,7 @@ def capture(self, name=None):
369385
params str name: The name of the subsegment. If not specified
370386
the function name will be used.
371387
"""
372-
@wrapt.decorator
373-
def wrapper(wrapped, instance, args, kwargs):
374-
func_name = name
375-
if not func_name:
376-
func_name = wrapped.__name__
377-
378-
return self.record_subsegment(
379-
wrapped, instance, args, kwargs,
380-
name=func_name,
381-
namespace='local',
382-
meta_processor=None,
383-
)
384-
385-
return wrapper
388+
return self.in_subsegment(name=name)
386389

387390
def record_subsegment(self, wrapped, instance, args, kwargs, name,
388391
namespace, meta_processor):

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
description='The AWS X-Ray SDK for Python (the SDK) enables Python developers to record'
2020
' and emit information from within their applications to the AWS X-Ray service.',
2121
long_description=long_description,
22+
long_description_content_type='text/markdown',
2223

2324
url='https://github.com/aws/aws-xray-sdk-python',
2425

tests/test_async_recorder.py

+13
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,16 @@ async def test_capture(loop):
4242
service = segment.service
4343
assert platform.python_implementation() == service.get('runtime')
4444
assert platform.python_version() == service.get('runtime_version')
45+
46+
47+
async def test_async_context_managers(loop):
48+
xray_recorder.configure(service='test', sampling=False, context=AsyncContext(loop=loop))
49+
50+
async with xray_recorder.in_segment_async('segment') as segment:
51+
async with xray_recorder.capture_async('aio_capture') as subsegment:
52+
assert segment.subsegments[0].name == 'aio_capture'
53+
assert subsegment.in_progress is False
54+
async with xray_recorder.in_subsegment_async('in_sub') as subsegment:
55+
assert segment.subsegments[1].name == 'in_sub'
56+
assert subsegment.in_progress is True
57+
assert subsegment.in_progress is False

0 commit comments

Comments
 (0)