Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced attribute manager #23

Merged
merged 8 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 28 additions & 38 deletions backtracepython/attributes/attribute_manager.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,41 @@
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 get_report_attributes
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)

return result
attributes = {}
annotations = {}
for attribute_provider in self.attribute_providers:
try:
provider_attributes = attribute_provider.get()
generated_attributes, generated_annotations = get_report_attributes(
provider_attributes
)
attributes.update(generated_attributes)
annotations.update(generated_annotations)
except:
continue

return attributes, annotations

def add(self, attributes):
self.scoped_attributes.update(attributes)

def try_add(self, dictionary, provider):
try:
dictionary.update(provider.get())
except:
return
self.attribute_providers.append(UserAttributeProvider(attributes))

def get_predefined_scoped_attribute_providers(self):
return [
Expand Down
24 changes: 24 additions & 0 deletions backtracepython/attributes/report_data_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +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)
)


def get_report_attributes(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

# Return both dictionaries
return attributes, annotations
10 changes: 10 additions & 0 deletions backtracepython/attributes/user_attribute_provider.py
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion backtracepython/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
13 changes: 7 additions & 6 deletions backtracepython/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand All @@ -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(),
}

Expand Down Expand Up @@ -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"]

Expand Down
62 changes: 62 additions & 0 deletions tests/test_client_attributes.py
Original file line number Diff line number Diff line change
@@ -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,
perf2711 marked this conversation as resolved.
Show resolved Hide resolved
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)
26 changes: 25 additions & 1 deletion tests/test_report_attributes.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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
Loading