diff --git a/CHANGELOG.md b/CHANGELOG.md index 7297b2f..925b8ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - **[FEATURE]**: feat(capture): add support for capture tracepoints [#34](https://github.com/intergral/deep/pull/34) [@Umaaz](https://github.com/Umaaz) - **[BUGFIX]**: fix(duration): snapshot duration not set [#37](https://github.com/intergral/deep/pull/37) [@Umaaz](https://github.com/Umaaz) +- **[BUGFIX]**: fix(attributes): snapshot attributes not set [#38](https://github.com/intergral/deep/pull/38) [@Umaaz](https://github.com/Umaaz) # 1.1.0 (06/02/2024) diff --git a/src/deep/api/plugin/__init__.py b/src/deep/api/plugin/__init__.py index b7569d3..af3d908 100644 --- a/src/deep/api/plugin/__init__.py +++ b/src/deep/api/plugin/__init__.py @@ -148,10 +148,11 @@ class SnapshotDecorator(Plugin, abc.ABC): """Implement this to decorate collected snapshots with attributes.""" @abc.abstractmethod - def decorate(self, context: ActionContext) -> Optional[BoundedAttributes]: + def decorate(self, snapshot_id: str, context: ActionContext) -> Optional[BoundedAttributes]: """ Decorate a snapshot with additional data. + :param snapshot_id: the id of the collected snapshot :param context: the action context for this action :return: the additional attributes to attach diff --git a/src/deep/api/plugin/otel.py b/src/deep/api/plugin/otel.py index d0064ca..f4cff7b 100644 --- a/src/deep/api/plugin/otel.py +++ b/src/deep/api/plugin/otel.py @@ -91,14 +91,22 @@ class OTelPlugin(ResourceProvider, SnapshotDecorator, SpanProcessor): Provide span and trace information to the snapshot. """ - def create_span(self, name: str) -> Optional['Span']: + def create_span(self, name: str, context_id: str, tracepoint_id: str) -> Optional['Span']: """ Create and return a new span. :param name: the name of the span to create + :param context_id: the id of the context + :param tracepoint_id: the id of thr tracepoint :return: the created span """ - span = trace.get_tracer("deep").start_as_current_span(name, end_on_exit=False, attributes={'dynamic': 'deep'}) + span = trace.get_tracer("deep").start_as_current_span(name, + end_on_exit=False, + attributes={'dynamic': 'deep', + 'context': context_id, + "tracepoint": tracepoint_id + }, + ) if span: # noinspection PyUnresolvedReferences # this is a generator contextlib._GeneratorContextManager @@ -132,16 +140,18 @@ def resource(self) -> Optional[Resource]: return Resource.create(attributes=attributes) return None - def decorate(self, context: ActionContext) -> Optional[BoundedAttributes]: + def decorate(self, snapshot_id: str, context: ActionContext) -> Optional[BoundedAttributes]: """ Decorate a snapshot with additional data. + :param snapshot_id: the id of the collected snapshot :param context: the action context for this action :return: the additional attributes to attach """ span = self.current_span() if span is not None: + span.add_attribute("snapshot", snapshot_id) return BoundedAttributes(attributes={ "span_name": span.name, "trace_id": span.trace_id, diff --git a/src/deep/api/plugin/python.py b/src/deep/api/plugin/python.py index 22d7e38..f38343b 100644 --- a/src/deep/api/plugin/python.py +++ b/src/deep/api/plugin/python.py @@ -35,10 +35,11 @@ class PythonPlugin(ResourceProvider, SnapshotDecorator, TracepointLogger): This plugin provides the python version to the resource, and the thread name to the attributes. """ - def decorate(self, context: ActionContext) -> Optional[BoundedAttributes]: + def decorate(self, snapshot_id: str, context: ActionContext) -> Optional[BoundedAttributes]: """ Decorate a snapshot with additional data. + :param snapshot_id: the id of the collected snapshot :param context: the action context for this action :return: the additional attributes to attach diff --git a/src/deep/api/plugin/span/__init__.py b/src/deep/api/plugin/span/__init__.py index 4d68be8..e64b473 100644 --- a/src/deep/api/plugin/span/__init__.py +++ b/src/deep/api/plugin/span/__init__.py @@ -29,11 +29,13 @@ class SpanProcessor(Plugin, abc.ABC): """Span processor connects Deep to a span provider.""" @abc.abstractmethod - def create_span(self, name: str) -> Optional['Span']: + def create_span(self, name: str, context_id: str, tracepoint_id: str) -> Optional['Span']: """ Create and return a new span. :param name: the name of the span to create + :param context_id: the id of the context + :param tracepoint_id: the id of thr tracepoint :return: the created span """ pass diff --git a/src/deep/api/tracepoint/eventsnapshot.py b/src/deep/api/tracepoint/eventsnapshot.py index 8cf9da8..6f2f04c 100644 --- a/src/deep/api/tracepoint/eventsnapshot.py +++ b/src/deep/api/tracepoint/eventsnapshot.py @@ -73,6 +73,11 @@ def id(self): """The id of this snapshot.""" return self._id + @property + def id_str(self): + """The id of this snapshot.""" + return format(self._id, "032x") + @property def tracepoint(self): """The tracepoint that triggered this snapshot.""" diff --git a/src/deep/processor/context/snapshot_action.py b/src/deep/processor/context/snapshot_action.py index b648226..0c70968 100644 --- a/src/deep/processor/context/snapshot_action.py +++ b/src/deep/processor/context/snapshot_action.py @@ -177,10 +177,12 @@ def process(self, ctx: 'TriggerContext') -> Optional[ActionCallback]: return DeferredSnapshotActionCallback(self.action_context, snapshot) def _decorate_snapshot(self, ctx): - attributes = BoundedAttributes(attributes={'ctx_id': ctx.id}, immutable=False) + attributes = BoundedAttributes( + attributes={'context': ctx.id, 'tracepoint': self.action_context.location_action.tracepoint.id}, + immutable=False) for decorator in ctx.config.snapshot_decorators: try: - decorate = decorator.decorate(self.action_context) + decorate = decorator.decorate(self.snapshot.id_str, self.action_context) if decorate is not None: attributes.merge_in(decorate) except Exception: @@ -231,8 +233,7 @@ def __init__(self, action_context: ActionContext, snapshot: EventSnapshot): :param action_context: the action context that created this result :param snapshot: the snapshot result """ - self.action_context = action_context - self.snapshot = snapshot + super().__init__(action_context, snapshot) def process(self, ctx: 'TriggerContext') -> Optional[ActionCallback]: """ diff --git a/src/deep/processor/context/span_action.py b/src/deep/processor/context/span_action.py index 7eb0846..341a411 100644 --- a/src/deep/processor/context/span_action.py +++ b/src/deep/processor/context/span_action.py @@ -86,7 +86,7 @@ def _process_action(self): spans = [] for span_processor in self.trigger_context.config.span_processors: - span = span_processor.create_span(name) + span = span_processor.create_span(name, self.trigger_context.id, self.location_action.tracepoint.id) if span: spans.append(span) diff --git a/tests/unit_tests/api/plugin/test_otel.py b/tests/unit_tests/api/plugin/test_otel.py index ef2e6f5..773a473 100644 --- a/tests/unit_tests/api/plugin/test_otel.py +++ b/tests/unit_tests/api/plugin/test_otel.py @@ -39,8 +39,16 @@ def test_load_plugin(self): def test_collect_attributes(self): with trace.get_tracer_provider().get_tracer("test").start_as_current_span("test-span"): plugin = OTelPlugin() - attributes = plugin.decorate(None) + attributes = plugin.decorate("snap_id", None) self.assertIsNotNone(attributes) self.assertEqual("test-span", attributes.get("span_name")) self.assertIsNotNone(attributes.get("span_id")) self.assertIsNotNone(attributes.get("trace_id")) + self.assertEqual(plugin.current_span().proxy.attributes, {"snapshot": "snap_id"}) + + def test_create_span(self): + plugin = OTelPlugin() + span = plugin.create_span("test", "ctx_id", "tp_id") + self.assertIsNotNone(span) + attributes = span.proxy.attributes + self.assertEqual({"dynamic": "deep", "context": "ctx_id", "tracepoint": "tp_id"}, attributes) diff --git a/tests/unit_tests/api/plugin/test_python.py b/tests/unit_tests/api/plugin/test_python.py index 6864539..376eb3b 100644 --- a/tests/unit_tests/api/plugin/test_python.py +++ b/tests/unit_tests/api/plugin/test_python.py @@ -27,6 +27,6 @@ def test_load_plugin(self): def test_collect_attributes(self): plugin = PythonPlugin() - attributes = plugin.decorate(None) + attributes = plugin.decorate("", None) self.assertIsNotNone(attributes) self.assertEqual("MainThread", attributes.get("thread_name")) diff --git a/tests/unit_tests/processor/test_trigger_handler.py b/tests/unit_tests/processor/test_trigger_handler.py index 51be700..f4722c4 100644 --- a/tests/unit_tests/processor/test_trigger_handler.py +++ b/tests/unit_tests/processor/test_trigger_handler.py @@ -256,7 +256,7 @@ def test_span_action(self): config = MockConfigService({}) mock_plugin = mockito.mock(spec=SpanProcessor) mock_span = mockito.mock() - mockito.when(mock_plugin).create_span('some_test_function').thenReturn(mock_span) + mockito.when(mock_plugin).create_span('some_test_function', mockito.ANY, mockito.ANY).thenReturn(mock_span) config.plugins = [mock_plugin] push = MockPushService(None, None) handler = TriggerHandler(config, push) @@ -285,7 +285,7 @@ def test_span_action(self): pushed = push.pushed self.assertEqual(0, len(pushed)) - mockito.verify(mock_plugin, mockito.times(1)).create_span("some_test_function") + mockito.verify(mock_plugin, mockito.times(1)).create_span("some_test_function", mockito.ANY, mockito.ANY) mockito.verify(mock_span, mockito.times(1)).close()