Skip to content

Commit

Permalink
[issue-202] extension support as Bottle plugin (#204)
Browse files Browse the repository at this point in the history
* [iss-202] extension support as Bottle plugin

* [iss-202] extension support as Bottle plugin

* [iss-202] extension support as Bottle plugin

Co-authored-by: Bhautik Pipaliya <[email protected]>
  • Loading branch information
bhautikpip authored Mar 4, 2020
1 parent b47f1e8 commit a839d7f
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 1 deletion.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,19 @@ xray_recorder.configure(service='fallback_name', dynamic_naming='*mysite.com*')
XRayMiddleware(app, xray_recorder)
```

### Serverless Support for Flask & Django Using X-Ray
### Add Bottle middleware(plugin)

```python
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.ext.bottle.middleware import XRayMiddleware

app = Bottle()

xray_recorder.configure(service='fallback_name', dynamic_naming='*mysite.com*')
app.install(XRayMiddleware(xray_recorder))
```

### Serverless Support for Flask & Django & Bottle Using X-Ray
Serverless is an application model that enables you to shift more of your operational responsibilities to AWS. As a result, you can focus only on your applications and services, instead of the infrastructure management tasks such as server provisioning, patching, operating system maintenance, and capacity provisioning. With serverless, you can deploy your web application to [AWS Lambda](https://aws.amazon.com/lambda/) and have customers interact with it through a Lambda-invoking endpoint, such as [Amazon API Gateway](https://aws.amazon.com/api-gateway/).

X-Ray supports the Serverless model out of the box and requires no extra configuration. The middlewares in Lambda generate `Subsegments` instead of `Segments` when an endpoint is reached. This is because `Segments` cannot be generated inside the Lambda function, but it is generated automatically by the Lambda container. Therefore, when using the middlewares with this model, it is important to make sure that your methods only generate `Subsegments`.
Expand Down
Empty file.
111 changes: 111 additions & 0 deletions aws_xray_sdk/ext/bottle/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from bottle import request, response, SimpleTemplate

from aws_xray_sdk.core.lambda_launcher import check_in_lambda, LambdaContext
from aws_xray_sdk.core.models import http
from aws_xray_sdk.core.utils import stacktrace
from aws_xray_sdk.ext.util import calculate_sampling_decision, \
calculate_segment_name, construct_xray_header, prepare_response_header


class XRayMiddleware(object):
"""
Middleware that wraps each incoming request to a segment.
"""
name = 'xray'
api = 2

def __init__(self, recorder):
self._recorder = recorder
self._in_lambda_ctx = False

if check_in_lambda() and type(self._recorder.context) == LambdaContext:
self._in_lambda_ctx = True

_patch_render(recorder)

def apply(self, callback, route):
"""
Apply middleware directly to each route callback.
"""
def wrapper(*a, **ka):
headers = request.headers
xray_header = construct_xray_header(headers)
name = calculate_segment_name(request.urlparts[1], self._recorder)

sampling_req = {
'host': request.urlparts[1],
'method': request.method,
'path': request.path,
'service': name,
}
sampling_decision = calculate_sampling_decision(
trace_header=xray_header,
recorder=self._recorder,
sampling_req=sampling_req,
)

if self._in_lambda_ctx:
segment = self._recorder.begin_subsegment(name)
else:
segment = self._recorder.begin_segment(
name=name,
traceid=xray_header.root,
parent_id=xray_header.parent,
sampling=sampling_decision,
)

segment.save_origin_trace_header(xray_header)
segment.put_http_meta(http.URL, request.url)
segment.put_http_meta(http.METHOD, request.method)
segment.put_http_meta(http.USER_AGENT, headers.get('User-Agent'))

client_ip = request.environ.get('HTTP_X_FORWARDED_FOR') or request.environ.get('REMOTE_ADDR')
if client_ip:
segment.put_http_meta(http.CLIENT_IP, client_ip)
segment.put_http_meta(http.X_FORWARDED_FOR, True)
else:
segment.put_http_meta(http.CLIENT_IP, request.remote_addr)

try:
rv = callback(*a, **ka)
except Exception as resp:
segment.put_http_meta(http.STATUS, getattr(resp, 'status_code', 500))
stack = stacktrace.get_stacktrace(limit=self._recorder._max_trace_back)
segment.add_exception(resp, stack)
if self._in_lambda_ctx:
self._recorder.end_subsegment()
else:
self._recorder.end_segment()

raise resp

segment.put_http_meta(http.STATUS, response.status_code)

origin_header = segment.get_origin_trace_header()
resp_header_str = prepare_response_header(origin_header, segment)
response.set_header(http.XRAY_HEADER, resp_header_str)

cont_len = response.headers.get('Content-Length')
if cont_len:
segment.put_http_meta(http.CONTENT_LENGTH, int(cont_len))

if self._in_lambda_ctx:
self._recorder.end_subsegment()
else:
self._recorder.end_segment()

return rv

return wrapper

def _patch_render(recorder):

_render = SimpleTemplate.render

@recorder.capture('template_render')
def _traced_render(self, *args, **kwargs):
if self.filename:
recorder.current_subsegment().name = self.filename
return _render(self, *args, **kwargs)

SimpleTemplate.render = _traced_render
Empty file added tests/ext/bottle/__init__.py
Empty file.
Loading

0 comments on commit a839d7f

Please sign in to comment.