Skip to content

[issue-202] extension support as Bottle plugin #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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