@ -173,6 +173,7 @@ from typing import (
Any ,
Callable ,
Collection ,
ContextManager ,
Dict ,
Generator ,
Iterable ,
@ -823,75 +824,117 @@ def extract_text_map(carrier: Dict[str, str]) -> Optional["opentracing.SpanConte
# Tracing decorators
def trace_with_opname ( opname : str ) - > Callable [ [ Callable [ P , R ] ] , Callable [ P , R ] ] :
def _custom_sync_async_decorator (
func : Callable [ P , R ] ,
wrapping_logic : Callable [ [ Callable [ P , R ] , Any , Any ] , ContextManager [ None ] ] ,
) - > Callable [ P , R ] :
"""
Decorator to trace a function with a custom opname .
See the module ' s doc string for usage examples.
Decorates a function that is sync or async ( coroutines ) , or that returns a Twisted
` Deferred ` . The custom business logic of the decorator goes in ` wrapping_logic ` .
Example usage :
` ` ` py
# Decorator to time the function and log it out
def duration ( func : Callable [ P , R ] ) - > Callable [ P , R ] :
@contextlib . contextmanager
def _wrapping_logic ( func : Callable [ P , R ] , * args : P . args , * * kwargs : P . kwargs ) - > Generator [ None , None , None ] :
start_ts = time . time ( )
try :
yield
finally :
end_ts = time . time ( )
duration = end_ts - start_ts
logger . info ( " %s took %s seconds " , func . __name__ , duration )
return _custom_sync_async_decorator ( func , _wrapping_logic )
` ` `
Args :
func : The function to be decorated
wrapping_logic : The business logic of your custom decorator .
This should be a ContextManager so you are able to run your logic
before / after the function as desired .
"""
def decorator ( func : Callable [ P , R ] ) - > Callable [ P , R ] :
if opentracing is None :
return func # type: ignore[unreachable]
if inspect . iscoroutinefunction ( func ) :
if inspect . iscoroutinefunction ( func ) :
@wraps ( func )
async def _wrapper ( * args : P . args , * * kwargs : P . kwargs ) - > R :
with wrapping_logic ( func , * args , * * kwargs ) :
return await func ( * args , * * kwargs ) # type: ignore[misc]
@wraps ( func )
async def _trace_inner ( * args : P . args , * * kwargs : P . kwargs ) - > R :
with start_active_span ( opname ) :
return await func ( * args , * * kwargs ) # type: ignore[misc]
else :
# The other case here handles both sync functions and those
# decorated with inlineDeferred.
@wraps ( func )
def _wrapper ( * args : P . args , * * kwargs : P . kwargs ) - > R :
scope = wrapping_logic ( func , * args , * * kwargs )
scope . __enter__ ( )
else :
# The other case here handles both sync functions and those
# decorated with inlineDeferred.
@wraps ( func )
def _trace_inner ( * args : P . args , * * kwargs : P . kwargs ) - > R :
scope = start_active_span ( opname )
scope . __enter__ ( )
try :
result = func ( * args , * * kwargs )
if isinstance ( result , defer . Deferred ) :
def call_back ( result : R ) - > R :
scope . __exit__ ( None , None , None )
return result
def err_back ( result : R ) - > R :
scope . __exit__ ( None , None , None )
return result
result . addCallbacks ( call_back , err_back )
else :
if inspect . isawaitable ( result ) :
logger . error (
" @trace may not have wrapped %s correctly! "
" The function is not async but returned a %s . " ,
func . __qualname__ ,
type ( result ) . __name__ ,
)
try :
result = func ( * args , * * kwargs )
if isinstance ( result , defer . Deferred ) :
def call_back ( result : R ) - > R :
scope . __exit__ ( None , None , None )
return result
def err_back ( result : R ) - > R :
scope . __exit__ ( None , None , None )
return result
result . addCallbacks ( call_back , err_back )
else :
if inspect . isawaitable ( result ) :
logger . error (
" @trace may not have wrapped %s correctly! "
" The function is not async but returned a %s . " ,
func . __qualname__ ,
type ( result ) . __name__ ,
)
scope . __exit__ ( None , None , None )
return result
return result
except Exception as e :
scope . __exit__ ( type ( e ) , None , e . __traceback__ )
raise
except Exception as e :
scope . __exit__ ( type ( e ) , None , e . __traceback__ )
raise
return _trace_inner # type: ignore[return-value]
return _wrapp er # type: ignore[return-value]
return decorator
def trace_with_opname (
opname : str ,
* ,
tracer : Optional [ " opentracing.Tracer " ] = None ,
) - > Callable [ [ Callable [ P , R ] ] , Callable [ P , R ] ] :
"""
Decorator to trace a function with a custom opname .
See the module ' s doc string for usage examples.
"""
# type-ignore: mypy bug, see https://github.com/python/mypy/issues/12909
@contextlib . contextmanager # type: ignore[arg-type]
def _wrapping_logic (
func : Callable [ P , R ] , * args : P . args , * * kwargs : P . kwargs
) - > Generator [ None , None , None ] :
with start_active_span ( opname , tracer = tracer ) :
yield
def _decorator ( func : Callable [ P , R ] ) - > Callable [ P , R ] :
if not opentracing :
return func
return _custom_sync_async_decorator ( func , _wrapping_logic )
return _decorator
def trace ( func : Callable [ P , R ] ) - > Callable [ P , R ] :
"""
Decorator to trace a function .
Sets the operation name to that of the function ' s name.
See the module ' s doc string for usage examples.
"""
@ -900,7 +943,7 @@ def trace(func: Callable[P, R]) -> Callable[P, R]:
def tag_args ( func : Callable [ P , R ] ) - > Callable [ P , R ] :
"""
Tags all of the args to the active span .
Decorator to tag all of the args to the active span .
Args :
func : ` func ` is assumed to be a method taking a ` self ` parameter , or a
@ -911,22 +954,25 @@ def tag_args(func: Callable[P, R]) -> Callable[P, R]:
if not opentracing :
return func
@wraps ( func )
def _tag_args_inner ( * args : P . args , * * kwargs : P . kwargs ) - > R :
# type-ignore: mypy bug, see https://github.com/python/mypy/issues/12909
@contextlib . contextmanager # type: ignore[arg-type]
def _wrapping_logic (
func : Callable [ P , R ] , * args : P . args , * * kwargs : P . kwargs
) - > Generator [ None , None , None ] :
argspec = inspect . getfullargspec ( func )
# We use `[1:]` to skip the `self` object reference and `start=1` to
# make the index line up with `argspec.args`.
#
# FIXME: We could update this handle any type of function by ignoring the
# FIXME: We could update this to handle any type of function by ignoring the
# first argument only if it's named `self` or `cls`. This isn't fool-proof
# but handles the idiomatic cases.
for i , arg in enumerate ( args [ 1 : ] , start = 1 ) : # type: ignore[index]
set_tag ( " ARG_ " + argspec . args [ i ] , str ( arg ) )
set_tag ( " args " , str ( args [ len ( argspec . args ) : ] ) ) # type: ignore[index]
set_tag ( " kwargs " , str ( kwargs ) )
return func ( * args , * * kwargs )
yield
return _tag_args_inner
return _custom_sync_async_decorator ( func , _wrapping_logic )
@contextlib . contextmanager