From 8fe3b7e42c43b8b71f0cb68db12fb3875385bdad Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 24 Sep 2024 16:41:47 +0200 Subject: [PATCH 1/8] Advanced attribute manager --- .../attributes/attribute_manager.py | 61 ++++++++---------- .../attributes/report_data_builder.py | 20 ++++++ .../attributes/user_attribute_provider.py | 10 +++ backtracepython/client.py | 8 ++- backtracepython/report.py | 13 ++-- tests/test_client_attributes.py | 62 +++++++++++++++++++ tests/test_report_attributes.py | 26 +++++++- 7 files changed, 158 insertions(+), 42 deletions(-) create mode 100644 backtracepython/attributes/report_data_builder.py create mode 100644 backtracepython/attributes/user_attribute_provider.py create mode 100644 tests/test_client_attributes.py diff --git a/backtracepython/attributes/attribute_manager.py b/backtracepython/attributes/attribute_manager.py index e5c361e..a4c7901 100644 --- a/backtracepython/attributes/attribute_manager.py +++ b/backtracepython/attributes/attribute_manager.py @@ -1,51 +1,44 @@ import platform -from backtracepython.attributes.backtrace_attribute_provider import ( - BacktraceAttributeProvider, -) -from backtracepython.attributes.linux_memory_attribute_provider import ( - LinuxMemoryAttributeProvider, -) -from backtracepython.attributes.machine_attribute_provider import ( - MachineAttributeProvider, -) -from backtracepython.attributes.machineId_attribute_provider import ( - MachineIdAttributeProvider, -) -from backtracepython.attributes.process_attribute_provider import ( - ProcessAttributeProvider, -) -from backtracepython.attributes.session_attribute_provider import ( - SessionAttributeProvider, -) -from backtracepython.attributes.system_attribute_provider import SystemAttributeProvider +from .backtrace_attribute_provider import BacktraceAttributeProvider +from .linux_memory_attribute_provider import LinuxMemoryAttributeProvider +from .machine_attribute_provider import MachineAttributeProvider +from .machineId_attribute_provider import MachineIdAttributeProvider +from .process_attribute_provider import ProcessAttributeProvider +from .report_data_builder import ReportDataBuilder +from .session_attribute_provider import SessionAttributeProvider +from .system_attribute_provider import SystemAttributeProvider +from .user_attribute_provider import UserAttributeProvider class AttributeManager: def __init__(self): - self.dynamic_attributes = self.get_predefined_dynamic_attribute_providers() - self.scoped_attributes = {} - for ( - scoped_attribute_provider - ) in self.get_predefined_scoped_attribute_providers(): - self.try_add(self.scoped_attributes, scoped_attribute_provider) + self.attribute_providers = ( + self.get_predefined_dynamic_attribute_providers() + + self.get_predefined_scoped_attribute_providers() + ) def get(self): - result = {} - for dynamic_attribute_provider in self.dynamic_attributes: - self.try_add(result, dynamic_attribute_provider) - result.update(self.scoped_attributes) + attributes = {} + annotations = {} + for attribute_provider in self.attribute_providers: - return result + generated_attributes, generated_annotations = ReportDataBuilder.get( + self.try_get(attribute_provider) + ) + attributes.update(generated_attributes) + annotations.update(generated_annotations) + + return attributes, annotations def add(self, attributes): - self.scoped_attributes.update(attributes) + self.attribute_providers.append(UserAttributeProvider(attributes)) - def try_add(self, dictionary, provider): + def try_get(self, provider): try: - dictionary.update(provider.get()) + return provider.get() except: - return + return {} def get_predefined_scoped_attribute_providers(self): return [ diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py new file mode 100644 index 0000000..fc62e3a --- /dev/null +++ b/backtracepython/attributes/report_data_builder.py @@ -0,0 +1,20 @@ +import unicodedata + + +class ReportDataBuilder: + primitive_types = (int, float, str, bool, bytes, type(None)) + + @staticmethod + def get(provider_attributes): + attributes = {} + annotations = {} + + # Iterate through input_dict and split based on value types + for key, value in provider_attributes.items(): + if isinstance(value, ReportDataBuilder.primitive_types): + attributes[key] = value + else: + annotations[key] = value + + # Return both dictionaries + return attributes, annotations diff --git a/backtracepython/attributes/user_attribute_provider.py b/backtracepython/attributes/user_attribute_provider.py new file mode 100644 index 0000000..ea492e1 --- /dev/null +++ b/backtracepython/attributes/user_attribute_provider.py @@ -0,0 +1,10 @@ +from backtracepython.attributes.attribute_provider import AttributeProvider +from backtracepython.version import version_string + + +class UserAttributeProvider(AttributeProvider): + def __init__(self, attributes): + self.attributes = attributes + + def get(self): + return self.attributes diff --git a/backtracepython/client.py b/backtracepython/client.py index 06aed09..7d1b587 100644 --- a/backtracepython/client.py +++ b/backtracepython/client.py @@ -20,7 +20,13 @@ class globs: def get_attributes(): - return attribute_manager.get() + attributes, _ = attribute_manager.get() + return attributes + + +def get_annotations(): + _, annotations = attribute_manager.get() + return annotations def set_attribute(key, value): diff --git a/backtracepython/report.py b/backtracepython/report.py index ad7feaa..ea61d49 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -17,8 +17,8 @@ def __init__(self): self.source_path_dict = {} self.attachments = [] - init_attrs = {"error.type": "Exception"} - init_attrs.update(attribute_manager.get()) + attributes, annotations = attribute_manager.get() + attributes.update({"error.type": "Exception"}) self.log_lines = [] @@ -30,10 +30,8 @@ def __init__(self): "agent": "backtrace-python", "agentVersion": version_string, "mainThread": str(self.fault_thread.ident), - "attributes": init_attrs, - "annotations": { - "Environment Variables": dict(os.environ), - }, + "attributes": attributes, + "annotations": annotations, "threads": self.generate_stack_trace(), } @@ -124,6 +122,9 @@ def set_dict_attributes(self, target_dict): def set_annotation(self, key, value): self.report["annotations"][key] = value + def get_annotations(self): + return self.report["annotations"] + def get_attributes(self): return self.report["attributes"] diff --git a/tests/test_client_attributes.py b/tests/test_client_attributes.py new file mode 100644 index 0000000..b010e7d --- /dev/null +++ b/tests/test_client_attributes.py @@ -0,0 +1,62 @@ +from backtracepython.client import get_attributes, set_attribute, set_attributes +from backtracepython.report import BacktraceReport + + +def test_setting_client_attribute(): + key = "foo" + value = "bar" + set_attribute(key, value) + + client_attributes = get_attributes() + assert client_attributes[key] == value + + +def test_overriding_client_attribute(): + current_attributes = get_attributes() + key = list(current_attributes.keys())[0] + previous_value = list(current_attributes.values())[0] + + new_value = "bar" + set_attribute(key, new_value) + + client_attributes = get_attributes() + assert client_attributes[key] == new_value + assert new_value != previous_value + + +def test_primitive_values_in_attributes(): + primitive_attributes = { + "string": "test", + "int": 123, + "float": 123123.123, + "boolean": False, + "None": None, + } + + set_attributes(primitive_attributes) + new_report = BacktraceReport() + report_attributes = new_report.get_attributes() + + for primitive_value_key in primitive_attributes: + assert primitive_value_key in report_attributes + assert ( + report_attributes[primitive_value_key] + == primitive_attributes[primitive_value_key] + ) + + +def test_complex_objects_in_annotations(): + objects_to_test = ( + {"foo": 1, "bar": 2}, + ("foo", "bar", "baz"), + lambda: None, + BacktraceReport(), + ) + + for index, value in enumerate(objects_to_test): + set_attribute(index, value) + + new_report = BacktraceReport() + report_annotations = new_report.get_annotations() + + assert len(report_annotations) == len(objects_to_test) diff --git a/tests/test_report_attributes.py b/tests/test_report_attributes.py index e15d297..e277c26 100644 --- a/tests/test_report_attributes.py +++ b/tests/test_report_attributes.py @@ -1,4 +1,4 @@ -from backtracepython.client import set_attribute +from backtracepython.client import set_attribute, set_attributes from backtracepython.report import BacktraceReport report = BacktraceReport() @@ -61,3 +61,27 @@ def test_override_default_client_attribute_by_report(): new_report.set_attribute(test_attribute, test_attribute_value) attributes = new_report.get_attributes() assert attributes["guid"] == test_attribute_value + + +def test_annotation_in_annotations_data(): + annotation_name = "annotation_name" + annotation = {"name": "foo", "surname": "bar"} + + set_attribute(annotation_name, annotation) + + new_report = BacktraceReport() + report_annotation = new_report.get_annotations() + assert report_annotation[annotation_name] == annotation + + +def test_override_client_annotation(): + annotation_name = "annotation_name" + annotation = {"name": "foo", "surname": "bar"} + override_report_annotation = {"name": "foo", "surname": "bar", "age": "unknown"} + + set_attribute(annotation_name, annotation) + + new_report = BacktraceReport() + new_report.set_annotation(annotation_name, override_report_annotation) + report_annotation = new_report.get_annotations() + assert report_annotation[annotation_name] == override_report_annotation From 56a4a106bcc5efa227e72ada6c02ec8c73bfb241 Mon Sep 17 00:00:00 2001 From: kdysput Date: Tue, 24 Sep 2024 15:02:15 +0000 Subject: [PATCH 2/8] Add unicode --- backtracepython/attributes/report_data_builder.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py index fc62e3a..d22ae0a 100644 --- a/backtracepython/attributes/report_data_builder.py +++ b/backtracepython/attributes/report_data_builder.py @@ -1,8 +1,5 @@ -import unicodedata - - class ReportDataBuilder: - primitive_types = (int, float, str, bool, bytes, type(None)) + primitive_types = (int, float, str, bool, bytes, type(None), unicode) @staticmethod def get(provider_attributes): @@ -14,6 +11,7 @@ def get(provider_attributes): if isinstance(value, ReportDataBuilder.primitive_types): attributes[key] = value else: + print("Adding {} as an annotation. Type {}".format(key, type(value))) annotations[key] = value # Return both dictionaries From 06d3f680e8b6b7716f9aef526824b923a2143db7 Mon Sep 17 00:00:00 2001 From: kdysput Date: Tue, 24 Sep 2024 15:08:02 +0000 Subject: [PATCH 3/8] if statment for python3/2 --- backtracepython/attributes/report_data_builder.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py index d22ae0a..05dd639 100644 --- a/backtracepython/attributes/report_data_builder.py +++ b/backtracepython/attributes/report_data_builder.py @@ -1,5 +1,8 @@ +import sys + class ReportDataBuilder: - primitive_types = (int, float, str, bool, bytes, type(None), unicode) + + primitive_types = (int, float, bool, type(None), str) if sys.version_info.major >= 3 else (int, float, bool, type(None), str, unicode) @staticmethod def get(provider_attributes): From 8c42fe966fa4900573868f74ad2546f76faced71 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 24 Sep 2024 17:10:13 +0200 Subject: [PATCH 4/8] Format primitive types --- backtracepython/attributes/report_data_builder.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py index 05dd639..b8418c9 100644 --- a/backtracepython/attributes/report_data_builder.py +++ b/backtracepython/attributes/report_data_builder.py @@ -1,8 +1,13 @@ import sys + class ReportDataBuilder: - - primitive_types = (int, float, bool, type(None), str) if sys.version_info.major >= 3 else (int, float, bool, type(None), str, unicode) + + primitive_types = ( + (int, float, bool, type(None), str) + if sys.version_info.major >= 3 + else (int, float, bool, type(None), str, unicode) + ) @staticmethod def get(provider_attributes): From 0132da7002476e57332b8f5761bc4736c78b240d Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 24 Sep 2024 17:11:15 +0200 Subject: [PATCH 5/8] Remove debug logs --- backtracepython/attributes/report_data_builder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py index b8418c9..ddea2b5 100644 --- a/backtracepython/attributes/report_data_builder.py +++ b/backtracepython/attributes/report_data_builder.py @@ -19,7 +19,6 @@ def get(provider_attributes): if isinstance(value, ReportDataBuilder.primitive_types): attributes[key] = value else: - print("Adding {} as an annotation. Type {}".format(key, type(value))) annotations[key] = value # Return both dictionaries From 6ca384ad171aa4789026a5edf543e30a43abe4e5 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 24 Sep 2024 17:35:22 +0200 Subject: [PATCH 6/8] Simplify the get method --- .../attributes/attribute_manager.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/backtracepython/attributes/attribute_manager.py b/backtracepython/attributes/attribute_manager.py index a4c7901..f0dc617 100644 --- a/backtracepython/attributes/attribute_manager.py +++ b/backtracepython/attributes/attribute_manager.py @@ -22,24 +22,21 @@ def get(self): attributes = {} annotations = {} for attribute_provider in self.attribute_providers: - - generated_attributes, generated_annotations = ReportDataBuilder.get( - self.try_get(attribute_provider) - ) - attributes.update(generated_attributes) - annotations.update(generated_annotations) + try: + provider_attributes = attribute_provider.get() + generated_attributes, generated_annotations = ReportDataBuilder.get( + provider_attributes + ) + attributes.update(generated_attributes) + annotations.update(generated_annotations) + except: + continue return attributes, annotations def add(self, attributes): self.attribute_providers.append(UserAttributeProvider(attributes)) - def try_get(self, provider): - try: - return provider.get() - except: - return {} - def get_predefined_scoped_attribute_providers(self): return [ MachineIdAttributeProvider(), From 722e645febf8ddf77db089eaad85d5daf341d897 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 24 Sep 2024 17:36:34 +0200 Subject: [PATCH 7/8] Primitve types description --- backtracepython/attributes/report_data_builder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py index ddea2b5..2ffb1ce 100644 --- a/backtracepython/attributes/report_data_builder.py +++ b/backtracepython/attributes/report_data_builder.py @@ -3,6 +3,8 @@ class ReportDataBuilder: + # unicode is not available in Python3. However due to the Python2 support + # We need to use it to verify primitive values. primitive_types = ( (int, float, bool, type(None), str) if sys.version_info.major >= 3 From 00523be46858d606a23bc0d7e0732571400e320e Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Fri, 27 Sep 2024 13:05:50 +0200 Subject: [PATCH 8/8] Use report data builder via function --- .../attributes/attribute_manager.py | 4 +- .../attributes/report_data_builder.py | 39 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/backtracepython/attributes/attribute_manager.py b/backtracepython/attributes/attribute_manager.py index f0dc617..e443c1a 100644 --- a/backtracepython/attributes/attribute_manager.py +++ b/backtracepython/attributes/attribute_manager.py @@ -5,7 +5,7 @@ from .machine_attribute_provider import MachineAttributeProvider from .machineId_attribute_provider import MachineIdAttributeProvider from .process_attribute_provider import ProcessAttributeProvider -from .report_data_builder import ReportDataBuilder +from .report_data_builder import get_report_attributes from .session_attribute_provider import SessionAttributeProvider from .system_attribute_provider import SystemAttributeProvider from .user_attribute_provider import UserAttributeProvider @@ -24,7 +24,7 @@ def get(self): for attribute_provider in self.attribute_providers: try: provider_attributes = attribute_provider.get() - generated_attributes, generated_annotations = ReportDataBuilder.get( + generated_attributes, generated_annotations = get_report_attributes( provider_attributes ) attributes.update(generated_attributes) diff --git a/backtracepython/attributes/report_data_builder.py b/backtracepython/attributes/report_data_builder.py index 2ffb1ce..197121a 100644 --- a/backtracepython/attributes/report_data_builder.py +++ b/backtracepython/attributes/report_data_builder.py @@ -1,27 +1,24 @@ import sys +# unicode is not available in Python3. However due to the Python2 support +# We need to use it to verify primitive values. +primitive_types = ( + (int, float, bool, type(None), str) + if sys.version_info.major >= 3 + else (int, float, bool, type(None), str, unicode) +) -class ReportDataBuilder: - # unicode is not available in Python3. However due to the Python2 support - # We need to use it to verify primitive values. - primitive_types = ( - (int, float, bool, type(None), str) - if sys.version_info.major >= 3 - else (int, float, bool, type(None), str, unicode) - ) +def get_report_attributes(provider_attributes): + attributes = {} + annotations = {} - @staticmethod - def get(provider_attributes): - attributes = {} - annotations = {} + # Iterate through input_dict and split based on value types + for key, value in provider_attributes.items(): + if isinstance(value, primitive_types): + attributes[key] = value + else: + annotations[key] = value - # Iterate through input_dict and split based on value types - for key, value in provider_attributes.items(): - if isinstance(value, ReportDataBuilder.primitive_types): - attributes[key] = value - else: - annotations[key] = value - - # Return both dictionaries - return attributes, annotations + # Return both dictionaries + return attributes, annotations