From 90fd6d988aa5a2fed065600f0df8fc8728a6795d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 27 Apr 2026 19:45:11 -0400 Subject: [PATCH] fix(integrations): standardize error logging Log exception objects directly across integration spans instead of stringifying errors, and remove the now-unused log_exc_info_to_span helper. Also simplify Anthropic stream cleanup paths now that traceback formatting is no longer used. --- py/src/braintrust/integrations/adk/tracing.py | 4 +-- .../integrations/agentscope/tracing.py | 6 ++-- .../braintrust/integrations/agno/tracing.py | 30 +++++++++---------- .../integrations/anthropic/tracing.py | 28 ++++++++--------- .../integrations/claude_agent_sdk/tracing.py | 6 ++-- .../integrations/langchain/callbacks.py | 8 ++--- .../integrations/llamaindex/tracing.py | 3 +- .../integrations/strands/tracing.py | 2 +- py/src/braintrust/logger.py | 7 ----- 9 files changed, 44 insertions(+), 50 deletions(-) diff --git a/py/src/braintrust/integrations/adk/tracing.py b/py/src/braintrust/integrations/adk/tracing.py index 73c3ea6f..012ac877 100644 --- a/py/src/braintrust/integrations/adk/tracing.py +++ b/py/src/braintrust/integrations/adk/tracing.py @@ -534,7 +534,7 @@ async def _tool_call_async_wrapper(wrapped: Any, instance: Any, args: Any, kwarg tool_span.log(output=result) return result except Exception as e: - tool_span.log(error=str(e)) + tool_span.log(error=e) raise @@ -555,5 +555,5 @@ async def _mcp_tool_run_async_wrapper_async(wrapped: Any, instance: Any, args: A return result except Exception as e: # Log error to span but re-raise for ADK to handle - tool_span.log(error=str(e)) + tool_span.log(error=e) raise diff --git a/py/src/braintrust/integrations/agentscope/tracing.py b/py/src/braintrust/integrations/agentscope/tracing.py index d2370ef5..08dafd8a 100644 --- a/py/src/braintrust/integrations/agentscope/tracing.py +++ b/py/src/braintrust/integrations/agentscope/tracing.py @@ -167,7 +167,7 @@ async def _wrapper(wrapped: Any, instance: Any, args: Any, kwargs: dict[str, Any span.log(output=result) return result except Exception as exc: - span.log(error=str(exc)) + span.log(error=exc) raise return _wrapper @@ -238,7 +238,7 @@ async def _toolkit_call_tool_function_wrapper(wrapped: Any, instance: Any, args: span.log(output=result) return result except Exception as exc: - span.log(error=str(exc)) + span.log(error=exc) raise @@ -265,5 +265,5 @@ async def _model_call_wrapper(wrapped: Any, instance: Any, args: Any, kwargs: di span.log(output=_model_call_output(result), metrics=_extract_metrics(result)) return result except Exception as exc: - span.log(error=str(exc)) + span.log(error=exc) raise diff --git a/py/src/braintrust/integrations/agno/tracing.py b/py/src/braintrust/integrations/agno/tracing.py index 65a6ff7e..5067cdd3 100644 --- a/py/src/braintrust/integrations/agno/tracing.py +++ b/py/src/braintrust/integrations/agno/tracing.py @@ -381,7 +381,7 @@ def _inner(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -409,7 +409,7 @@ async def _inner(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -494,7 +494,7 @@ def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -535,7 +535,7 @@ async def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -610,7 +610,7 @@ def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -651,7 +651,7 @@ async def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -698,7 +698,7 @@ def _run_public_dispatch_wrapper( span.end() return result except Exception as e: - span.log(error=str(e)) + span.log(error=e) span.unset_current() span.end() raise @@ -742,7 +742,7 @@ async def _trace_awaitable(): span.log(output=awaited, metrics=extract_metrics(awaited)) return awaited except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_end_span: @@ -759,7 +759,7 @@ async def _trace_awaitable(): span.end() return result except Exception as e: - span.log(error=str(e)) + span.log(error=e) span.unset_current() span.end() raise @@ -1113,7 +1113,7 @@ def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -1171,7 +1171,7 @@ async def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -1218,7 +1218,7 @@ def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -1232,7 +1232,7 @@ def _trace_stream(): span.end() return result except Exception as e: - span.log(error=str(e)) + span.log(error=e) span.unset_current() span.end() raise @@ -1275,7 +1275,7 @@ async def _trace_stream(): should_unset = False raise except Exception as e: - span.log(error=str(e)) + span.log(error=e) raise finally: if should_unset: @@ -1289,7 +1289,7 @@ async def _trace_stream(): span.end() return result except Exception as e: - span.log(error=str(e)) + span.log(error=e) span.unset_current() span.end() raise diff --git a/py/src/braintrust/integrations/anthropic/tracing.py b/py/src/braintrust/integrations/anthropic/tracing.py index a95eaa17..a39ad0d0 100644 --- a/py/src/braintrust/integrations/anthropic/tracing.py +++ b/py/src/braintrust/integrations/anthropic/tracing.py @@ -6,7 +6,7 @@ from braintrust.bt_json import bt_safe_deep_copy from braintrust.integrations.anthropic._utils import Wrapper, _try_to_dict, extract_anthropic_usage from braintrust.integrations.utils import _materialize_attachment -from braintrust.logger import log_exc_info_to_span, start_span +from braintrust.logger import start_span from braintrust.span_types import SpanTypeAttribute from braintrust.util import is_numeric @@ -648,22 +648,22 @@ def __aexit__(self, exc_type, exc_value, traceback): try: return self.__msg_stream_mgr.__aexit__(exc_type, exc_value, traceback) finally: - self.__close(exc_type, exc_value, traceback) + self.__close(exc_value) def __exit__(self, exc_type, exc_value, traceback): try: return self.__msg_stream_mgr.__exit__(exc_type, exc_value, traceback) finally: - self.__close(exc_type, exc_value, traceback) + self.__close(exc_value) - def __close(self, exc_type, exc_value, traceback): + def __close(self, exc_value): tms = self.__traced_message_stream msg = tms._get_final_traced_message() if msg: ttft = tms._get_time_to_first_token() _log_message_to_span(msg, self.__span, time_to_first_token=ttft) - if exc_type: - log_exc_info_to_span(self.__span, exc_type, exc_value, traceback) + if exc_value is not None: + self.__span.log(error=exc_value) self.__span.end() @@ -765,7 +765,7 @@ def __exit__(self, exc_type, exc_value, traceback): try: return self.__stream.__exit__(exc_type, exc_value, traceback) finally: - self._finish(exc_type=exc_type, exc_value=exc_value, traceback=traceback) + self._finish(exc_value=exc_value) def close(self): try: @@ -775,7 +775,7 @@ def close(self): finally: self._finish() - def _finish(self, exc_type=None, exc_value=None, traceback=None, error=None): + def _finish(self, exc_value=None, error=None): if self.__finished: return self.__finished = True @@ -783,8 +783,8 @@ def _finish(self, exc_type=None, exc_value=None, traceback=None, error=None): _log_managed_agents_stream_to_span(self.__events, self.__span) if error is not None: self.__span.log(error=error) - elif exc_type is not None: - log_exc_info_to_span(self.__span, exc_type, exc_value, traceback) + elif exc_value is not None: + self.__span.log(error=exc_value) self.__span.end() @@ -822,7 +822,7 @@ async def __aexit__(self, exc_type, exc_value, traceback): try: return await self.__stream.__aexit__(exc_type, exc_value, traceback) finally: - await self._finish(exc_type=exc_type, exc_value=exc_value, traceback=traceback) + await self._finish(exc_value=exc_value) async def close(self): try: @@ -832,7 +832,7 @@ async def close(self): finally: await self._finish() - async def _finish(self, exc_type=None, exc_value=None, traceback=None, error=None): + async def _finish(self, exc_value=None, error=None): if self.__finished: return self.__finished = True @@ -840,8 +840,8 @@ async def _finish(self, exc_type=None, exc_value=None, traceback=None, error=Non _log_managed_agents_stream_to_span(self.__events, self.__span) if error is not None: self.__span.log(error=error) - elif exc_type is not None: - log_exc_info_to_span(self.__span, exc_type, exc_value, traceback) + elif exc_value is not None: + self.__span.log(error=exc_value) self.__span.end() diff --git a/py/src/braintrust/integrations/claude_agent_sdk/tracing.py b/py/src/braintrust/integrations/claude_agent_sdk/tracing.py index 42915a8e..be6a3e65 100644 --- a/py/src/braintrust/integrations/claude_agent_sdk/tracing.py +++ b/py/src/braintrust/integrations/claude_agent_sdk/tracing.py @@ -56,7 +56,7 @@ def activate(self) -> None: self.span.set_current() def log_error(self, exc: Exception) -> None: - self.span.log(error=str(exc)) + self.span.log(error=exc) def release(self) -> None: if not self.handler_active: @@ -911,7 +911,7 @@ def add_message(self, message: Any) -> None: self._context_tracker.add(message) def log_error(self, exc: Exception) -> None: - self._root_span.log(error=str(exc)) + self._root_span.log(error=exc) async def trace_hook_callback( self, @@ -932,7 +932,7 @@ async def trace_hook_callback( try: result = await callback(input_data, tool_use_id, context) except Exception as exc: - span.log(error=str(exc)) + span.log(error=exc) raise span.log(output=_serialize_hook_value(result)) diff --git a/py/src/braintrust/integrations/langchain/callbacks.py b/py/src/braintrust/integrations/langchain/callbacks.py index a80a5625..4f283390 100644 --- a/py/src/braintrust/integrations/langchain/callbacks.py +++ b/py/src/braintrust/integrations/langchain/callbacks.py @@ -217,7 +217,7 @@ def on_llm_error( parent_run_id: UUID | None = None, **kwargs: Any, # TODO: response= ) -> None: - self._end_span(run_id, error=str(error), metadata={**kwargs}) + self._end_span(run_id, error=error, metadata={**kwargs}) self._start_times.pop(run_id, None) self._first_token_times.pop(run_id, None) @@ -231,7 +231,7 @@ def on_chain_error( parent_run_id: UUID | None = None, **kwargs: Any, # TODO: some metadata ) -> None: - self._end_span(run_id, error=str(error), metadata={**kwargs}) + self._end_span(run_id, error=error, metadata={**kwargs}) def on_tool_error( self, @@ -241,7 +241,7 @@ def on_tool_error( parent_run_id: UUID | None = None, **kwargs: Any, ) -> None: - self._end_span(run_id, error=str(error), metadata={**kwargs}) + self._end_span(run_id, error=error, metadata={**kwargs}) def on_retriever_error( self, @@ -251,7 +251,7 @@ def on_retriever_error( parent_run_id: UUID | None = None, **kwargs: Any, ) -> None: - self._end_span(run_id, error=str(error), metadata={**kwargs}) + self._end_span(run_id, error=error, metadata={**kwargs}) # Agent Methods def on_agent_action( diff --git a/py/src/braintrust/integrations/llamaindex/tracing.py b/py/src/braintrust/integrations/llamaindex/tracing.py index ee2a6542..8bee6cd4 100644 --- a/py/src/braintrust/integrations/llamaindex/tracing.py +++ b/py/src/braintrust/integrations/llamaindex/tracing.py @@ -252,7 +252,8 @@ def prepare_to_drop_span( return None bt_span = record.bt_span - bt_span.log(error=f"{type(err).__name__}: {err}" if err else "Unknown error") + if err is not None: + bt_span.log(error=err) bt_span.unset_current() bt_span.end() diff --git a/py/src/braintrust/integrations/strands/tracing.py b/py/src/braintrust/integrations/strands/tracing.py index b277949b..bf2ff343 100644 --- a/py/src/braintrust/integrations/strands/tracing.py +++ b/py/src/braintrust/integrations/strands/tracing.py @@ -84,7 +84,7 @@ def _end_span_for_otel( if span is None: return if error is not None: - span.log(error=repr(error)) + span.log(error=error) span.log(output=output, metadata=metadata, metrics=metrics) span.end() diff --git a/py/src/braintrust/logger.py b/py/src/braintrust/logger.py index 6ed497a1..f6b072cb 100644 --- a/py/src/braintrust/logger.py +++ b/py/src/braintrust/logger.py @@ -4536,13 +4536,6 @@ def _get_otel_parent(self): return None -def log_exc_info_to_span( - span: Span, exc_type: type[BaseException], exc_value: BaseException, tb: TracebackType | None -) -> None: - error = stringify_exception(exc_type, exc_value, tb) - span.log(error=error) - - def stringify_exception(exc_type: type[BaseException], exc_value: BaseException, tb: TracebackType | None) -> str: return "".join(traceback.format_exception(exc_type, exc_value, tb))