Skip to content

Commit 266bc82

Browse files
authored
Replace jsonpickle with json to serialize entity (#275)
* Replace jsonpickle with json to serialize entity * Added workflow to create release tag * Pinned sqlalchemy and Flask-SQLAlchemy for unit test * Fixed version * Changed log to debug level * Update logging * Added empty line
1 parent 508f929 commit 266bc82

File tree

10 files changed

+640
-61
lines changed

10 files changed

+640
-61
lines changed

.github/workflows/Release.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Release X-Ray Python SDK
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: The version to tag the release with, e.g., 1.2.0, 1.3.0
8+
required: true
9+
10+
jobs:
11+
release:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout master branch
15+
uses: actions/checkout@v2
16+
17+
- name: Create Release
18+
id: create_release
19+
uses: actions/create-release@v1
20+
env:
21+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22+
with:
23+
tag_name: '${{ github.event.inputs.version }}'
24+
release_name: '${{ github.event.inputs.version }} Release'
25+
body: 'See details in [CHANGELOG](https://github.com/aws/aws-xray-sdk-python/blob/master/CHANGELOG.rst)'
26+
draft: true
27+
prerelease: false

aws_xray_sdk/core/models/entity.py

+32-25
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import time
55
import string
66

7-
import jsonpickle
7+
import json
88

99
from ..utils.compat import annotation_value_types, string_types
10+
from ..utils.conversion import metadata_to_dict
1011
from .throwable import Throwable
1112
from . import http
1213
from ..exceptions.exceptions import AlreadyEndedException
@@ -256,36 +257,42 @@ def get_origin_trace_header(self):
256257
def serialize(self):
257258
"""
258259
Serialize to JSON document that can be accepted by the
259-
X-Ray backend service. It uses jsonpickle to perform
260-
serialization.
260+
X-Ray backend service. It uses json to perform serialization.
261261
"""
262262
try:
263-
return jsonpickle.encode(self, unpicklable=False)
263+
return json.dumps(self.to_dict(), default=str)
264264
except Exception:
265-
log.exception("got an exception during serialization")
265+
log.exception("Failed to serialize %s", self.name)
266266

267-
def _delete_empty_properties(self, properties):
267+
def to_dict(self):
268268
"""
269-
Delete empty properties before serialization to avoid
270-
extra keys with empty values in the output json.
269+
Convert Entity(Segment/Subsegment) object to dict
270+
with required properties that have non-empty values.
271271
"""
272-
if not self.parent_id:
273-
del properties['parent_id']
274-
if not self.subsegments:
275-
del properties['subsegments']
276-
if not self.aws:
277-
del properties['aws']
278-
if not self.http:
279-
del properties['http']
280-
if not self.cause:
281-
del properties['cause']
282-
if not self.annotations:
283-
del properties['annotations']
284-
if not self.metadata:
285-
del properties['metadata']
286-
properties.pop(ORIGIN_TRACE_HEADER_ATTR_KEY, None)
287-
288-
del properties['sampled']
272+
entity_dict = {}
273+
274+
for key, value in vars(self).items():
275+
if isinstance(value, bool) or value:
276+
if key == 'subsegments':
277+
# child subsegments are stored as List
278+
subsegments = []
279+
for subsegment in value:
280+
subsegments.append(subsegment.to_dict())
281+
entity_dict[key] = subsegments
282+
elif key == 'cause':
283+
entity_dict[key] = {}
284+
entity_dict[key]['working_directory'] = self.cause['working_directory']
285+
# exceptions are stored as List
286+
throwables = []
287+
for throwable in value['exceptions']:
288+
throwables.append(throwable.to_dict())
289+
entity_dict[key]['exceptions'] = throwables
290+
elif key == 'metadata':
291+
entity_dict[key] = metadata_to_dict(value)
292+
elif key != 'sampled' and key != ORIGIN_TRACE_HEADER_ATTR_KEY:
293+
entity_dict[key] = value
294+
295+
return entity_dict
289296

290297
def _check_ended(self):
291298
if not self.in_progress:

aws_xray_sdk/core/models/segment.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,14 @@ def set_rule_name(self, rule_name):
155155
self.aws['xray'] = {}
156156
self.aws['xray']['sampling_rule_name'] = rule_name
157157

158-
def __getstate__(self):
159-
"""
160-
Used by jsonpikle to remove unwanted fields.
161-
"""
162-
properties = copy.copy(self.__dict__)
163-
super(Segment, self)._delete_empty_properties(properties)
164-
if not self.user:
165-
del properties['user']
166-
del properties['ref_counter']
167-
del properties['_subsegments_counter']
168-
return properties
158+
def to_dict(self):
159+
"""
160+
Convert Segment object to dict with required properties
161+
that have non-empty values.
162+
"""
163+
segment_dict = super(Segment, self).to_dict()
164+
165+
del segment_dict['ref_counter']
166+
del segment_dict['_subsegments_counter']
167+
168+
return segment_dict

aws_xray_sdk/core/models/subsegment.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,13 @@ def set_sql(self, sql):
149149
"""
150150
self.sql = sql
151151

152-
def __getstate__(self):
153-
154-
properties = copy.copy(self.__dict__)
155-
super(Subsegment, self)._delete_empty_properties(properties)
156-
157-
del properties['parent_segment']
158-
if not self.sql:
159-
del properties['sql']
160-
return properties
152+
def to_dict(self):
153+
"""
154+
Convert Subsegment object to dict with required properties
155+
that have non-empty values.
156+
"""
157+
subsegment_dict = super(Subsegment, self).to_dict()
158+
159+
del subsegment_dict['parent_segment']
160+
161+
return subsegment_dict

aws_xray_sdk/core/models/throwable.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ def __init__(self, exception, stack, remote=False):
4646
if exception:
4747
setattr(exception, '_recorded', True)
4848
setattr(exception, '_cause_id', self.id)
49+
50+
def to_dict(self):
51+
"""
52+
Convert Throwable object to dict with required properties that
53+
have non-empty values.
54+
"""
55+
throwable_dict = {}
56+
57+
for key, value in vars(self).items():
58+
if isinstance(value, bool) or value:
59+
throwable_dict[key] = value
60+
61+
return throwable_dict
4962

5063
def _normalize_stack_trace(self, stack):
5164
if stack is None:
@@ -66,11 +79,3 @@ def _normalize_stack_trace(self, stack):
6679
normalized['label'] = label.strip()
6780

6881
self.stack.append(normalized)
69-
70-
def __getstate__(self):
71-
properties = copy.copy(self.__dict__)
72-
73-
if not self.stack:
74-
del properties['stack']
75-
76-
return properties

aws_xray_sdk/core/utils/conversion.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import logging
2+
3+
log = logging.getLogger(__name__)
4+
5+
def metadata_to_dict(obj):
6+
"""
7+
Convert object to dict with all serializable properties like:
8+
dict, list, set, tuple, str, bool, int, float, type, object, etc.
9+
"""
10+
try:
11+
if isinstance(obj, dict):
12+
metadata = {}
13+
for key, value in obj.items():
14+
metadata[key] = metadata_to_dict(value)
15+
return metadata
16+
elif isinstance(obj, type):
17+
return str(obj)
18+
elif hasattr(obj, "_ast"):
19+
return metadata_to_dict(obj._ast())
20+
elif hasattr(obj, "__iter__") and not isinstance(obj, str):
21+
metadata = []
22+
for item in obj:
23+
metadata.append(metadata_to_dict(item))
24+
return metadata
25+
elif hasattr(obj, "__dict__"):
26+
metadata = {}
27+
for key, value in vars(obj).items():
28+
if not callable(value) and not key.startswith('_'):
29+
metadata[key] = metadata_to_dict(value)
30+
return metadata
31+
else:
32+
return obj
33+
except Exception:
34+
log.exception("Failed to convert {} to dict".format(str(obj)))
35+
return {}

setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
],
4545

4646
install_requires=[
47-
'jsonpickle',
4847
'enum34;python_version<"3.4"',
4948
'wrapt',
5049
'future',

0 commit comments

Comments
 (0)