Skip to content

Commit 3801f34

Browse files
committed
Add Serverless Framework Support Revision 2 (aws#127)
* Renamed ServerlessContext to ServerlessLambdaContext to be more precise about its purpose. * Instead of explicitly using Context class's put_subsegment/put_segment methods, use LambdaContext's grandparent class to store. LambdaContext's methods are really only used to acquire the FacadeSegment. * Parameters when initializing the mimic segment are now required. * Unit tests added to test these changes.
1 parent ba85a75 commit 3801f34

File tree

6 files changed

+43
-29
lines changed

6 files changed

+43
-29
lines changed

aws_xray_sdk/core/models/mimic_segment.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
from .segment import Segment
2+
from .facade_segment import FacadeSegment
23
from ..exceptions.exceptions import MimicSegmentInvalidException
34

45

56
class MimicSegment(Segment):
67
"""
7-
The MimicSegment is an entity that mimics a segment for the use of the serverless context.
8+
The MimicSegment is an entity that mimics a segment. It's primary use is for a special-case
9+
in Lambda; specifically, for the Serverless Design Pattern. It is not recommended to use
10+
this MimicSegment for any other purpose.
11+
812
When the MimicSegment is generated, its parent segment is assigned to be the FacadeSegment
913
generated by the Lambda Environment. Upon serialization and transmission of the MimicSegment,
1014
it is converted to a locally-namespaced, subsegment. This is only done during serialization.
@@ -16,8 +20,8 @@ class MimicSegment(Segment):
1620
is available to be used.
1721
"""
1822

19-
def __init__(self, facade_segment=None, original_segment=None):
20-
if not original_segment or not facade_segment:
23+
def __init__(self, facade_segment, original_segment):
24+
if not issubclass(type(original_segment), Segment) or type(facade_segment) != FacadeSegment:
2125
raise MimicSegmentInvalidException("Invalid MimicSegment construction. "
2226
"Please put in the original segment and the facade segment.")
2327
super(MimicSegment, self).__init__(name=original_segment.name, entityid=original_segment.id,
@@ -27,7 +31,7 @@ def __init__(self, facade_segment=None, original_segment=None):
2731
def __getstate__(self):
2832
"""
2933
Used during serialization. We mark the subsegment properties to let the dataplane know
30-
that we want the mimic segment to be represented as a subsegment.
34+
that we want the mimic segment to be transformed as a subsegment.
3135
"""
3236
properties = super(MimicSegment, self).__getstate__()
3337
properties['type'] = 'subsegment'

aws_xray_sdk/core/serverless_context.py renamed to aws_xray_sdk/core/serverless_lambda_context.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
from .models.mimic_segment import MimicSegment
77
from .context import CXT_MISSING_STRATEGY_KEY
88
from .lambda_launcher import LambdaContext
9-
from .context import Context
109

1110

1211
log = logging.getLogger(__name__)
1312

1413

15-
class ServerlessContext(LambdaContext):
14+
class ServerlessLambdaContext(LambdaContext):
1615
"""
1716
Context used specifically for running middlewares on Lambda through the
1817
Serverless design. This context is built on top of the LambdaContext, but
@@ -23,7 +22,7 @@ class ServerlessContext(LambdaContext):
2322
ensures that FacadeSegments exist through underlying calls to _refresh_context().
2423
"""
2524
def __init__(self, context_missing='RUNTIME_ERROR'):
26-
super(ServerlessContext, self).__init__()
25+
super(ServerlessLambdaContext, self).__init__()
2726

2827
strategy = os.getenv(CXT_MISSING_STRATEGY_KEY, context_missing)
2928
self._context_missing = strategy
@@ -38,15 +37,15 @@ def put_segment(self, segment):
3837
parent_facade_segment = self.__get_facade_entity() # type: FacadeSegment
3938
mimic_segment = MimicSegment(parent_facade_segment, segment)
4039
parent_facade_segment.add_subsegment(mimic_segment)
41-
Context.put_segment(self, mimic_segment)
40+
super(LambdaContext, self).put_segment(mimic_segment)
4241

4342
def end_segment(self, end_time=None):
4443
"""
4544
Close the MimicSegment
4645
"""
4746
# Close the last mimic segment opened then remove it from our facade segment.
4847
mimic_segment = self.get_trace_entity()
49-
Context.end_segment(self, end_time)
48+
super(LambdaContext, self).end_segment(end_time)
5049
if type(mimic_segment) == MimicSegment:
5150
# The facade segment can only hold mimic segments.
5251
facade_segment = self.__get_facade_entity()
@@ -58,7 +57,7 @@ def put_subsegment(self, subsegment):
5857
another subsegment if they are the last opened entity.
5958
:param subsegment: The subsegment to to be added as a subsegment.
6059
"""
61-
Context.put_subsegment(self, subsegment)
60+
super(LambdaContext, self).put_subsegment(subsegment)
6261

6362
def end_subsegment(self, end_time=None):
6463
"""
@@ -69,7 +68,7 @@ def end_subsegment(self, end_time=None):
6968
system time will be used.
7069
:return: True on success, false if no parent mimic segment/subsegment is found.
7170
"""
72-
return Context.end_subsegment(self, end_time)
71+
return super(LambdaContext, self).end_subsegment(end_time)
7372

7473
def __get_facade_entity(self):
7574
"""
@@ -92,12 +91,12 @@ def get_trace_entity(self):
9291
# Call to Context.get_trace_entity() returns the latest mimic segment/subsegment if they exist.
9392
# Otherwise, returns None through the following way:
9493
# No mimic segment/subsegment exists so Context calls LambdaContext's handle_context_missing().
95-
# By default, Lambda's method returns no-op, so it will return None to ServerlessContext.
94+
# By default, Lambda's method returns no-op, so it will return None to ServerlessLambdaContext.
9695
# Take that None as an indication to return the rightful handle_context_missing(), otherwise
9796
# return the entity.
98-
entity = Context.get_trace_entity(self)
97+
entity = super(LambdaContext, self).get_trace_entity()
9998
if entity is None:
100-
return Context.handle_context_missing(self)
99+
return super(LambdaContext, self).handle_context_missing()
101100
else:
102101
return entity
103102

@@ -116,11 +115,11 @@ def set_trace_entity(self, trace_entity):
116115
# behavior would be invoked.
117116
mimic_segment = trace_entity
118117

119-
Context.set_trace_entity(self, mimic_segment)
118+
super(LambdaContext, self).set_trace_entity(mimic_segment)
120119
self.__get_facade_entity().subsegments = [mimic_segment]
121120

122121
def _is_subsegment(self, entity):
123-
return super(ServerlessContext, self)._is_subsegment(entity) and type(entity) != MimicSegment
122+
return super(ServerlessLambdaContext, self)._is_subsegment(entity) and type(entity) != MimicSegment
124123

125124
@property
126125
def context_missing(self):

aws_xray_sdk/ext/django/middleware.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import logging
22

33
from aws_xray_sdk.core import xray_recorder
4-
from aws_xray_sdk.core.lambda_launcher import check_in_lambda
4+
from aws_xray_sdk.core.lambda_launcher import check_in_lambda, LambdaContext
55
from aws_xray_sdk.core.models import http
6-
from aws_xray_sdk.core.serverless_context import ServerlessContext
6+
from aws_xray_sdk.core.serverless_lambda_context import ServerlessLambdaContext
77
from aws_xray_sdk.core.utils import stacktrace
88
from aws_xray_sdk.ext.util import calculate_sampling_decision, \
99
calculate_segment_name, construct_xray_header, prepare_response_header
@@ -28,9 +28,11 @@ def __init__(self, get_response):
2828
self.get_response = get_response
2929

3030
# The case when the middleware is initialized in a Lambda Context, we make sure
31-
# to use the ServerlessContext so that the middleware properly functions.
32-
if check_in_lambda() is not None:
33-
xray_recorder.context = ServerlessContext()
31+
# to use the ServerlessLambdaContext so that the middleware properly functions.
32+
# We also check if the current context is a LambdaContext to not override customer
33+
# provided contexts.
34+
if check_in_lambda() is not None and type(xray_recorder.context) == LambdaContext:
35+
xray_recorder.context = ServerlessLambdaContext()
3436

3537
# hooks for django version >= 1.10
3638
def __call__(self, request):

aws_xray_sdk/ext/flask/middleware.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import flask.templating
22
from flask import request
33

4-
from aws_xray_sdk.core.lambda_launcher import check_in_lambda
4+
from aws_xray_sdk.core.lambda_launcher import check_in_lambda, LambdaContext
55
from aws_xray_sdk.core.models import http
6-
from aws_xray_sdk.core.serverless_context import ServerlessContext
6+
from aws_xray_sdk.core.serverless_lambda_context import ServerlessLambdaContext
77
from aws_xray_sdk.core.utils import stacktrace
88
from aws_xray_sdk.ext.util import calculate_sampling_decision, \
99
calculate_segment_name, construct_xray_header, prepare_response_header
@@ -21,9 +21,11 @@ def __init__(self, app, recorder):
2121
self.app.teardown_request(self._handle_exception)
2222

2323
# The case when the middleware is initialized in a Lambda Context, we make sure
24-
# to use the ServerlessContext so that the middleware properly functions.
25-
if check_in_lambda() is not None:
26-
self._recorder.context = ServerlessContext()
24+
# to use the ServerlessLambdaContext so that the middleware properly functions.
25+
# We also check if the current context is a LambdaContext to not override customer
26+
# provided contexts.
27+
if check_in_lambda() is not None and type(self._recorder.context) == LambdaContext:
28+
self._recorder.context = ServerlessLambdaContext()
2729

2830
_patch_render(recorder)
2931

tests/test_mimic_segment.py

+8
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,17 @@ def test_ready():
3030
def test_invalid_init():
3131
with pytest.raises(MimicSegmentInvalidException):
3232
MimicSegment(facade_segment=None, original_segment=original_segment)
33+
with pytest.raises(MimicSegmentInvalidException):
3334
MimicSegment(facade_segment=facade_segment, original_segment=None)
35+
with pytest.raises(MimicSegmentInvalidException):
3436
MimicSegment(facade_segment=Subsegment("Test", "local", original_segment), original_segment=None)
37+
with pytest.raises(MimicSegmentInvalidException):
3538
MimicSegment(facade_segment=None, original_segment=Subsegment("Test", "local", original_segment))
39+
with pytest.raises(MimicSegmentInvalidException):
40+
MimicSegment(facade_segment=facade_segment, original_segment=Subsegment("Test", "local", original_segment))
41+
with pytest.raises(MimicSegmentInvalidException):
42+
MimicSegment(facade_segment=original_segment, original_segment=facade_segment)
43+
MimicSegment(facade_segment=facade_segment, original_segment=original_segment)
3644

3745

3846
def test_init_similar():

tests/test_serverless_context.py renamed to tests/test_serverless_lambda_context.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import os
22
import pytest
33

4-
from aws_xray_sdk.core import serverless_context
5-
from aws_xray_sdk.core import context
4+
from aws_xray_sdk.core.serverless_lambda_context import ServerlessLambdaContext
65
from aws_xray_sdk.core.lambda_launcher import LAMBDA_TRACE_HEADER_KEY
76
from aws_xray_sdk.core.exceptions.exceptions import AlreadyEndedException, SegmentNotFoundException
87
from aws_xray_sdk.core.models.segment import Segment
@@ -16,7 +15,7 @@
1615
HEADER_VAR = "Root=%s;Parent=%s;Sampled=1" % (TRACE_ID, PARENT_ID)
1716

1817
os.environ[LAMBDA_TRACE_HEADER_KEY] = HEADER_VAR
19-
context = serverless_context.ServerlessContext()
18+
context = ServerlessLambdaContext()
2019

2120
service_name = "Test Flask Server"
2221

0 commit comments

Comments
 (0)