1
1
from contextlib import contextmanager
2
2
import functools
3
+ import inspect
4
+ from inspect import isasyncgenfunction
3
5
from inspect import iscoroutinefunction
4
6
from itertools import chain
5
7
import logging
@@ -780,6 +782,70 @@ def flush(self):
780
782
"""Flush the buffer of the trace writer. This does nothing if an unbuffered trace writer is used."""
781
783
self ._span_aggregator .writer .flush_queue ()
782
784
785
+ def _wrap_generator (
786
+ self ,
787
+ f : AnyCallable ,
788
+ span_name : str ,
789
+ service : Optional [str ] = None ,
790
+ resource : Optional [str ] = None ,
791
+ span_type : Optional [str ] = None ,
792
+ ) -> AnyCallable :
793
+ """Wrap a generator function with tracing."""
794
+
795
+ @functools .wraps (f )
796
+ def func_wrapper (* args , ** kwargs ):
797
+ if getattr (self , "_wrap_executor" , None ):
798
+ return self ._wrap_executor (
799
+ self ,
800
+ f ,
801
+ args ,
802
+ kwargs ,
803
+ span_name ,
804
+ service = service ,
805
+ resource = resource ,
806
+ span_type = span_type ,
807
+ )
808
+
809
+ with self .trace (span_name , service = service , resource = resource , span_type = span_type ) as span :
810
+ gen = f (* args , ** kwargs )
811
+ try :
812
+ while True :
813
+ value = next (gen )
814
+ yield value
815
+ except StopIteration :
816
+ pass
817
+ except Exception :
818
+ span .set_exc_info (sys .exc_info ())
819
+ raise
820
+
821
+ return func_wrapper
822
+
823
+ def _wrap_generator_async (
824
+ self ,
825
+ f : AnyCallable ,
826
+ span_name : str ,
827
+ service : Optional [str ] = None ,
828
+ resource : Optional [str ] = None ,
829
+ span_type : Optional [str ] = None ,
830
+ ) -> AnyCallable :
831
+ """Wrap a generator function with tracing."""
832
+
833
+ @functools .wraps (f )
834
+ async def func_wrapper (* args , ** kwargs ):
835
+ with self .trace (span_name , service = service , resource = resource , span_type = span_type ) as span :
836
+ agen = f (* args , ** kwargs )
837
+ try :
838
+ while True :
839
+ value = next (agen )
840
+ yield value
841
+ except StopIteration :
842
+ pass
843
+ except Exception :
844
+ span .set_exc_info (sys .exc_info ())
845
+ raise
846
+
847
+ return func_wrapper
848
+
783
849
def wrap (
784
850
self ,
785
851
name : Optional [str ] = None ,
@@ -817,6 +883,15 @@ async def coroutine():
817
883
def coroutine():
818
884
return 'executed'
819
885
886
+ >>> # or use it on generators
887
+ @tracer.wrap()
888
+ def gen():
889
+ yield 'executed'
890
+
891
+ >>> @tracer.wrap()
892
+ async def gen():
893
+ yield 'executed'
894
+
820
895
You can access the current span using `tracer.current_span()` to set
821
896
tags:
822
897
@@ -830,10 +905,26 @@ def wrap_decorator(f: AnyCallable) -> AnyCallable:
830
905
# FIXME[matt] include the class name for methods.
831
906
span_name = name if name else "%s.%s" % (f .__module__ , f .__name__ )
832
907
833
- # detect if the the given function is a coroutine to use the
834
- # right decorator; this initial check ensures that the
908
+ # detect if the the given function is a coroutine and/or a generator
909
+ # to use the right decorator; this initial check ensures that the
835
910
# evaluation is done only once for each @tracer.wrap
836
- if iscoroutinefunction (f ):
911
+ if inspect .isgeneratorfunction (f ):
912
+ func_wrapper = self ._wrap_generator (
913
+ f ,
914
+ span_name ,
915
+ service = service ,
916
+ resource = resource ,
917
+ span_type = span_type ,
918
+ )
919
+ elif inspect .isasyncgenfunction (f ):
920
+ func_wrapper = self ._wrap_generator_async (
921
+ f ,
922
+ span_name ,
923
+ service = service ,
924
+ resource = resource ,
925
+ span_type = span_type ,
926
+ )
927
+ elif iscoroutinefunction (f ):
837
928
# call the async factory that creates a tracing decorator capable
838
929
# to await the coroutine execution before finishing the span. This
839
930
# code is used for compatibility reasons to prevent Syntax errors
@@ -850,8 +941,6 @@ def wrap_decorator(f: AnyCallable) -> AnyCallable:
850
941
851
942
@functools .wraps (f )
852
943
def func_wrapper (* args , ** kwargs ):
853
- # if a wrap executor has been configured, it is used instead
854
- # of the default tracing function
855
944
if getattr (self , "_wrap_executor" , None ):
856
945
return self ._wrap_executor (
857
946
self ,
@@ -864,9 +953,12 @@ def func_wrapper(*args, **kwargs):
864
953
span_type = span_type ,
865
954
)
866
955
867
- # otherwise fallback to a default tracing
868
956
with self .trace (span_name , service = service , resource = resource , span_type = span_type ):
869
- return f (* args , ** kwargs )
957
+ try :
958
+ return f (* args , ** kwargs )
959
+ except Exception :
960
+ span .set_exc_info (sys .exc_info ())
961
+ raise
870
962
871
963
return func_wrapper
872
964
0 commit comments