Skip to content

Commit a839d7f

Browse files
authored
[issue-202] extension support as Bottle plugin (#204)
* [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]>
1 parent b47f1e8 commit a839d7f

File tree

7 files changed

+432
-1
lines changed

7 files changed

+432
-1
lines changed

README.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,19 @@ xray_recorder.configure(service='fallback_name', dynamic_naming='*mysite.com*')
386386
XRayMiddleware(app, xray_recorder)
387387
```
388388

389-
### Serverless Support for Flask & Django Using X-Ray
389+
### Add Bottle middleware(plugin)
390+
391+
```python
392+
from aws_xray_sdk.core import xray_recorder
393+
from aws_xray_sdk.ext.bottle.middleware import XRayMiddleware
394+
395+
app = Bottle()
396+
397+
xray_recorder.configure(service='fallback_name', dynamic_naming='*mysite.com*')
398+
app.install(XRayMiddleware(xray_recorder))
399+
```
400+
401+
### Serverless Support for Flask & Django & Bottle Using X-Ray
390402
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/).
391403

392404
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`.

aws_xray_sdk/ext/bottle/__init__.py

Whitespace-only changes.

aws_xray_sdk/ext/bottle/middleware.py

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from bottle import request, response, SimpleTemplate
2+
3+
from aws_xray_sdk.core.lambda_launcher import check_in_lambda, LambdaContext
4+
from aws_xray_sdk.core.models import http
5+
from aws_xray_sdk.core.utils import stacktrace
6+
from aws_xray_sdk.ext.util import calculate_sampling_decision, \
7+
calculate_segment_name, construct_xray_header, prepare_response_header
8+
9+
10+
class XRayMiddleware(object):
11+
"""
12+
Middleware that wraps each incoming request to a segment.
13+
"""
14+
name = 'xray'
15+
api = 2
16+
17+
def __init__(self, recorder):
18+
self._recorder = recorder
19+
self._in_lambda_ctx = False
20+
21+
if check_in_lambda() and type(self._recorder.context) == LambdaContext:
22+
self._in_lambda_ctx = True
23+
24+
_patch_render(recorder)
25+
26+
def apply(self, callback, route):
27+
"""
28+
Apply middleware directly to each route callback.
29+
"""
30+
def wrapper(*a, **ka):
31+
headers = request.headers
32+
xray_header = construct_xray_header(headers)
33+
name = calculate_segment_name(request.urlparts[1], self._recorder)
34+
35+
sampling_req = {
36+
'host': request.urlparts[1],
37+
'method': request.method,
38+
'path': request.path,
39+
'service': name,
40+
}
41+
sampling_decision = calculate_sampling_decision(
42+
trace_header=xray_header,
43+
recorder=self._recorder,
44+
sampling_req=sampling_req,
45+
)
46+
47+
if self._in_lambda_ctx:
48+
segment = self._recorder.begin_subsegment(name)
49+
else:
50+
segment = self._recorder.begin_segment(
51+
name=name,
52+
traceid=xray_header.root,
53+
parent_id=xray_header.parent,
54+
sampling=sampling_decision,
55+
)
56+
57+
segment.save_origin_trace_header(xray_header)
58+
segment.put_http_meta(http.URL, request.url)
59+
segment.put_http_meta(http.METHOD, request.method)
60+
segment.put_http_meta(http.USER_AGENT, headers.get('User-Agent'))
61+
62+
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR') or request.environ.get('REMOTE_ADDR')
63+
if client_ip:
64+
segment.put_http_meta(http.CLIENT_IP, client_ip)
65+
segment.put_http_meta(http.X_FORWARDED_FOR, True)
66+
else:
67+
segment.put_http_meta(http.CLIENT_IP, request.remote_addr)
68+
69+
try:
70+
rv = callback(*a, **ka)
71+
except Exception as resp:
72+
segment.put_http_meta(http.STATUS, getattr(resp, 'status_code', 500))
73+
stack = stacktrace.get_stacktrace(limit=self._recorder._max_trace_back)
74+
segment.add_exception(resp, stack)
75+
if self._in_lambda_ctx:
76+
self._recorder.end_subsegment()
77+
else:
78+
self._recorder.end_segment()
79+
80+
raise resp
81+
82+
segment.put_http_meta(http.STATUS, response.status_code)
83+
84+
origin_header = segment.get_origin_trace_header()
85+
resp_header_str = prepare_response_header(origin_header, segment)
86+
response.set_header(http.XRAY_HEADER, resp_header_str)
87+
88+
cont_len = response.headers.get('Content-Length')
89+
if cont_len:
90+
segment.put_http_meta(http.CONTENT_LENGTH, int(cont_len))
91+
92+
if self._in_lambda_ctx:
93+
self._recorder.end_subsegment()
94+
else:
95+
self._recorder.end_segment()
96+
97+
return rv
98+
99+
return wrapper
100+
101+
def _patch_render(recorder):
102+
103+
_render = SimpleTemplate.render
104+
105+
@recorder.capture('template_render')
106+
def _traced_render(self, *args, **kwargs):
107+
if self.filename:
108+
recorder.current_subsegment().name = self.filename
109+
return _render(self, *args, **kwargs)
110+
111+
SimpleTemplate.render = _traced_render

tests/ext/bottle/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)