Skip to content

Commit e19dfac

Browse files
konraddysputkdysput
andauthored
Advanced attribute manager (#23)
**Why** Our users can add advanced attributes as attributes and we should treat them from the beginning as annotations. This approach requires us to think about annotations and attributes in the attribute manager system we already have. This pull request checks each attribute provided by the user and converts it to the attributes/annotations in the flight --------- Co-authored-by: kdysput <[email protected]>
1 parent 326c1ea commit e19dfac

File tree

7 files changed

+163
-46
lines changed

7 files changed

+163
-46
lines changed

backtracepython/attributes/attribute_manager.py

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,41 @@
11
import platform
22

3-
from backtracepython.attributes.backtrace_attribute_provider import (
4-
BacktraceAttributeProvider,
5-
)
6-
from backtracepython.attributes.linux_memory_attribute_provider import (
7-
LinuxMemoryAttributeProvider,
8-
)
9-
from backtracepython.attributes.machine_attribute_provider import (
10-
MachineAttributeProvider,
11-
)
12-
from backtracepython.attributes.machineId_attribute_provider import (
13-
MachineIdAttributeProvider,
14-
)
15-
from backtracepython.attributes.process_attribute_provider import (
16-
ProcessAttributeProvider,
17-
)
18-
from backtracepython.attributes.session_attribute_provider import (
19-
SessionAttributeProvider,
20-
)
21-
from backtracepython.attributes.system_attribute_provider import SystemAttributeProvider
3+
from .backtrace_attribute_provider import BacktraceAttributeProvider
4+
from .linux_memory_attribute_provider import LinuxMemoryAttributeProvider
5+
from .machine_attribute_provider import MachineAttributeProvider
6+
from .machineId_attribute_provider import MachineIdAttributeProvider
7+
from .process_attribute_provider import ProcessAttributeProvider
8+
from .report_data_builder import get_report_attributes
9+
from .session_attribute_provider import SessionAttributeProvider
10+
from .system_attribute_provider import SystemAttributeProvider
11+
from .user_attribute_provider import UserAttributeProvider
2212

2313

2414
class AttributeManager:
2515
def __init__(self):
26-
self.dynamic_attributes = self.get_predefined_dynamic_attribute_providers()
27-
self.scoped_attributes = {}
28-
for (
29-
scoped_attribute_provider
30-
) in self.get_predefined_scoped_attribute_providers():
31-
self.try_add(self.scoped_attributes, scoped_attribute_provider)
16+
self.attribute_providers = (
17+
self.get_predefined_dynamic_attribute_providers()
18+
+ self.get_predefined_scoped_attribute_providers()
19+
)
3220

3321
def get(self):
34-
result = {}
35-
for dynamic_attribute_provider in self.dynamic_attributes:
36-
self.try_add(result, dynamic_attribute_provider)
37-
result.update(self.scoped_attributes)
38-
39-
return result
22+
attributes = {}
23+
annotations = {}
24+
for attribute_provider in self.attribute_providers:
25+
try:
26+
provider_attributes = attribute_provider.get()
27+
generated_attributes, generated_annotations = get_report_attributes(
28+
provider_attributes
29+
)
30+
attributes.update(generated_attributes)
31+
annotations.update(generated_annotations)
32+
except:
33+
continue
34+
35+
return attributes, annotations
4036

4137
def add(self, attributes):
42-
self.scoped_attributes.update(attributes)
43-
44-
def try_add(self, dictionary, provider):
45-
try:
46-
dictionary.update(provider.get())
47-
except:
48-
return
38+
self.attribute_providers.append(UserAttributeProvider(attributes))
4939

5040
def get_predefined_scoped_attribute_providers(self):
5141
return [
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import sys
2+
3+
# unicode is not available in Python3. However due to the Python2 support
4+
# We need to use it to verify primitive values.
5+
primitive_types = (
6+
(int, float, bool, type(None), str)
7+
if sys.version_info.major >= 3
8+
else (int, float, bool, type(None), str, unicode)
9+
)
10+
11+
12+
def get_report_attributes(provider_attributes):
13+
attributes = {}
14+
annotations = {}
15+
16+
# Iterate through input_dict and split based on value types
17+
for key, value in provider_attributes.items():
18+
if isinstance(value, primitive_types):
19+
attributes[key] = value
20+
else:
21+
annotations[key] = value
22+
23+
# Return both dictionaries
24+
return attributes, annotations
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from backtracepython.attributes.attribute_provider import AttributeProvider
2+
from backtracepython.version import version_string
3+
4+
5+
class UserAttributeProvider(AttributeProvider):
6+
def __init__(self, attributes):
7+
self.attributes = attributes
8+
9+
def get(self):
10+
return self.attributes

backtracepython/client.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ class globs:
2020

2121

2222
def get_attributes():
23-
return attribute_manager.get()
23+
attributes, _ = attribute_manager.get()
24+
return attributes
25+
26+
27+
def get_annotations():
28+
_, annotations = attribute_manager.get()
29+
return annotations
2430

2531

2632
def set_attribute(key, value):

backtracepython/report.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ def __init__(self):
1717
self.source_path_dict = {}
1818
self.attachments = []
1919

20-
init_attrs = {"error.type": "Exception"}
21-
init_attrs.update(attribute_manager.get())
20+
attributes, annotations = attribute_manager.get()
21+
attributes.update({"error.type": "Exception"})
2222

2323
self.log_lines = []
2424

@@ -30,10 +30,8 @@ def __init__(self):
3030
"agent": "backtrace-python",
3131
"agentVersion": version_string,
3232
"mainThread": str(self.fault_thread.ident),
33-
"attributes": init_attrs,
34-
"annotations": {
35-
"Environment Variables": dict(os.environ),
36-
},
33+
"attributes": attributes,
34+
"annotations": annotations,
3735
"threads": self.generate_stack_trace(),
3836
}
3937

@@ -124,6 +122,9 @@ def set_dict_attributes(self, target_dict):
124122
def set_annotation(self, key, value):
125123
self.report["annotations"][key] = value
126124

125+
def get_annotations(self):
126+
return self.report["annotations"]
127+
127128
def get_attributes(self):
128129
return self.report["attributes"]
129130

tests/test_client_attributes.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from backtracepython.client import get_attributes, set_attribute, set_attributes
2+
from backtracepython.report import BacktraceReport
3+
4+
5+
def test_setting_client_attribute():
6+
key = "foo"
7+
value = "bar"
8+
set_attribute(key, value)
9+
10+
client_attributes = get_attributes()
11+
assert client_attributes[key] == value
12+
13+
14+
def test_overriding_client_attribute():
15+
current_attributes = get_attributes()
16+
key = list(current_attributes.keys())[0]
17+
previous_value = list(current_attributes.values())[0]
18+
19+
new_value = "bar"
20+
set_attribute(key, new_value)
21+
22+
client_attributes = get_attributes()
23+
assert client_attributes[key] == new_value
24+
assert new_value != previous_value
25+
26+
27+
def test_primitive_values_in_attributes():
28+
primitive_attributes = {
29+
"string": "test",
30+
"int": 123,
31+
"float": 123123.123,
32+
"boolean": False,
33+
"None": None,
34+
}
35+
36+
set_attributes(primitive_attributes)
37+
new_report = BacktraceReport()
38+
report_attributes = new_report.get_attributes()
39+
40+
for primitive_value_key in primitive_attributes:
41+
assert primitive_value_key in report_attributes
42+
assert (
43+
report_attributes[primitive_value_key]
44+
== primitive_attributes[primitive_value_key]
45+
)
46+
47+
48+
def test_complex_objects_in_annotations():
49+
objects_to_test = (
50+
{"foo": 1, "bar": 2},
51+
("foo", "bar", "baz"),
52+
lambda: None,
53+
BacktraceReport(),
54+
)
55+
56+
for index, value in enumerate(objects_to_test):
57+
set_attribute(index, value)
58+
59+
new_report = BacktraceReport()
60+
report_annotations = new_report.get_annotations()
61+
62+
assert len(report_annotations) == len(objects_to_test)

tests/test_report_attributes.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from backtracepython.client import set_attribute
1+
from backtracepython.client import set_attribute, set_attributes
22
from backtracepython.report import BacktraceReport
33

44
report = BacktraceReport()
@@ -61,3 +61,27 @@ def test_override_default_client_attribute_by_report():
6161
new_report.set_attribute(test_attribute, test_attribute_value)
6262
attributes = new_report.get_attributes()
6363
assert attributes["guid"] == test_attribute_value
64+
65+
66+
def test_annotation_in_annotations_data():
67+
annotation_name = "annotation_name"
68+
annotation = {"name": "foo", "surname": "bar"}
69+
70+
set_attribute(annotation_name, annotation)
71+
72+
new_report = BacktraceReport()
73+
report_annotation = new_report.get_annotations()
74+
assert report_annotation[annotation_name] == annotation
75+
76+
77+
def test_override_client_annotation():
78+
annotation_name = "annotation_name"
79+
annotation = {"name": "foo", "surname": "bar"}
80+
override_report_annotation = {"name": "foo", "surname": "bar", "age": "unknown"}
81+
82+
set_attribute(annotation_name, annotation)
83+
84+
new_report = BacktraceReport()
85+
new_report.set_annotation(annotation_name, override_report_annotation)
86+
report_annotation = new_report.get_annotations()
87+
assert report_annotation[annotation_name] == override_report_annotation

0 commit comments

Comments
 (0)