This repository was archived by the owner on Apr 26, 2024. It is now read-only.
  
  
  - 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 2.1k
 
          Allow use of both @trace and @tag_args stacked on the same function
          #13453
        
      
          
     Merged
      
      
            MadLittleMods
  merged 16 commits into
  develop
from
madlittlemods/use-both-@trace-and-@tag_args
  
      
      
   
  Aug 9, 2022 
      
    
  
     Merged
                    Changes from 2 commits
      Commits
    
    
            Show all changes
          
          
            16 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      b21fc7c
              
                Allow use of both @trace and @tag_args stacked on the same function
              
              
                MadLittleMods 991f3d0
              
                Add changelog
              
              
                MadLittleMods 87b09dc
              
                Simpify wrapper logic
              
              
                MadLittleMods 6727f3d
              
                Add some usage
              
              
                MadLittleMods 0bb8f7b
              
                Remove dead code
              
              
                MadLittleMods 20a6d40
              
                Merge branch 'develop' into madlittlemods/use-both-@trace-and-@tag_args
              
              
                MadLittleMods e57ccaa
              
                Fix some types and add tests
              
              
                MadLittleMods 00cc8a0
              
                Remove usage
              
              
                MadLittleMods 223e600
              
                Remove todo
              
              
                MadLittleMods f218fa2
              
                Fix last lints
              
              
                MadLittleMods dd5f966
              
                Decorate is the function
              
              
                MadLittleMods 2ef4cf4
              
                Other new comment
              
              
                MadLittleMods f327305
              
                Fix weird mismash commentdoc
              
              
                MadLittleMods 513b575
              
                More robust example
              
              
                MadLittleMods 7b62d48
              
                Add a descriptor to the name
              
              
                MadLittleMods 2f77b13
              
                Fix deferred type lint in old deps
              
              
                MadLittleMods File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Allow use of both `@trace` and `@tag_args` stacked on the same function (tracing). | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -173,6 +173,7 @@ def set_fates(clotho, lachesis, atropos, father="Zues", mother="Themis"): | |
| Any, | ||
| Callable, | ||
| Collection, | ||
| ContextManager, | ||
| Dict, | ||
| Generator, | ||
| Iterable, | ||
| 
          
            
          
           | 
    @@ -823,75 +824,110 @@ 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 _create_decorator( | ||
| func: Callable[P, R], | ||
| # TODO: What is the correct type for these `Any`? `P.args, P.kwargs` isn't allowed here | ||
| wrapping_logic: Callable[[Callable[P, R], Any, Any], ContextManager[None]], | ||
                
      
                  squahtx marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| ) -> Callable[P, R]: | ||
| """ | ||
| Decorator to trace a function with a custom opname. | ||
| 
     | 
||
| See the module's doc string for usage examples. | ||
| 
     | 
||
| Creates a decorator that is able to handle sync functions, async functions | ||
| (coroutines), and inlineDeferred from Twisted. | ||
                
       | 
||
| 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): | ||
| start_ts = time.time() | ||
| yield | ||
| end_ts = time.time() | ||
| duration = end_ts - start_ts | ||
| logger.info("%s took %s seconds", func.__name__, duration) | ||
| return _create_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] | ||
| 
     | 
||
| @wraps(func) | ||
| async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: | ||
                
      
                  squahtx marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| if inspect.iscoroutinefunction(func): | ||
| 
     | 
||
| @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] | ||
| 
     | 
||
| with wrapping_logic(func, *args, **kwargs): | ||
| return await func(*args, **kwargs) | ||
| 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__, | ||
| ) | ||
| scope = wrapping_logic(func, *args, **kwargs) | ||
| 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 | ||
| 
     | 
||
| return result | ||
| def err_back(result: R) -> R: | ||
| scope.__exit__(None, None, None) | ||
| return result | ||
| 
     | 
||
| except Exception as e: | ||
| scope.__exit__(type(e), None, e.__traceback__) | ||
| raise | ||
| result.addCallbacks(call_back, err_back) | ||
| 
     | 
||
| return _trace_inner # type: ignore[return-value] | ||
| 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__, | ||
| ) | ||
                
      
                  squahtx marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| return decorator | ||
| scope.__exit__(None, None, None) | ||
| 
     | 
||
| return result | ||
| 
     | 
||
| except Exception as e: | ||
| scope.__exit__(type(e), None, e.__traceback__) | ||
| raise | ||
| 
     | 
||
| return _wrapper # type: ignore[return-value] | ||
                
      
                  squahtx marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| 
     | 
||
| def trace_with_opname(opname: str) -> 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. | ||
| """ | ||
| 
     | 
||
| @contextlib.contextmanager | ||
| def _wrapping_logic(func: Callable[P, R], *args: P.args, **kwargs: P.kwargs): | ||
                
      
                  MadLittleMods marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| if opentracing is None: | ||
| return None | ||
                
      
                  MadLittleMods marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| scope = start_active_span(opname) | ||
| scope.__enter__() | ||
| try: | ||
| yield | ||
| except Exception as e: | ||
| scope.__exit__(type(e), None, e.__traceback__) | ||
| raise | ||
| finally: | ||
| scope.__exit__(None, None, None) | ||
| 
     | 
||
| def _decorator(func: Callable[P, R]): | ||
                
      
                  MadLittleMods marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| return _create_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,22 +936,22 @@ 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. | ||
| """ | ||
| 
     | 
||
| if not opentracing: | ||
| return func | ||
| 
     | 
||
| @wraps(func) | ||
| def _tag_args_inner(*args: P.args, **kwargs: P.kwargs) -> R: | ||
| @contextlib.contextmanager | ||
| def _wrapping_logic(func: Callable[P, R], *args: P.args, **kwargs: P.kwargs): | ||
| argspec = inspect.getfullargspec(func) | ||
| for i, arg in enumerate(argspec.args[1:]): | ||
                
      
                  MadLittleMods marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| set_tag("ARG_" + arg, str(args[i])) # type: ignore[index] | ||
| 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 _create_decorator(func, _wrapping_logic) | ||
| 
     | 
||
| 
     | 
||
| @contextlib.contextmanager | ||
| 
          
            
          
           | 
    ||
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this to
_decorate, or similar?In a sense, this method is the decorator: it takes
funcand returns a wrapped function with the same signature.The docstring could also do with rewording: "Decorates a function that is..."
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a descriptor we can add to it? I feel like
_create_decoratorand_decoratedon't properly describe that this does something you probably want to use for all decorators (handle any function regardless if sync/async). But I don't know how to encapsulate that into a name like_fancy_decorate(_safe_decorate)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also stuck thinking of a better name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_create_sync_async_decorator? It feels like it needs more than just_create_decoratorsince not all decorators need to care about this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@clokep Better than current at least 👍 What about
_custom_sync_async_decorator? "custom" to try to hint that you add your own business logic viawrapping_logic