diff --git a/CHANGELOG.md b/CHANGELOG.md index e210cc986..42168b977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - You can now specify a `chunk_overlap` when creating an index in the Document Index. ### Fixes -... + - TaskSpan now properly sets its status to `Error` on crash. ### Deprecations - Deprecate old Trace Viewer as the new `StudioClient` replaces it. This affects `Tracer.submit_to_trace_viewer`. diff --git a/src/intelligence_layer/core/tracer/tracer.py b/src/intelligence_layer/core/tracer/tracer.py index 1983a7105..0beb67c19 100644 --- a/src/intelligence_layer/core/tracer/tracer.py +++ b/src/intelligence_layer/core/tracer/tracer.py @@ -331,21 +331,6 @@ def record_output(self, output: PydanticSerializable) -> None: """ ... - def __exit__( - self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - _traceback: Optional[TracebackType], - ) -> None: - if exc_type is not None and exc_value is not None and _traceback is not None: - error_value = ErrorValue( - error_type=str(exc_type.__qualname__), - message=str(exc_value), - stack_trace=str(traceback.format_exc()), - ) - self.record_output(error_value) - self.end() - class NoOpTracer(TaskSpan): """A no-op tracer. @@ -391,6 +376,14 @@ def end(self, timestamp: Optional[datetime] = None) -> None: def export_for_viewing(self) -> Sequence[ExportedSpan]: return [] + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_value: Optional[BaseException], + _traceback: Optional[TracebackType], + ) -> None: + pass + class JsonSerializer(RootModel[PydanticSerializable]): root: SerializeAsAny[PydanticSerializable] diff --git a/tests/core/tracer/test_in_memory_tracer.py b/tests/core/tracer/test_in_memory_tracer.py index 530faf0f7..fa41c1487 100644 --- a/tests/core/tracer/test_in_memory_tracer.py +++ b/tests/core/tracer/test_in_memory_tracer.py @@ -99,7 +99,10 @@ def test_task_span_records_error_value() -> None: raise ValueError("my bad, sorry") assert isinstance(tracer.entries[0], InMemoryTaskSpan) - error = tracer.entries[0].output + error_log = tracer.entries[0].entries[0] + assert isinstance(error_log, LogEntry) + + error = error_log.value assert isinstance(error, ErrorValue) assert error.message == "my bad, sorry" assert error.error_type == "ValueError" diff --git a/tests/core/tracer/test_tracer.py b/tests/core/tracer/test_tracer.py index 19a896ab8..88563b46f 100644 --- a/tests/core/tracer/test_tracer.py +++ b/tests/core/tracer/test_tracer.py @@ -104,7 +104,7 @@ def test_tracer_exports_task_spans_to_unified_format( "tracer_fixture", tracer_fixtures, ) -def test_tracer_exports_error_correctly( +def test_tracer_exports_error_correctly_on_span( tracer_fixture: str, request: pytest.FixtureRequest, ) -> None: @@ -129,6 +129,36 @@ def test_tracer_exports_error_correctly( assert span.status == SpanStatus.ERROR +@pytest.mark.parametrize( + "tracer_fixture", + tracer_fixtures, +) +def test_tracer_exports_error_correctly_on_taskspan( + tracer_fixture: str, + request: pytest.FixtureRequest, +) -> None: + tracer: Tracer = request.getfixturevalue(tracer_fixture) + + try: + with tracer.task_span("name", input="input"): + delay() + raise SpecificTestException + except SpecificTestException: + pass + delay() + + unified_format = tracer.export_for_viewing() + + assert len(unified_format) == 1 + span = unified_format[0] + assert span.name == "name" + assert span.parent_id is None + assert span.start_time < span.end_time < utc_now() + assert span.attributes.type == SpanType.TASK_SPAN + assert span.status == SpanStatus.ERROR + assert span.attributes.output is None + + @pytest.mark.parametrize( "tracer_fixture", tracer_fixtures,