This repository has been 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 5 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 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 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,103 @@ 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.
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic is generalized from our existing We now re-use it for both |
||
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.
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 | ||
|
||
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__, | ||
) | ||
squahtx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return result | ||
scope.__exit__(None, None, None) | ||
|
||
except Exception as e: | ||
scope.__exit__(type(e), None, e.__traceback__) | ||
raise | ||
return result | ||
|
||
return _trace_inner # type: ignore[return-value] | ||
except Exception as e: | ||
scope.__exit__(type(e), None, e.__traceback__) | ||
raise | ||
|
||
return decorator | ||
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.
Show resolved
Hide resolved
|
||
if opentracing is None: | ||
return None | ||
MadLittleMods marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
with start_active_span(opname): | ||
yield | ||
|
||
def _decorator(func: Callable[P, R]): | ||
MadLittleMods marked this conversation as resolved.
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 +929,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.
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 | ||
|
This file contains 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
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
func
and returns a wrapped function with the same signature.The docstring could also do with rewording: "Decorates a function that is..."
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_decorator
and_decorate
don'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_decorator
since 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