-
Notifications
You must be signed in to change notification settings - Fork 635
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
Make tracer.start_as_current_span()
decorator work with async functions
#3270
Comments
tracer.start_as_current_span()
decorator work with async functions
…pentelemetry-python#3270 Signed-off-by: QuentinN42 <quentin@lieumont.fr>
Any news on this subject ? |
If you check the original #62 you'll find some workarounds. I think we need some consensus from the project maintainers if we want to go ahead with this feature request @open-telemetry/python-approvers. I'm in favor personally 🙂 |
Same here, would you like to implement this, @aabmass? |
I don't have time right now if someone else wants to take it |
I've successfully make it works with a little bit of hacking : def _with_span_sync(self, func: Callable) -> Callable:
"""Decorate sync functions."""
@wraps(func)
def func_wrapper(*args, **kwargs):
with self.start_as_current_span(func.__name__):
return func(*args, **kwargs)
return func_wrapper
def _with_span_async(self, func: Callable) -> Callable:
"""Decorate async functions."""
@wraps(func)
async def func_wrapper(*args, **kwargs):
with self.start_as_current_span(func.__name__):
return await func(*args, **kwargs)
return func_wrapper
def decorate(self, func: Callable) -> Callable:
# define if a function is async or not
CO_COROUTINE = 0x0080
if bool(func.__code__.co_flags & CO_COROUTINE):
return self._with_span_async(func)
return self._with_span_sync(func) If you overload the main trace api with this method, you can have it working with both sync and async traces. opentelemetry-python/opentelemetry-api/src/opentelemetry/trace/__init__.py Lines 269 to 275 in 3dfe224
Here is my code example : import asyncio
import time
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
# Setup console tracing :
# https://opentelemetry.io/docs/instrumentation/python/exporters/
resource = Resource(attributes={SERVICE_NAME: "your-service-name"})
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("diceroller.tracer")
@tracer.start_as_current_span("sync") # v1
# @tracer.decorate # v2
def sync() -> None:
time.sleep(1)
@tracer.start_as_current_span("async") # v1
# @tracer.decorate # v2
async def async_() -> None:
await asyncio.sleep(1)
async def main() -> None:
sync()
await async_()
if __name__ == "__main__":
asyncio.run(main()) Install the dependencies with : python3 -m venv venv
source ./venv/bin/activate
pip install opentelemetry-distro
python3 main.py If you just launch this program with the latest version of OTLP it will print you two traces (sync with If you paste my code inside the Here are my current changes : main...QuentinN42:opentelemetry-python:main |
Discuss in SIG. There are a few options:
1 adds a new This github search turns up a few cases where |
After some investigation, it seems possible to make the However, I was wondering if the API Tracer abstract class could be implemented by external libraries ? If so, I'm afraid my changes will break the API as it's removing the |
Yes it could be implemented by other SDKs. I'm not aware of any that exist.
I think it should be OK as long as the returned object matches the existing public protocol of |
import asyncio
import contextlib
import functools
import time
class AgnosticContextManager(contextlib._GeneratorContextManager):
def __call__(self, func):
if asyncio.iscoroutinefunction(func):
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
with self._recreate_cm():
return await func(*args, **kwargs)
return async_wrapper
else:
@functools.wraps(func)
def wrapper(*args, **kwargs):
with self._recreate_cm():
return func(*args, **kwargs)
return wrapper
def agnosticcontextmanager(func):
@functools.wraps(func)
def helper(*args, **kwds):
return AgnosticContextManager(func, args, kwds)
return helper
@agnosticcontextmanager
def start_as_current_span():
start_time = time.time()
yield
end_time = time.time()
print(f"Elapsed time: {end_time - start_time} seconds")
@start_as_current_span()
def sync_example():
time.sleep(0.01)
@start_as_current_span()
async def async_example():
await asyncio.sleep(0.01)
# Using examples
sync_example()
asyncio.run(async_example()) Here is a minimal fix to the |
Maybe we don't have to make tracer.start_as_current_span() decorator work with async functions,we can add this task to coroutine loop,code like this: import asyncio
from asyncio.queues import Queue
async def start(q :Queue):
await asyncio.sleep(1)
print("start")
await q.put(0)
async def end(q :Queue):
await q.get()
print("end")
def xx():
q=Queue()
asyncio.create_task(start(q))
print("xx")
asyncio.create_task(end(q))
async def main():
xx()
await asyncio.sleep(2)
print("main")
if __name__ == '__main__':
asyncio.run(main())
We can use an environment variable to control whether to use coroutines or multithreads. |
|
Originaly from #62 (comment)
Alternatives:
tracer.start_as_current_span_async()
Support async/await syntax #62 (comment). IMO this is confusing because it would only be a decorator unlikestart_as_current_span()
.The text was updated successfully, but these errors were encountered: