Skip to content

Commit 0fc505f

Browse files
committed
brute force migration to core api
1 parent 4ee9deb commit 0fc505f

File tree

3 files changed

+133
-64
lines changed

3 files changed

+133
-64
lines changed

ddtrace/_trace/trace_handlers.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,87 @@ def _on_django_cache(
595595
_finish_span(ctx, exc_info)
596596

597597

598+
def _on_requests_send_start(ctx: core.ExecutionContext) -> None:
599+
span = _start_span(ctx)
600+
if not span:
601+
return
602+
603+
integration_config = ctx.get_item("integration_config")
604+
hostname = ctx.get_item("hostname")
605+
606+
# integration_config can be either a dict (from pin._config) or ConfigNamespace (from config.requests)
607+
split_by_domain = (
608+
integration_config.get("split_by_domain")
609+
if isinstance(integration_config, dict)
610+
else integration_config.split_by_domain
611+
)
612+
distributed_tracing = (
613+
integration_config.get("distributed_tracing", True)
614+
if isinstance(integration_config, dict)
615+
else integration_config.distributed_tracing
616+
)
617+
618+
if split_by_domain and hostname:
619+
span.service = hostname
620+
621+
if distributed_tracing:
622+
request = ctx.get_item("request")
623+
HTTPPropagator.inject(span.context, request.headers)
624+
625+
626+
def _on_requests_send_end(
627+
ctx: core.ExecutionContext,
628+
exc_info: Tuple[Optional[type], Optional[BaseException], Optional[TracebackType]],
629+
) -> None:
630+
span = ctx.span
631+
if not span:
632+
_finish_span(ctx, exc_info)
633+
return
634+
635+
(
636+
request,
637+
request_method,
638+
request_url,
639+
host_without_port,
640+
response,
641+
) = ctx.get_items(["request", "request_method", "request_url", "host_without_port", "response"])
642+
643+
def _extract_query_string(uri):
644+
start = uri.find("?") + 1
645+
if start == 0:
646+
return None
647+
end = len(uri)
648+
j = uri.rfind("#", 0, end)
649+
if j != -1:
650+
end = j
651+
if end <= start:
652+
return None
653+
return uri[start:end]
654+
655+
status_code = None
656+
response_headers = {}
657+
if response is not None:
658+
status_code = response.status_code
659+
response_headers = dict(getattr(response, "headers", {}))
660+
661+
if request is not None:
662+
# For set_http_meta, we need the global config.requests which has all the HTTP tagging settings
663+
# The pin's integration_config dict may not have all attributes
664+
trace_utils.set_http_meta(
665+
span,
666+
config.requests,
667+
request_headers=request.headers,
668+
response_headers=response_headers,
669+
method=request_method,
670+
url=request_url,
671+
target_host=host_without_port,
672+
status_code=status_code,
673+
query=_extract_query_string(request_url) if request_url else None,
674+
)
675+
676+
_finish_span(ctx, exc_info)
677+
678+
598679
def _on_django_func_wrapped(_unused1, _unused2, _unused3, ctx, ignored_excs):
599680
if ignored_excs:
600681
for exc in ignored_excs:
@@ -1524,5 +1605,9 @@ def listen():
15241605
# Special/extra handling before calling _finish_span
15251606
core.on("context.ended.django.cache", _on_django_cache)
15261607

1608+
# Requests handlers (also registered conditionally in patch.py)
1609+
core.on("context.started.requests.send", _on_requests_send_start)
1610+
core.on("context.ended.requests.send", _on_requests_send_end)
1611+
15271612

15281613
listen()

ddtrace/contrib/internal/requests/connection.py

Lines changed: 45 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,20 @@
55

66
import requests
77

8-
import ddtrace
98
from ddtrace import config
10-
from ddtrace._trace.pin import Pin
11-
from ddtrace.constants import _SPAN_MEASURED_KEY
129
from ddtrace.constants import SPAN_KIND
1310
from ddtrace.contrib import trace_utils
1411
from ddtrace.contrib.internal.trace_utils import _sanitized_url
1512
from ddtrace.ext import SpanKind
1613
from ddtrace.ext import SpanTypes
14+
from ddtrace.internal import core
1715
from ddtrace.internal.constants import COMPONENT
1816
from ddtrace.internal.constants import USER_AGENT_HEADER
1917
from ddtrace.internal.logger import get_logger
2018
from ddtrace.internal.opentelemetry.constants import OTLP_EXPORTER_HEADER_IDENTIFIER
2119
from ddtrace.internal.schema import schematize_url_operation
2220
from ddtrace.internal.schema.span_attribute_schema import SpanDirection
23-
from ddtrace.internal.settings.asm import config as asm_config
2421
from ddtrace.internal.utils import get_argument_value
25-
from ddtrace.propagation.http import HTTPPropagator
2622

2723

2824
log = get_logger(__name__)
@@ -69,18 +65,9 @@ def _extract_query_string(uri):
6965
return uri[start:end]
7066

7167

72-
def _wrap_send(func, instance, args, kwargs):
68+
@trace_utils.with_traced_module
69+
def _wrap_send(_requests_mod, pin, func, instance, args, kwargs):
7370
"""Trace the `Session.send` instance method"""
74-
# TODO[manu]: we already offer a way to provide the Global Tracer
75-
# and is ddtrace.tracer; it's used only inside our tests and can
76-
# be easily changed by providing a TracingTestCase that sets common
77-
# tracing functionalities.
78-
tracer = getattr(instance, "datadog_tracer", ddtrace.tracer)
79-
80-
# skip if tracing is not enabled
81-
if not tracer.enabled and not asm_config._apm_opt_out:
82-
return func(*args, **kwargs)
83-
8471
request = get_argument_value(args, kwargs, 0, "request")
8572
if not request or is_otlp_export(request):
8673
return func(*args, **kwargs)
@@ -92,59 +79,55 @@ def _wrap_send(func, instance, args, kwargs):
9279
hostname, path = _extract_hostname_and_path(url)
9380
host_without_port = hostname.split(":")[0] if hostname is not None else None
9481

95-
cfg: Dict[str, Any] = {}
96-
pin = Pin.get_from(instance)
97-
if pin:
98-
cfg = pin._config
82+
parsed_uri = parse.urlparse(url)
9983

100-
service = None
101-
if cfg["split_by_domain"] and hostname:
102-
service = hostname
103-
if service is None:
104-
service = cfg.get("service", None)
105-
if service is None:
106-
service = cfg.get("service_name", None)
107-
if service is None:
108-
service = trace_utils.ext_service(None, config.requests)
84+
# Support legacy datadog_tracer attribute for backwards compatibility (used in tests)
85+
# If not set, use ddtrace.tracer to respect override_global_tracer
86+
tracer = getattr(instance, "datadog_tracer", None)
87+
if tracer is None:
88+
import ddtrace
10989

110-
operation_name = schematize_url_operation("requests.request", protocol="http", direction=SpanDirection.OUTBOUND)
111-
with tracer.trace(operation_name, service=service, resource=f"{method} {path}", span_type=SpanTypes.HTTP) as span:
112-
span._set_tag_str(COMPONENT, config.requests.integration_name)
90+
tracer = ddtrace.tracer
11391

114-
# set span.kind to the type of operation being performed
115-
span._set_tag_str(SPAN_KIND, SpanKind.CLIENT)
92+
# Check if tracing is disabled on the tracer (for APM opt-out tests)
93+
from ddtrace.internal.settings.asm import config as asm_config
11694

117-
# PERF: avoid setting via Span.set_tag
118-
span.set_metric(_SPAN_MEASURED_KEY, 1)
95+
if not tracer.enabled and not asm_config._apm_opt_out:
96+
return func(*args, **kwargs)
11997

120-
# propagate distributed tracing headers
121-
if cfg.get("distributed_tracing"):
122-
HTTPPropagator.inject(span.context, request.headers)
98+
# Use pin's config if available, otherwise fall back to global config
99+
integration_config = pin._config if pin else config.requests
123100

124-
response = response_headers = None
101+
# Determine service name (check pin config for "service" or "service_name")
102+
service = None
103+
if integration_config and isinstance(integration_config, dict):
104+
service = integration_config.get("service") or integration_config.get("service_name")
105+
if service is None:
106+
service = trace_utils.ext_service(pin, config.requests)
107+
108+
with core.context_with_data(
109+
"requests.send",
110+
span_name=schematize_url_operation("requests.request", protocol="http", direction=SpanDirection.OUTBOUND),
111+
pin=pin,
112+
tracer=tracer,
113+
service=service,
114+
resource=f"{method} {path}",
115+
span_type=SpanTypes.HTTP,
116+
integration_config=integration_config,
117+
measured=True,
118+
tags={COMPONENT: config.requests.integration_name, SPAN_KIND: SpanKind.CLIENT},
119+
request=request,
120+
request_url=url,
121+
request_method=method,
122+
request_headers=request.headers,
123+
hostname=hostname,
124+
path=path,
125+
host_without_port=host_without_port,
126+
parsed_uri=parsed_uri,
127+
) as ctx:
128+
response = None
125129
try:
126130
response = func(*args, **kwargs)
127131
return response
128132
finally:
129-
try:
130-
status = None
131-
if response is not None:
132-
status = response.status_code
133-
# Storing response headers in the span.
134-
# Note that response.headers is not a dict, but an iterable
135-
# requests custom structure, that we convert to a dict
136-
response_headers = dict(getattr(response, "headers", {}))
137-
138-
trace_utils.set_http_meta(
139-
span,
140-
config.requests,
141-
request_headers=request.headers,
142-
response_headers=response_headers,
143-
method=method,
144-
url=request.url,
145-
target_host=host_without_port,
146-
status_code=status,
147-
query=_extract_query_string(url),
148-
)
149-
except Exception:
150-
log.debug("requests: error adding tags", exc_info=True)
133+
ctx.set_item("response", response)

ddtrace/contrib/internal/requests/patch.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
config._add(
2020
"requests",
2121
{
22+
"tracing_enabled": asbool(os.getenv("DD_TRACE_REQUESTS_ENABLED", True)),
2223
"distributed_tracing": asbool(os.getenv("DD_REQUESTS_DISTRIBUTED_TRACING", default=True)),
2324
"split_by_domain": asbool(os.getenv("DD_REQUESTS_SPLIT_BY_DOMAIN", default=False)),
2425
"default_http_tag_query_string": config._http_client_tag_query_string,
@@ -27,7 +28,7 @@
2728
)
2829

2930
# always patch our `TracedSession` when imported
30-
_w(TracedSession, "send", _wrap_send)
31+
_w(TracedSession, "send", _wrap_send(requests))
3132
Pin(_config=config.requests).onto(TracedSession)
3233

3334

@@ -45,7 +46,7 @@ def patch():
4546
return
4647
requests.__datadog_patch = True
4748

48-
_w("requests", "Session.send", _wrap_send)
49+
_w("requests", "Session.send", _wrap_send(requests))
4950
# IAST needs to wrap this function because `Session.send` is too late
5051
if asm_config._load_modules:
5152
from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request

0 commit comments

Comments
 (0)