-
Notifications
You must be signed in to change notification settings - Fork 3
Fix/HYBIM-962 review follow ups #209
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |||||||||||||
|
|
||||||||||||||
| import json | ||||||||||||||
| import logging | ||||||||||||||
| import math | ||||||||||||||
| import re | ||||||||||||||
| import threading | ||||||||||||||
| import time | ||||||||||||||
|
|
@@ -247,6 +248,9 @@ def _positive_json_integer(value: object) -> int | None: | |||||||||||||
| return None | ||||||||||||||
| if isinstance(value, int): | ||||||||||||||
| return value if value > 0 else None | ||||||||||||||
| if isinstance(value, float) and math.isfinite(value) and value.is_integer(): | ||||||||||||||
| parsed = int(value) | ||||||||||||||
| return parsed if parsed > 0 else None | ||||||||||||||
|
Comment on lines
+251
to
+253
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 minor (bug): Integral floats above 2^53 are accepted and converted to a misleading exact integer. The protobuf path is naturally bounded by int64, so requiring exact float representability keeps the JSON path consistent with it: if isinstance(value, float) and math.isfinite(value) and value.is_integer() and abs(value) <= 2**53:The new parametrized test is a good place to pin this — adding
Suggested change
🤖 Generated by the Astra agent |
||||||||||||||
| if isinstance(value, str) and value.isdecimal(): | ||||||||||||||
| parsed = int(value) | ||||||||||||||
| return parsed if parsed > 0 else None | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -368,7 +368,9 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||||
| "User must provide project_name or project_id to SplunkAOLogger, or set it as an environment variable." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| if self.experiment_id is None and self.agent_stream_name is None and self.agent_stream_id is None: | ||||||||||||||||||||||||||||||||||||||||||||
| raise SplunkAOLoggerException("agent_stream or agent_stream_id is required to initialize SplunkAOLogger.") | ||||||||||||||||||||||||||||||||||||||||||||
| raise SplunkAOLoggerException( | ||||||||||||||||||||||||||||||||||||||||||||
| "agent_stream or agent_stream_id is required to initialize SplunkAOLogger." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if local_metrics: | ||||||||||||||||||||||||||||||||||||||||||||
| self.local_metrics = local_metrics | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -413,6 +415,18 @@ def _set_current_parent(self, parent: StepWithChildSpans | None) -> None: | |||||||||||||||||||||||||||||||||||||||||||
| super()._set_current_parent(parent) | ||||||||||||||||||||||||||||||||||||||||||||
| self._sync_otel_context(parent) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _is_current_root(self, trace: Trace | None) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||
| """Return whether trace owns the current proprietary parent chain.""" | ||||||||||||||||||||||||||||||||||||||||||||
| if trace is None: | ||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| root = self.current_parent() | ||||||||||||||||||||||||||||||||||||||||||||
| if root is None: | ||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||
| while root._parent is not None: | ||||||||||||||||||||||||||||||||||||||||||||
| root = root._parent | ||||||||||||||||||||||||||||||||||||||||||||
| return root is trace | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+418
to
+428
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 minor (design): The PR's stated goal is to centralize the current-root ownership check, but only the boolean form was centralized — three copies of the same parent-chain walk remain:
Each of those needs the root object rather than a boolean, so they can't call def _current_root(self) -> StepWithChildSpans | None:
root = self.current_parent()
while root is not None and root._parent is not None:
root = root._parent
return root
def _is_current_root(self, trace: Trace | None) -> bool:
"""Return whether trace owns the current proprietary parent chain."""
return trace is not None and self._current_root() is traceSeparately:
Suggested change
🤖 Generated by the Astra agent |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def reset_parent_tracking(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """Clear proprietary and OTel tracking for the current request context.""" | ||||||||||||||||||||||||||||||||||||||||||||
| current_parent = self.current_parent() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import pytest | ||
|
|
||
| from splunk_ao import log, splunk_ao_context | ||
| from splunk_ao.schema.logged import LoggedTrace | ||
| from tests.testutils.setup import setup_mock_logstreams_client, setup_mock_projects_client, setup_mock_traces_client | ||
|
|
||
|
|
||
|
|
@@ -88,6 +89,44 @@ def failing_operation() -> None: | |
| assert splunk_ao_context.get_current_trace() is None | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_async_coroutine_exception_is_preserved_and_owned_trace_is_concluded(initialized_context: None) -> None: | ||
| # Given: a decorated async operation that raises an application exception | ||
| @log(span_type="workflow") | ||
| async def failing_operation() -> None: | ||
| await asyncio.sleep(0) | ||
| raise RuntimeError("async application failure") | ||
|
|
||
| # When: the operation is awaited | ||
| with pytest.raises(RuntimeError, match="async application failure"): | ||
| await failing_operation() | ||
|
|
||
| # Then: the original exception is re-raised and both telemetry contexts are released | ||
| logger = splunk_ao_context.get_logger_instance() | ||
| assert logger.current_parent() is None | ||
| assert splunk_ao_context.get_current_trace() is None | ||
|
Comment on lines
+100
to
+107
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 minor (testing): The test name promises
(The sync sibling at line 79 has the same gap; no need to fix it here, but it would be worth strengthening both together.) 🤖 Generated by the Astra agent |
||
|
|
||
|
|
||
| def test_logger_identifies_current_root_ownership(initialized_context: None) -> None: | ||
| # Given: an empty logger and an unrelated proprietary trace | ||
| logger = splunk_ao_context.get_logger_instance() | ||
| unrelated_trace = LoggedTrace(input="unrelated") | ||
|
|
||
| # Then: absent inputs and an empty parent chain are never owned | ||
| assert logger._is_current_root(None) is False | ||
| assert logger._is_current_root(unrelated_trace) is False | ||
|
|
||
| # When: an owned root and nested current child are created | ||
| owned_trace = logger.start_trace(input="request", name="owned") | ||
| assert logger._is_current_root(owned_trace) is True | ||
| logger.add_workflow_span(input="nested", name="nested") | ||
|
|
||
| # Then: the root is discovered by identity and an unrelated trace is rejected | ||
| assert logger._is_current_root(owned_trace) is True | ||
| assert logger._is_current_root(unrelated_trace) is False | ||
| logger.conclude(output="done", conclude_all=True) | ||
|
|
||
|
|
||
| def test_sync_generator_concludes_on_close_and_preserves_errors(initialized_context: None) -> None: | ||
| @log(span_type="workflow") | ||
| def stream(fail: bool = False): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 minor (bug): When a message carries an explicitly empty
partsand a non-emptycontent, this now silently drops the content entirely.Input
{"role": "user", "parts": [], "content": "hello"}maps to{"role": "user", "parts": []}—"hello"is gone. The old code was also lossy here (it emitted the bogus{"type": "text", "content": "[]"}and droppedcontenttoo), so this isn't a regression, but the fix is the natural place to decide the precedence. Since the point of the change is to stop losing/garbling content, havingparts: []win over real content seems like the wrong tiebreak — an adapter that initializesparts=[]by default and puts the payload incontentwould produce a message with no content at all.Suggest only honouring the empty list when there is nothing else to fall back on:
If
parts: []is meant to be authoritative regardless ofcontent, that's a defensible call — worth a short comment saying so, plus a test pinning the both-present case so the precedence isn't accidentally flipped later.🤖 Generated by the Astra agent