@@ -775,14 +775,16 @@ def exceptions_from_error(
775775):
776776 # type: (...) -> Tuple[int, List[Dict[str, Any]]]
777777 """
778- Creates the list of exceptions.
779- This can include chained exceptions and exceptions from an ExceptionGroup.
780-
781- See the Exception Interface documentation for more details:
782- https://develop.sentry.dev/sdk/event-payloads/exception/
778+ Converts the given exception information into the Sentry structured "exception" format.
779+ This will return a list of exceptions in the format of the Exception Interface documentation:
780+ https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
781+
782+ This function can handle:
783+ - simple exceptions
784+ - chained exceptions (raise .. from ..)
785+ - exception groups
783786 """
784-
785- parent = single_exception_from_error_tuple (
787+ base_exception = single_exception_from_error_tuple (
786788 exc_type = exc_type ,
787789 exc_value = exc_value ,
788790 tb = tb ,
@@ -793,64 +795,70 @@ def exceptions_from_error(
793795 source = source ,
794796 full_stack = full_stack ,
795797 )
796- exceptions = [parent ]
798+ exceptions = [base_exception ]
797799
798800 parent_id = exception_id
799801 exception_id += 1
800802
801- should_supress_context = hasattr (exc_value , "__suppress_context__" ) and exc_value .__suppress_context__ # type: ignore
802- if should_supress_context :
803- # Add direct cause.
804- # The field `__cause__` is set when raised with the exception (using the `from` keyword).
805- exception_has_cause = (
803+ # Note: __suppress_context__ is True if the exception is raised with the `from` keyword.
804+ should_suppress_context = hasattr (exc_value , "__suppress_context__" ) and exc_value .__suppress_context__ # type: ignore
805+ if should_suppress_context :
806+ # Explicitly chained exceptions (Like: raise NewException() from OriginalException())
807+ # The field `__cause__` is set to OriginalException
808+ exception_has_explicit_causing_exception = (
806809 exc_value
807810 and hasattr (exc_value , "__cause__" )
808811 and exc_value .__cause__ is not None
809812 )
810- if exception_has_cause :
811- cause = exc_value .__cause__ # type: ignore
813+ if exception_has_explicit_causing_exception :
814+ causing_exception = exc_value .__cause__ # type: ignore
815+
812816 (exception_id , child_exceptions ) = exceptions_from_error (
813- exc_type = type (cause ),
814- exc_value = cause ,
815- tb = getattr (cause , "__traceback__" , None ),
817+ exc_type = type (causing_exception ),
818+ exc_value = causing_exception ,
819+ tb = getattr (causing_exception , "__traceback__" , None ),
816820 client_options = client_options ,
817821 mechanism = mechanism ,
818822 exception_id = exception_id ,
823+ # parent_id=parent_id, TODO: why is this not set?
819824 source = "__cause__" ,
820825 full_stack = full_stack ,
821826 )
822827 exceptions .extend (child_exceptions )
823828
824829 else :
825- # Add indirect cause.
826- # The field `__context__` is assigned if another exception occurs while handling the exception.
827- exception_has_content = (
830+ # Implicitly chained exceptions (when an exception occurs while handling another exception)
831+ # The field `__context__` is set in the exception that occurs while handling another exception,
832+ # to the other exception.
833+ exception_has_implicit_causing_exception = (
828834 exc_value
829835 and hasattr (exc_value , "__context__" )
830836 and exc_value .__context__ is not None
831837 )
832- if exception_has_content :
833- context = exc_value .__context__ # type: ignore
838+ if exception_has_implicit_causing_exception :
839+ causing_exception = exc_value .__context__ # type: ignore
840+
834841 (exception_id , child_exceptions ) = exceptions_from_error (
835- exc_type = type (context ),
836- exc_value = context ,
837- tb = getattr (context , "__traceback__" , None ),
842+ exc_type = type (causing_exception ),
843+ exc_value = causing_exception ,
844+ tb = getattr (causing_exception , "__traceback__" , None ),
838845 client_options = client_options ,
839846 mechanism = mechanism ,
840847 exception_id = exception_id ,
848+ # parent_id=parent_id, TODO: why is this not set?
841849 source = "__context__" ,
842850 full_stack = full_stack ,
843851 )
844852 exceptions .extend (child_exceptions )
845853
846- # Add exceptions from an ExceptionGroup.
854+ # Add child exceptions from an ExceptionGroup.
847855 is_exception_group = exc_value and hasattr (exc_value , "exceptions" )
848856 if is_exception_group :
849- for idx , e in enumerate (exc_value .exceptions ): # type: ignore
857+ for idx , causing_exception in enumerate (exc_value .exceptions ): # type: ignore
850858 (exception_id , child_exceptions ) = exceptions_from_error (
851- exc_type = type (e ),
852- exc_value = e ,
853- tb = getattr (e , "__traceback__" , None ),
859+ exc_type = type (causing_exception ),
860+ exc_value = causing_exception ,
861+ tb = getattr (causing_exception , "__traceback__" , None ),
854862 client_options = client_options ,
855863 mechanism = mechanism ,
856864 exception_id = exception_id ,
@@ -870,8 +878,15 @@ def exceptions_from_error_tuple(
870878 full_stack = None , # type: Optional[list[dict[str, Any]]]
871879):
872880 # type: (...) -> List[Dict[str, Any]]
881+ """
882+ Convert Python's exception information into Sentry's structured "exception" format in the event.
883+ See https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
884+ This is the entry point for the exception handling.
885+ """
886+ # unpack the exception info tuple
873887 exc_type , exc_value , tb = exc_info
874888
889+ # let exceptions_from_error do the actual work
875890 _ , exceptions = exceptions_from_error (
876891 exc_type = exc_type ,
877892 exc_value = exc_value ,
@@ -883,6 +898,9 @@ def exceptions_from_error_tuple(
883898 full_stack = full_stack ,
884899 )
885900
901+ # make sure the exceptions are sorted
902+ # from the innermost (oldest)
903+ # to the outermost (newest) exception
886904 exceptions .reverse ()
887905
888906 return exceptions
0 commit comments