Skip to content

Commit cfb61dc

Browse files
committed
refactor: Asyncio instrumentation
Signed-off-by: Paulo Vital <[email protected]>
1 parent 3a0cbdb commit cfb61dc

File tree

5 files changed

+77
-36
lines changed

5 files changed

+77
-36
lines changed

src/instana/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def boot_agent():
163163

164164
# Import & initialize instrumentation
165165
from instana.instrumentation import (
166-
# asyncio, # noqa: F401
166+
asyncio, # noqa: F401
167167
boto3_inst, # noqa: F401
168168
# cassandra_inst, # noqa: F401
169169
# couchbase_inst, # noqa: F401

src/instana/configurator.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@
55
This file contains a config object that will hold configuration options for the package.
66
Defaults are set and can be overridden after package load.
77
"""
8-
from .util import DictionaryOfStan
8+
9+
from instana.util import DictionaryOfStan
910

1011
# La Protagonista
1112
config = DictionaryOfStan()
1213

1314

1415
# This option determines if tasks created via asyncio (with ensure_future or create_task) will
1516
# automatically carry existing context into the created task.
16-
config['asyncio_task_context_propagation']['enabled'] = False
17-
18-
19-
20-
17+
config["asyncio_task_context_propagation"]["enabled"] = False

src/instana/instrumentation/asyncio.py

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,89 @@
22
# (c) Copyright Instana Inc. 2019
33

44

5+
import time
6+
from contextlib import contextmanager
7+
from typing import Any, Callable, Dict, Iterator, Tuple
8+
59
import wrapt
6-
from opentracing.scope_managers.constants import ACTIVE_ATTR
7-
from opentracing.scope_managers.contextvars import no_parent_scope
10+
from opentelemetry.trace import use_span
11+
from opentelemetry.trace.status import StatusCode
812

9-
from ..configurator import config
10-
from ..log import logger
11-
from ..singletons import async_tracer
13+
from instana.configurator import config
14+
from instana.log import logger
15+
from instana.span.span import InstanaSpan
16+
from instana.util.traceutils import get_tracer_tuple, tracing_is_off
1217

1318
try:
1419
import asyncio
1520

1621
@wrapt.patch_function_wrapper("asyncio", "ensure_future")
17-
def ensure_future_with_instana(wrapped, instance, argv, kwargs):
18-
if config["asyncio_task_context_propagation"]["enabled"] is False:
19-
with no_parent_scope():
20-
return wrapped(*argv, **kwargs)
21-
22-
scope = async_tracer.scope_manager.active
23-
task = wrapped(*argv, **kwargs)
24-
25-
if scope is not None:
26-
setattr(task, ACTIVE_ATTR, scope)
22+
def ensure_future_with_instana(
23+
wrapped: Callable[..., asyncio.ensure_future],
24+
instance: object,
25+
argv: Tuple[object, Tuple[object, ...]],
26+
kwargs: Dict[str, Any],
27+
) -> object:
28+
if (
29+
not config["asyncio_task_context_propagation"]["enabled"]
30+
or tracing_is_off()
31+
):
32+
return wrapped(*argv, **kwargs)
2733

28-
return task
34+
with _start_as_current_async_span() as span:
35+
try:
36+
span.set_status(StatusCode.OK)
37+
return wrapped(*argv, **kwargs)
38+
except Exception as exc:
39+
logger.debug(f"asyncio ensure_future_with_instana error: {exc}")
2940

3041
if hasattr(asyncio, "create_task"):
3142

3243
@wrapt.patch_function_wrapper("asyncio", "create_task")
33-
def create_task_with_instana(wrapped, instance, argv, kwargs):
34-
if config["asyncio_task_context_propagation"]["enabled"] is False:
35-
with no_parent_scope():
44+
def create_task_with_instana(
45+
wrapped: Callable[..., asyncio.create_task],
46+
instance: object,
47+
argv: Tuple[object, Tuple[object, ...]],
48+
kwargs: Dict[str, Any],
49+
) -> object:
50+
if (
51+
not config["asyncio_task_context_propagation"]["enabled"]
52+
or tracing_is_off()
53+
):
54+
return wrapped(*argv, **kwargs)
55+
56+
with _start_as_current_async_span() as span:
57+
try:
58+
span.set_status(StatusCode.OK)
3659
return wrapped(*argv, **kwargs)
60+
except Exception as exc:
61+
logger.debug(f"asyncio create_task_with_instana error: {exc}")
3762

38-
scope = async_tracer.scope_manager.active
39-
task = wrapped(*argv, **kwargs)
63+
@contextmanager
64+
def _start_as_current_async_span() -> Iterator[InstanaSpan]:
65+
"""
66+
Creates and yield a special InstanaSpan to only propagate the Asyncio
67+
context.
68+
"""
69+
tracer, parent_span, _ = get_tracer_tuple()
70+
parent_context = parent_span.get_span_context() if parent_span else None
4071

41-
if scope is not None:
42-
setattr(task, ACTIVE_ATTR, scope)
72+
_time = time.time_ns()
4373

44-
return task
74+
span = InstanaSpan(
75+
name="asyncio",
76+
context=parent_context,
77+
span_processor=tracer.span_processor,
78+
start_time=_time,
79+
end_time=_time,
80+
)
81+
with use_span(
82+
span,
83+
end_on_exit=False,
84+
record_exception=False,
85+
set_status_on_exception=False,
86+
) as span:
87+
yield span
4588

4689
logger.debug("Instrumenting asyncio")
4790
except ImportError:

src/instana/span/kind.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
EXIT_KIND = ("exit", "client", "producer", SpanKind.CLIENT, SpanKind.PRODUCER)
88

9-
LOCAL_SPANS = ("render", SpanKind.INTERNAL)
9+
LOCAL_SPANS = ("asyncio", "render", SpanKind.INTERNAL)
1010

1111
HTTP_SPANS = (
1212
"aiohttp-client",

src/instana/util/traceutils.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ def extract_custom_headers(tracing_span, headers) -> None:
2222

2323
def get_active_tracer() -> Optional[InstanaTracer]:
2424
try:
25-
# ToDo: Might have to add additional stuff when testing with async and tornado tracer
2625
current_span = get_current_span()
27-
if current_span and current_span.is_recording():
28-
return tracer
29-
else:
26+
if current_span:
27+
# asyncio Spans are used as NonRecording Spans solely for context propagation
28+
if current_span.is_recording() or current_span.name == "asyncio":
29+
return tracer
3030
return None
31+
return None
3132
except Exception:
3233
# Do not try to log this with instana, as there is no active tracer and there will be an infinite loop at least
3334
# for PY2

0 commit comments

Comments
 (0)