-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from terrycain/aiobotocore
Add support for aiobotocore
- Loading branch information
Showing
28 changed files
with
515 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
include aws_xray_sdk/ext/botocore/*.json | ||
include aws_xray_sdk/ext/resources/*.json | ||
include aws_xray_sdk/core/sampling/*.json | ||
include README.md | ||
include LICENSE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import time | ||
import traceback | ||
|
||
import wrapt | ||
|
||
from aws_xray_sdk.core.recorder import AWSXRayRecorder | ||
|
||
|
||
class AsyncAWSXRayRecorder(AWSXRayRecorder): | ||
def capture_async(self, name=None): | ||
""" | ||
A decorator that records enclosed function in a subsegment. | ||
It only works with asynchronous functions. | ||
params str name: The name of the subsegment. If not specified | ||
the function name will be used. | ||
""" | ||
|
||
@wrapt.decorator | ||
async def wrapper(wrapped, instance, args, kwargs): | ||
func_name = name | ||
if not func_name: | ||
func_name = wrapped.__name__ | ||
|
||
result = await self.record_subsegment_async( | ||
wrapped, instance, args, kwargs, | ||
name=func_name, | ||
namespace='local', | ||
meta_processor=None, | ||
) | ||
|
||
return result | ||
|
||
return wrapper | ||
|
||
async def record_subsegment_async(self, wrapped, instance, args, kwargs, name, | ||
namespace, meta_processor): | ||
|
||
subsegment = self.begin_subsegment(name, namespace) | ||
|
||
exception = None | ||
stack = None | ||
return_value = None | ||
|
||
try: | ||
return_value = await wrapped(*args, **kwargs) | ||
return return_value | ||
except Exception as e: | ||
exception = e | ||
stack = traceback.extract_stack(limit=self._max_trace_back) | ||
raise | ||
finally: | ||
end_time = time.time() | ||
if callable(meta_processor): | ||
meta_processor( | ||
wrapped=wrapped, | ||
instance=instance, | ||
args=args, | ||
kwargs=kwargs, | ||
return_value=return_value, | ||
exception=exception, | ||
subsegment=subsegment, | ||
stack=stack, | ||
) | ||
elif exception: | ||
if subsegment: | ||
subsegment.add_exception(exception, stack) | ||
|
||
self.end_subsegment(end_time) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .patch import patch | ||
|
||
__all__ = ['patch'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import aiobotocore.client | ||
import wrapt | ||
|
||
from aws_xray_sdk.core import xray_recorder | ||
from aws_xray_sdk.ext.boto_utils import inject_header, aws_meta_processor | ||
|
||
|
||
def patch(): | ||
""" | ||
Patch aiobotocore client so it generates subsegments | ||
when calling AWS services. | ||
""" | ||
if hasattr(aiobotocore.client, '_xray_enabled'): | ||
return | ||
setattr(aiobotocore.client, '_xray_enabled', True) | ||
|
||
wrapt.wrap_function_wrapper( | ||
'aiobotocore.client', | ||
'AioBaseClient._make_api_call', | ||
_xray_traced_aiobotocore, | ||
) | ||
|
||
wrapt.wrap_function_wrapper( | ||
'aiobotocore.endpoint', | ||
'AioEndpoint._encode_headers', | ||
inject_header, | ||
) | ||
|
||
|
||
async def _xray_traced_aiobotocore(wrapped, instance, args, kwargs): | ||
service = instance._service_model.metadata["endpointPrefix"] | ||
result = await xray_recorder.record_subsegment_async( | ||
wrapped, instance, args, kwargs, | ||
name=service, | ||
namespace='aws', | ||
meta_processor=aws_meta_processor, | ||
) | ||
|
||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
from __future__ import absolute_import | ||
# Need absolute import as botocore is also in the current folder for py27 | ||
import json | ||
|
||
from pkg_resources import resource_filename | ||
from botocore.exceptions import ClientError | ||
|
||
from aws_xray_sdk.core import xray_recorder | ||
from aws_xray_sdk.core.models import http | ||
|
||
from aws_xray_sdk.ext.util import inject_trace_header, to_snake_case | ||
|
||
|
||
with open(resource_filename(__name__, 'resources/aws_para_whitelist.json'), 'r') as data_file: | ||
whitelist = json.load(data_file) | ||
|
||
|
||
def inject_header(wrapped, instance, args, kwargs): | ||
headers = args[0] | ||
inject_trace_header(headers, xray_recorder.current_subsegment()) | ||
return wrapped(*args, **kwargs) | ||
|
||
|
||
def aws_meta_processor(wrapped, instance, args, kwargs, | ||
return_value, exception, subsegment, stack): | ||
region = instance.meta.region_name | ||
|
||
if 'operation_name' in kwargs: | ||
operation_name = kwargs['operation_name'] | ||
else: | ||
operation_name = args[0] | ||
|
||
aws_meta = { | ||
'operation': operation_name, | ||
'region': region, | ||
} | ||
|
||
if return_value: | ||
resp_meta = return_value.get('ResponseMetadata') | ||
if resp_meta: | ||
aws_meta['request_id'] = resp_meta.get('RequestId') | ||
subsegment.put_http_meta(http.STATUS, | ||
resp_meta.get('HTTPStatusCode')) | ||
# for service like S3 that returns special request id in response headers | ||
if 'HTTPHeaders' in resp_meta and resp_meta['HTTPHeaders'].get('x-amz-id-2'): | ||
aws_meta['id_2'] = resp_meta['HTTPHeaders']['x-amz-id-2'] | ||
|
||
elif exception: | ||
_aws_error_handler(exception, stack, subsegment, aws_meta) | ||
|
||
_extract_whitelisted_params(subsegment.name, operation_name, | ||
aws_meta, args, kwargs, return_value) | ||
|
||
subsegment.set_aws(aws_meta) | ||
|
||
|
||
def _aws_error_handler(exception, stack, subsegment, aws_meta): | ||
|
||
if not exception or not isinstance(exception, ClientError): | ||
return | ||
|
||
response_metadata = exception.response.get('ResponseMetadata') | ||
|
||
if not response_metadata: | ||
return | ||
|
||
aws_meta['request_id'] = response_metadata.get('RequestId') | ||
|
||
status_code = response_metadata.get('HTTPStatusCode') | ||
|
||
subsegment.put_http_meta(http.STATUS, status_code) | ||
if status_code == 429: | ||
subsegment.add_throttle_flag() | ||
if status_code / 100 == 4: | ||
subsegment.add_error_flag() | ||
|
||
subsegment.add_exception(exception, stack, True) | ||
|
||
|
||
def _extract_whitelisted_params(service, operation, | ||
aws_meta, args, kwargs, response): | ||
|
||
# check if service is whitelisted | ||
if service not in whitelist['services']: | ||
return | ||
operations = whitelist['services'][service]['operations'] | ||
|
||
# check if operation is whitelisted | ||
if operation not in operations: | ||
return | ||
params = operations[operation] | ||
|
||
# record whitelisted request/response parameters | ||
if 'request_parameters' in params: | ||
_record_params(params['request_parameters'], args[1], aws_meta) | ||
|
||
if 'request_descriptors' in params: | ||
_record_special_params(params['request_descriptors'], | ||
args[1], aws_meta) | ||
|
||
if 'response_parameters' in params and response: | ||
_record_params(params['response_parameters'], response, aws_meta) | ||
|
||
if 'response_descriptors' in params and response: | ||
_record_special_params(params['response_descriptors'], | ||
response, aws_meta) | ||
|
||
|
||
def _record_params(whitelisted, actual, aws_meta): | ||
|
||
for key in whitelisted: | ||
if key in actual: | ||
snake_key = to_snake_case(key) | ||
aws_meta[snake_key] = actual[key] | ||
|
||
|
||
def _record_special_params(whitelisted, actual, aws_meta): | ||
|
||
for key in whitelisted: | ||
if key in actual: | ||
_process_descriptor(whitelisted[key], actual[key], aws_meta) | ||
|
||
|
||
def _process_descriptor(descriptor, value, aws_meta): | ||
|
||
# "get_count" = true | ||
if 'get_count' in descriptor and descriptor['get_count']: | ||
value = len(value) | ||
|
||
# "get_keys" = true | ||
if 'get_keys' in descriptor and descriptor['get_keys']: | ||
value = value.keys() | ||
|
||
aws_meta[descriptor['rename_to']] = value |
Oops, something went wrong.