-
Notifications
You must be signed in to change notification settings - Fork 439
tracer.wrap does not work on generators correctly #5403
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I suspect this is because |
@P403n1x87 I think that is correct. The main issue here is that its hard for I think the best solution is to create a separate wrapped specifically for generators. I am writing what that would look like now but thought it would be good to get early feedback in the meanwhile? |
@MichaelAquilina Thank you for starting this discussion and starting a PR! It's been a while since I looked closely at Internally, we use wrapt for wrapping functions and objects. I wonder if we should be extending it's decorator. For example, here's one approach that doesn't hit the issue you are finding: import inspect
from ddtrace import tracer
import wrapt
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
with tracer.trace(wrapped.__name__):
if not inspect.isgeneratorfunction(wrapped):
return wrapped(*args, **kwargs)
yield wrapped(*args, **kwargs)
@wrapper
def foobar():
current_span = tracer.current_span()
current_span.set_tag("hello", "world")
yield 1
if __name__ == "__main__":
assert inspect.isgeneratorfunction(foobar)
print("main")
list(foobar()) Would this resolve your issue? |
@majorgreys that is actually similar to the approach I started off with, but the problem is that once you include a Unless I used this code to test it out: import inspect
from ddtrace import tracer
import wrapt
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
with tracer.trace(wrapped.__name__):
if not inspect.isgeneratorfunction(wrapped):
return wrapped(*args, **kwargs)
yield wrapped(*args, **kwargs)
@wrapper
def foobar():
current_span = tracer.current_span()
current_span.set_tag("hello", "world")
return [1]
@wrapper
def bazbang():
current_span = tracer.current_span()
current_span.set_tag("hello", "world")
yield 1
def main():
print(foobar())
print(list(bazbang()))
if __name__ == "__main__":
main() The output is:
In this case, the code doesnt even seem to return the expected type for either so I guess there is a bug in the implementation, bt notice how both cases are generators. I implemented something similar in This is the diff: diff --git a/ddtrace/tracer.py b/ddtrace/tracer.py
index 4122aae3..038cfee3 100644
--- a/ddtrace/tracer.py
+++ b/ddtrace/tracer.py
@@ -1,3 +1,4 @@
+import inspect
import functools
import json
import logging
@@ -980,6 +981,11 @@ class Tracer(object):
span_type=span_type,
)
+ if inspect.isgeneratorfunction(f):
+ # otherwise fallback to a default tracing
+ with self.trace(span_name, service=service, resource=resource, span_type=span_type):
+ yield from f(*args, **kwargs)
+
# otherwise fallback to a default tracing
with self.trace(span_name, service=service, resource=resource, span_type=span_type):
return f(*args, **kwargs) and this is what was returned
|
Manually doing what the Github Action was supposed to do (fixed in #7835): This issue has been automatically closed after six months of inactivity. If it's a feature request, it has been added to the maintainers' internal backlog and will be included in an upcoming round of feature prioritization. Please comment or reopen if you think this issue was closed in error. |
We see a similar issue. Anything that happens inside generator functions fails to get traced. Specifically the SQL queries done with the Is there no workaround for this issue? This is our SQL store code:
I can see the manual traces ( |
Wanted to follow up on this! We use generators often in the context of streaming LLM token results back to users, and I think that is a very common use case. |
# PR Description **Fix support for wrapping generator functions with `tracer.wrap()` and ensure they generate traces in Datadog** Related issue: [#5403](#5403) --- ## Summary of the problem Currently, when a generator function is wrapped with `tracer.wrap()`, two major issues arise: 1. **Accessing the current span fails:** Inside the wrapped generator, `tracer.current_span()` returns `None`. As a result, any attempt to set tags or interact with the span raises: ``` AttributeError: 'NoneType' object has no attribute 'set_tag' ``` Example: ```python @tracer.wrap() def foobar(): current_span = tracer.current_span() current_span.set_tag("hello", "world") yield 1 ``` Calling `list(foobar())` → crashes with `AttributeError`. 2. **Traces are reported to Datadog with incorrect duration:** Even if the generator runs without explicit span interaction, traces emitted to Datadog do not correctly report the execution time of the function. This is because the `tracer.wrap()` decorator does not maintain the span context during generator iteration (`next()` or `async for`), so the span gets opened and closed at the same time. --- ## Root cause - The `wrap()` logic does not correctly handle Python generators (`def ... yield`) or async generators (`async def ... yield`). - Without this, both local span interactions (`current_span()`) and backend reporting (sending traces to Datadog) break. --- ## Proposed fix This PR updates the `tracer.wrap()` decorator to: - Add proper handling for **sync generators**: - Ensure `tracer.current_span()` is available. - Finalize the span after the generator is exhausted or on error. - Report traces to Datadog as expected. - Add dedicated support for **async generators**: - Use an `async for` wrapper. - Maintain the tracing context. With this change: - Spans inside generators work (`current_span()` is valid). - Traces from generator functions are correctly sent to Datadog. --- ## How to reproduce + verify the fix Minimal reproducible example: ```python @tracer.wrap() def foobar(): current_span = tracer.current_span() current_span.set_tag("hello", "world") yield 1 assert list(foobar()) == [1] ``` **Expected result:** - No errors. - Span is created and tagged. - Trace is visible in Datadog with the tag `hello: world`. **Added test cases:** - Sync generator with span tags. - Async generator with span tags. - Backward compatibility for sync/async functions. --- ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Brett Langdon <[email protected]> Co-authored-by: Brett Langdon <[email protected]>
Uh oh!
There was an error while loading. Please reload this page.
Summary of problem
Generator functions wrapped by
tracer.wrap()
do not seem to work correctly.In particular, trying to get the current span inside the wrapped function does not seem to work
Which version of dd-trace-py are you using?
1.6.1 (also tried on latest 1.10.1)
Which version of pip are you using?
poetry
Which libraries and their versions are you using?
`pip freeze`
How can we reproduce your problem?
Here is a minimal code example:
What is the result that you get?
Calling
list(foobar())
results in the following error:What is the result that you expected?
current_span is found and able to have tags set.
The text was updated successfully, but these errors were encountered: