30
30
from urlparse import urlsplit # type: ignore
31
31
from urlparse import urlunsplit # type: ignore
32
32
33
+ try :
34
+ # Python 3.11
35
+ from builtins import BaseExceptionGroup
36
+ except ImportError :
37
+ # Python 3.10 and below
38
+ BaseExceptionGroup = None # type: ignore
33
39
34
40
from datetime import datetime
35
41
from functools import partial
@@ -666,19 +672,54 @@ def single_exception_from_error_tuple(
666
672
tb , # type: Optional[TracebackType]
667
673
client_options = None , # type: Optional[Dict[str, Any]]
668
674
mechanism = None , # type: Optional[Dict[str, Any]]
675
+ exception_id = None , # type: Optional[int]
676
+ parent_id = None , # type: Optional[int]
677
+ source = None , # type: Optional[str]
669
678
):
670
679
# type: (...) -> Dict[str, Any]
671
- mechanism = mechanism or {"type" : "generic" , "handled" : True }
680
+ """
681
+ Creates a dict that goes into the events `exception.values` list and is ingestible by Sentry.
682
+
683
+ See the Exception Interface documentation for more details:
684
+ https://develop.sentry.dev/sdk/event-payloads/exception/
685
+ """
686
+ exception_value = {} # type: Dict[str, Any]
687
+ exception_value ["mechanism" ] = (
688
+ mechanism .copy () if mechanism else {"type" : "generic" , "handled" : True }
689
+ )
690
+ if exception_id is not None :
691
+ exception_value ["mechanism" ]["exception_id" ] = exception_id
672
692
673
693
if exc_value is not None :
674
694
errno = get_errno (exc_value )
675
695
else :
676
696
errno = None
677
697
678
698
if errno is not None :
679
- mechanism .setdefault ("meta" , {}).setdefault ("errno" , {}).setdefault (
680
- "number" , errno
681
- )
699
+ exception_value ["mechanism" ].setdefault ("meta" , {}).setdefault (
700
+ "errno" , {}
701
+ ).setdefault ("number" , errno )
702
+
703
+ if source is not None :
704
+ exception_value ["mechanism" ]["source" ] = source
705
+
706
+ is_root_exception = exception_id == 0
707
+ if not is_root_exception and parent_id is not None :
708
+ exception_value ["mechanism" ]["parent_id" ] = parent_id
709
+ exception_value ["mechanism" ]["type" ] = "chained"
710
+
711
+ if is_root_exception and "type" not in exception_value ["mechanism" ]:
712
+ exception_value ["mechanism" ]["type" ] = "generic"
713
+
714
+ is_exception_group = BaseExceptionGroup is not None and isinstance (
715
+ exc_value , BaseExceptionGroup
716
+ )
717
+ if is_exception_group :
718
+ exception_value ["mechanism" ]["is_exception_group" ] = True
719
+
720
+ exception_value ["module" ] = get_type_module (exc_type )
721
+ exception_value ["type" ] = get_type_name (exc_type )
722
+ exception_value ["value" ] = getattr (exc_value , "message" , safe_str (exc_value ))
682
723
683
724
if client_options is None :
684
725
include_local_variables = True
@@ -697,17 +738,10 @@ def single_exception_from_error_tuple(
697
738
for tb in iter_stacks (tb )
698
739
]
699
740
700
- rv = {
701
- "module" : get_type_module (exc_type ),
702
- "type" : get_type_name (exc_type ),
703
- "value" : safe_str (exc_value ),
704
- "mechanism" : mechanism ,
705
- }
706
-
707
741
if frames :
708
- rv ["stacktrace" ] = {"frames" : frames }
742
+ exception_value ["stacktrace" ] = {"frames" : frames }
709
743
710
- return rv
744
+ return exception_value
711
745
712
746
713
747
HAS_CHAINED_EXCEPTIONS = hasattr (Exception , "__suppress_context__" )
@@ -751,24 +785,139 @@ def walk_exception_chain(exc_info):
751
785
yield exc_info
752
786
753
787
788
+ def exceptions_from_error (
789
+ exc_type , # type: Optional[type]
790
+ exc_value , # type: Optional[BaseException]
791
+ tb , # type: Optional[TracebackType]
792
+ client_options = None , # type: Optional[Dict[str, Any]]
793
+ mechanism = None , # type: Optional[Dict[str, Any]]
794
+ exception_id = 0 , # type: int
795
+ parent_id = 0 , # type: int
796
+ source = None , # type: Optional[str]
797
+ ):
798
+ # type: (...) -> Tuple[int, List[Dict[str, Any]]]
799
+ """
800
+ Creates the list of exceptions.
801
+ This can include chained exceptions and exceptions from an ExceptionGroup.
802
+
803
+ See the Exception Interface documentation for more details:
804
+ https://develop.sentry.dev/sdk/event-payloads/exception/
805
+ """
806
+
807
+ parent = single_exception_from_error_tuple (
808
+ exc_type = exc_type ,
809
+ exc_value = exc_value ,
810
+ tb = tb ,
811
+ client_options = client_options ,
812
+ mechanism = mechanism ,
813
+ exception_id = exception_id ,
814
+ parent_id = parent_id ,
815
+ source = source ,
816
+ )
817
+ exceptions = [parent ]
818
+
819
+ parent_id = exception_id
820
+ exception_id += 1
821
+
822
+ should_supress_context = (
823
+ hasattr (exc_value , "__suppress_context__" ) and exc_value .__suppress_context__ # type: ignore
824
+ )
825
+ if should_supress_context :
826
+ # Add direct cause.
827
+ # The field `__cause__` is set when raised with the exception (using the `from` keyword).
828
+ exception_has_cause = (
829
+ exc_value
830
+ and hasattr (exc_value , "__cause__" )
831
+ and exc_value .__cause__ is not None
832
+ )
833
+ if exception_has_cause :
834
+ cause = exc_value .__cause__ # type: ignore
835
+ (exception_id , child_exceptions ) = exceptions_from_error (
836
+ exc_type = type (cause ),
837
+ exc_value = cause ,
838
+ tb = getattr (cause , "__traceback__" , None ),
839
+ client_options = client_options ,
840
+ mechanism = mechanism ,
841
+ exception_id = exception_id ,
842
+ source = "__cause__" ,
843
+ )
844
+ exceptions .extend (child_exceptions )
845
+
846
+ else :
847
+ # Add indirect cause.
848
+ # The field `__context__` is assigned if another exception occurs while handling the exception.
849
+ exception_has_content = (
850
+ exc_value
851
+ and hasattr (exc_value , "__context__" )
852
+ and exc_value .__context__ is not None
853
+ )
854
+ if exception_has_content :
855
+ context = exc_value .__context__ # type: ignore
856
+ (exception_id , child_exceptions ) = exceptions_from_error (
857
+ exc_type = type (context ),
858
+ exc_value = context ,
859
+ tb = getattr (context , "__traceback__" , None ),
860
+ client_options = client_options ,
861
+ mechanism = mechanism ,
862
+ exception_id = exception_id ,
863
+ source = "__context__" ,
864
+ )
865
+ exceptions .extend (child_exceptions )
866
+
867
+ # Add exceptions from an ExceptionGroup.
868
+ is_exception_group = exc_value and hasattr (exc_value , "exceptions" )
869
+ if is_exception_group :
870
+ for idx , e in enumerate (exc_value .exceptions ): # type: ignore
871
+ (exception_id , child_exceptions ) = exceptions_from_error (
872
+ exc_type = type (e ),
873
+ exc_value = e ,
874
+ tb = getattr (e , "__traceback__" , None ),
875
+ client_options = client_options ,
876
+ mechanism = mechanism ,
877
+ exception_id = exception_id ,
878
+ parent_id = parent_id ,
879
+ source = "exceptions[%s]" % idx ,
880
+ )
881
+ exceptions .extend (child_exceptions )
882
+
883
+ return (exception_id , exceptions )
884
+
885
+
754
886
def exceptions_from_error_tuple (
755
887
exc_info , # type: ExcInfo
756
888
client_options = None , # type: Optional[Dict[str, Any]]
757
889
mechanism = None , # type: Optional[Dict[str, Any]]
758
890
):
759
891
# type: (...) -> List[Dict[str, Any]]
760
892
exc_type , exc_value , tb = exc_info
761
- rv = []
762
- for exc_type , exc_value , tb in walk_exception_chain (exc_info ):
763
- rv .append (
764
- single_exception_from_error_tuple (
765
- exc_type , exc_value , tb , client_options , mechanism
766
- )
893
+
894
+ is_exception_group = BaseExceptionGroup is not None and isinstance (
895
+ exc_value , BaseExceptionGroup
896
+ )
897
+
898
+ if is_exception_group :
899
+ (_ , exceptions ) = exceptions_from_error (
900
+ exc_type = exc_type ,
901
+ exc_value = exc_value ,
902
+ tb = tb ,
903
+ client_options = client_options ,
904
+ mechanism = mechanism ,
905
+ exception_id = 0 ,
906
+ parent_id = 0 ,
767
907
)
768
908
769
- rv .reverse ()
909
+ else :
910
+ exceptions = []
911
+ for exc_type , exc_value , tb in walk_exception_chain (exc_info ):
912
+ exceptions .append (
913
+ single_exception_from_error_tuple (
914
+ exc_type , exc_value , tb , client_options , mechanism
915
+ )
916
+ )
917
+
918
+ exceptions .reverse ()
770
919
771
- return rv
920
+ return exceptions
772
921
773
922
774
923
def to_string (value ):
0 commit comments