diff --git a/.riot/requirements/13c3933.txt b/.riot/requirements/13c3933.txt new file mode 100644 index 00000000000..7e21f22718f --- /dev/null +++ b/.riot/requirements/13c3933.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/13c3933.in +# +anyio==4.14.2 +attrs==26.1.0 +certifi==2026.7.22 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.0.0 +httpx2==2.0.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +typing-extensions==4.16.0 diff --git a/.riot/requirements/143e698.txt b/.riot/requirements/143e698.txt new file mode 100644 index 00000000000..2d2e5ada1e7 --- /dev/null +++ b/.riot/requirements/143e698.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/143e698.in +# +anyio==4.14.2 +attrs==26.1.0 +backports-asyncio-runner==1.2.0 +coverage[toml]==7.15.4 +exceptiongroup==1.3.1 +h11==0.16.0 +httpcore2==2.12.0 +httpx2==2.12.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +tomli==2.4.1 +truststore==0.10.4 +typing-extensions==4.16.0 diff --git a/.riot/requirements/1e7c279.txt b/.riot/requirements/1e7c279.txt new file mode 100644 index 00000000000..7af0d0767a7 --- /dev/null +++ b/.riot/requirements/1e7c279.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e7c279.in +# +anyio==4.14.2 +attrs==26.1.0 +backports-asyncio-runner==1.2.0 +certifi==2026.7.22 +coverage[toml]==7.15.4 +exceptiongroup==1.3.1 +h11==0.16.0 +httpcore2==2.0.0 +httpx2==2.0.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +tomli==2.4.1 +typing-extensions==4.16.0 diff --git a/.riot/requirements/2a0ccf2.txt b/.riot/requirements/2a0ccf2.txt new file mode 100644 index 00000000000..963fb40b1b1 --- /dev/null +++ b/.riot/requirements/2a0ccf2.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2a0ccf2.in +# +anyio==4.14.2 +attrs==26.1.0 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.12.0 +httpx2==2.12.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +truststore==0.10.4 diff --git a/.riot/requirements/a77aa22.txt b/.riot/requirements/a77aa22.txt new file mode 100644 index 00000000000..e5725f8d829 --- /dev/null +++ b/.riot/requirements/a77aa22.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a77aa22.in +# +anyio==4.14.2 +attrs==26.1.0 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.12.0 +httpx2==2.12.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +truststore==0.10.4 +typing-extensions==4.16.0 diff --git a/.riot/requirements/da479b1.txt b/.riot/requirements/da479b1.txt new file mode 100644 index 00000000000..a1981257967 --- /dev/null +++ b/.riot/requirements/da479b1.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.14 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/da479b1.in +# +anyio==4.14.2 +attrs==26.1.0 +certifi==2026.7.22 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.0.0 +httpx2==2.0.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/e49b70d.txt b/.riot/requirements/e49b70d.txt new file mode 100644 index 00000000000..a7f42aa3b86 --- /dev/null +++ b/.riot/requirements/e49b70d.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.14 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e49b70d.in +# +anyio==4.14.2 +attrs==26.1.0 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.12.0 +httpx2==2.12.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +truststore==0.10.4 diff --git a/.riot/requirements/e9af4c9.txt b/.riot/requirements/e9af4c9.txt new file mode 100644 index 00000000000..fd23c4e5002 --- /dev/null +++ b/.riot/requirements/e9af4c9.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e9af4c9.in +# +anyio==4.14.2 +attrs==26.1.0 +certifi==2026.7.22 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.0.0 +httpx2==2.0.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +typing-extensions==4.16.0 diff --git a/.riot/requirements/f2c3ec9.txt b/.riot/requirements/f2c3ec9.txt new file mode 100644 index 00000000000..5959d5d092b --- /dev/null +++ b/.riot/requirements/f2c3ec9.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f2c3ec9.in +# +anyio==4.14.2 +attrs==26.1.0 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.12.0 +httpx2==2.12.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 +truststore==0.10.4 +typing-extensions==4.16.0 diff --git a/.riot/requirements/f38b0e1.txt b/.riot/requirements/f38b0e1.txt new file mode 100644 index 00000000000..962e49c698d --- /dev/null +++ b/.riot/requirements/f38b0e1.txt @@ -0,0 +1,33 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f38b0e1.in +# +anyio==4.12.1 +attrs==26.1.0 +backports-asyncio-runner==1.2.0 +certifi==2026.7.22 +coverage[toml]==7.10.7 +exceptiongroup==1.3.1 +h11==0.16.0 +httpcore2==2.0.0 +httpx2==2.0.0 +hypothesis==6.45.0 +idna==3.19 +importlib-metadata==8.7.1 +iniconfig==2.1.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==8.4.2 +pytest-asyncio==1.2.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +sortedcontainers==2.4.0 +tomli==2.4.1 +typing-extensions==4.16.0 +zipp==3.23.1 diff --git a/.riot/requirements/fd33cd8.txt b/.riot/requirements/fd33cd8.txt new file mode 100644 index 00000000000..044b2f6c0ff --- /dev/null +++ b/.riot/requirements/fd33cd8.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/fd33cd8.in +# +anyio==4.14.2 +attrs==26.1.0 +certifi==2026.7.22 +coverage[toml]==7.15.4 +h11==0.16.0 +httpcore2==2.0.0 +httpx2==2.0.0 +hypothesis==6.45.0 +idna==3.19 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==26.3 +pluggy==1.6.0 +pygments==2.21.0 +pytest==9.1.1 +pytest-asyncio==1.4.0 +pytest-cov==7.1.0 +pytest-mock==3.15.1 +pytest-randomly==4.1.0 +sortedcontainers==2.4.0 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index ab5bc9dcd2d..f281544067b 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -47,6 +47,7 @@ "gevent": True, "graphql": True, "grpc": True, + "httpx2": True, "httpx": True, "kafka": True, "langgraph": True, diff --git a/ddtrace/contrib/internal/httpx/patch.py b/ddtrace/contrib/internal/httpx/patch.py index db974c69dc5..1f9327ec3c1 100644 --- a/ddtrace/contrib/internal/httpx/patch.py +++ b/ddtrace/contrib/internal/httpx/patch.py @@ -11,10 +11,8 @@ from ddtrace.contrib._events.http_client import HttpClientEvents from ddtrace.contrib._events.http_client import HttpClientRequestEvent from ddtrace.contrib._events.http_client import HttpClientSendEvent -from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanKind from ddtrace.internal import core -from ddtrace.internal.compat import ensure_binary from ddtrace.internal.compat import ensure_text from ddtrace.internal.constants import COMPONENT from ddtrace.internal.settings import env @@ -23,6 +21,7 @@ from ddtrace.internal.utils.version import parse_version from ddtrace.internal.utils.wrappers import unwrap as _u +from .utils import httpx_get_service_name from .utils import httpx_url_to_str @@ -48,18 +47,6 @@ def _supported_versions() -> dict[str, str]: return {"httpx": ">=0.25"} -def _get_service_name(request: httpx.Request) -> Optional[str]: - if config.httpx.split_by_domain: - if hasattr(request.url, "netloc"): - return ensure_text(request.url.netloc, errors="backslashreplace") - - service = ensure_binary(request.url.host) - if request.url.port: - service += b":" + ensure_binary(str(request.url.port)) - return ensure_text(service, errors="backslashreplace") - return ext_service(None, config.httpx) - - def _wrapped_sync_send_single_request( wrapped: "BoundFunctionWrapper[..., httpx.Response]", instance: httpx.Client, @@ -121,7 +108,7 @@ async def _wrapped_async_send( with core.context_with_event( HttpClientRequestEvent( http_operation="http.request", - service=_get_service_name(req), + service=httpx_get_service_name(req, config.httpx), component=config.httpx.integration_name, request_method=req.method, request_headers=req.headers, @@ -153,7 +140,7 @@ def _wrapped_sync_send( HttpClientRequestEvent( component=config.httpx.integration_name, http_operation="http.request", - service=_get_service_name(req), + service=httpx_get_service_name(req, config.httpx), request_method=req.method, request_headers=req.headers, integration_config=config.httpx, diff --git a/ddtrace/contrib/internal/httpx/utils.py b/ddtrace/contrib/internal/httpx/utils.py index 6c6d690c51c..3ae3bf692a9 100644 --- a/ddtrace/contrib/internal/httpx/utils.py +++ b/ddtrace/contrib/internal/httpx/utils.py @@ -1,7 +1,15 @@ +from typing import TYPE_CHECKING +from typing import Optional + +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.internal.compat import ensure_binary from ddtrace.internal.compat import ensure_text +if TYPE_CHECKING: + from ddtrace.internal.settings.integration import IntegrationConfig + + def httpx_url_to_str(url) -> str: """ Helper to convert the httpx.URL parts from bytes to a str @@ -16,3 +24,15 @@ def httpx_url_to_str(url) -> str: url += raw_path return ensure_text(url) + + +def httpx_get_service_name(request, integration_config: "IntegrationConfig") -> Optional[str]: + if integration_config.split_by_domain: + if hasattr(request.url, "netloc"): + return ensure_text(request.url.netloc, errors="backslashreplace") + + service = ensure_binary(request.url.host) + if request.url.port: + service += b":" + ensure_binary(str(request.url.port)) + return ensure_text(service, errors="backslashreplace") + return ext_service(None, integration_config) diff --git a/ddtrace/contrib/internal/httpx2/__init__.py b/ddtrace/contrib/internal/httpx2/__init__.py new file mode 100644 index 00000000000..5ee627a6483 --- /dev/null +++ b/ddtrace/contrib/internal/httpx2/__init__.py @@ -0,0 +1,57 @@ +""" +The httpx2__ integration traces all HTTP requests made with the ``httpx2`` +library. + +Enabling +~~~~~~~~ + +The ``httpx2`` integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Alternatively, use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(httpx2=True) + + # use httpx2 like usual + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.httpx2['service'] + + The default service name for ``httpx2`` requests. + By default the ``httpx2`` integration will not define a service name and inherit + its service name from its parent span. + + If you are making calls to uninstrumented third party applications you can + set this setting or use the ``ddtrace.config.httpx2['split_by_domain']`` setting. + + This option can also be set with the ``DD_HTTPX2_SERVICE`` environment + variable. + + Default: ``None`` + + +.. py:data:: ddtrace.config.httpx2['distributed_tracing'] + + Whether or not to inject distributed tracing headers into requests. + + Default: ``True`` + + +.. py:data:: ddtrace.config.httpx2['split_by_domain'] + + Whether or not to use the domain name of requests as the service name. + + This setting takes precedence over ``ddtrace.config.httpx2['service']``. + + Default: ``False`` + +:ref:`Headers tracing ` is supported for this integration. + +:ref:`HTTP Tagging ` is supported for this integration. + +.. __: https://github.com/pydantic/httpx2 +""" diff --git a/ddtrace/contrib/internal/httpx2/patch.py b/ddtrace/contrib/internal/httpx2/patch.py new file mode 100644 index 00000000000..1b21f9cec1e --- /dev/null +++ b/ddtrace/contrib/internal/httpx2/patch.py @@ -0,0 +1,180 @@ +from typing import Any +from typing import Awaitable +from typing import Optional + +import httpx2 +from wrapt import BoundFunctionWrapper +from wrapt import wrap_function_wrapper as _w + +from ddtrace import config +from ddtrace.contrib._events.http_client import HttpClientEvents +from ddtrace.contrib._events.http_client import HttpClientRequestEvent +from ddtrace.contrib._events.http_client import HttpClientSendEvent +from ddtrace.contrib.internal.httpx.utils import httpx_get_service_name +from ddtrace.contrib.internal.httpx.utils import httpx_url_to_str +from ddtrace.internal import core +from ddtrace.internal.compat import ensure_text +from ddtrace.internal.settings import env +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.wrappers import unwrap as _u + + +def get_version() -> str: + return getattr(httpx2, "__version__", "") + + +config._add( # type: ignore[no-untyped-call] + "httpx2", + { + "distributed_tracing": asbool(env.get("DD_HTTPX2_DISTRIBUTED_TRACING", default=True)), + "split_by_domain": asbool(env.get("DD_HTTPX2_SPLIT_BY_DOMAIN", default=False)), + "default_http_tag_query_string": config._http_client_tag_query_string, + }, +) + + +def _supported_versions() -> dict[str, str]: + return {"httpx2": ">=2.0"} + + +def _wrapped_sync_send_single_request( + wrapped: "BoundFunctionWrapper[..., httpx2.Response]", + instance: httpx2.Client, + args: tuple[httpx2.Request], + kwargs: dict[str, Any], +) -> Optional[httpx2.Response]: + request: httpx2.Request = get_argument_value(args, kwargs, 0, "request") + + with core.context_with_event( + event=HttpClientSendEvent( + request_url=httpx_url_to_str(request.url), + request_method=request.method, + request_headers=request.headers, + request_body=lambda: request.content, + ), + context_name_override=HttpClientEvents.HTTPX_SEND_REQUEST.value, + ) as ctx: + response = None + try: + response = wrapped(*args, **kwargs) + return response + finally: + if response is not None: + ctx.event.set_response(response) + return None + + +async def _wrapped_async_send_single_request( + wrapped: "BoundFunctionWrapper[..., Awaitable[httpx2.Response]]", + instance: httpx2.AsyncClient, + args: tuple[httpx2.Request], + kwargs: dict[str, Any], +) -> Optional[httpx2.Response]: + request: httpx2.Request = get_argument_value(args, kwargs, 0, "request") + + with core.context_with_event( + event=HttpClientSendEvent( + request_url=httpx_url_to_str(request.url), + request_method=request.method, + request_headers=request.headers, + request_body=lambda: request.content, + ), + context_name_override=HttpClientEvents.HTTPX_SEND_REQUEST.value, + ) as ctx: + response = None + try: + response = await wrapped(*args, **kwargs) + return response + finally: + if response is not None: + ctx.event.set_response(response) + return None + + +async def _wrapped_async_send( + wrapped: "BoundFunctionWrapper[..., Awaitable[httpx2.Response]]", + instance: httpx2.AsyncClient, + args: tuple[httpx2.Request], + kwargs: dict[str, Any], +) -> Optional[httpx2.Response]: + request: httpx2.Request = get_argument_value(args, kwargs, 0, "request") + + with core.context_with_event( + HttpClientRequestEvent( + http_operation="http.request", + service=httpx_get_service_name(request, config.httpx2), + component=config.httpx2.integration_name, + request_method=request.method, + request_headers=request.headers, + integration_config=config.httpx2, + request_url=httpx_url_to_str(request.url), + query=ensure_text(request.url.query), + target_host=request.url.host, + ), + context_name_override=HttpClientEvents.HTTPX_REQUEST.value, + ) as ctx: + response = None + try: + response = await wrapped(*args, **kwargs) + return response + finally: + if response is not None: + ctx.event.set_response(response) + return None + + +def _wrapped_sync_send( + wrapped: "BoundFunctionWrapper[..., httpx2.Response]", + instance: httpx2.Client, + args: tuple[httpx2.Request], + kwargs: dict[str, Any], +) -> Optional[httpx2.Response]: + request: httpx2.Request = get_argument_value(args, kwargs, 0, "request") + + with core.context_with_event( + HttpClientRequestEvent( + component=config.httpx2.integration_name, + http_operation="http.request", + service=httpx_get_service_name(request, config.httpx2), + request_method=request.method, + request_headers=request.headers, + integration_config=config.httpx2, + request_url=httpx_url_to_str(request.url), + query=ensure_text(request.url.query), + target_host=request.url.host, + ), + context_name_override=HttpClientEvents.HTTPX_REQUEST.value, + ) as ctx: + response = None + try: + response = wrapped(*args, **kwargs) + return response + finally: + if response is not None: + ctx.event.set_response(response) + return None + + +def patch() -> None: + if getattr(httpx2, "_datadog_patch", False): + return + + httpx2._datadog_patch = True + + _w(httpx2.Client, "send", _wrapped_sync_send) + _w(httpx2.AsyncClient, "send", _wrapped_async_send) + _w(httpx2.Client, "_send_single_request", _wrapped_sync_send_single_request) + _w(httpx2.AsyncClient, "_send_single_request", _wrapped_async_send_single_request) + + +def unpatch() -> None: + if not getattr(httpx2, "_datadog_patch", False): + return + + httpx2._datadog_patch = False + + _u(httpx2.AsyncClient, "send") + _u(httpx2.Client, "send") + _u(httpx2.Client, "_send_single_request") + _u(httpx2.AsyncClient, "_send_single_request") diff --git a/ddtrace/internal/settings/_config.py b/ddtrace/internal/settings/_config.py index 373a7b4a8f2..374ed29cd82 100644 --- a/ddtrace/internal/settings/_config.py +++ b/ddtrace/internal/settings/_config.py @@ -166,6 +166,7 @@ "dogpile_cache", "pylibmc", "httpx", + "httpx2", "httplib", "rq", "jinja2", diff --git a/ddtrace/internal/settings/_supported_configurations.py b/ddtrace/internal/settings/_supported_configurations.py index 45e5894e35b..81b82575724 100644 --- a/ddtrace/internal/settings/_supported_configurations.py +++ b/ddtrace/internal/settings/_supported_configurations.py @@ -270,6 +270,9 @@ "DD_HOSTNAME", "DD_HTTPLIB_DISTRIBUTED_TRACING", "DD_HTTPLIB_SERVICE", + "DD_HTTPX2_DISTRIBUTED_TRACING", + "DD_HTTPX2_SERVICE", + "DD_HTTPX2_SPLIT_BY_DOMAIN", "DD_HTTPX_DISTRIBUTED_TRACING", "DD_HTTPX_SERVICE", "DD_HTTPX_SPLIT_BY_DOMAIN", @@ -575,6 +578,7 @@ "DD_TRACE_HEADER_TAGS", "DD_TRACE_HEALTH_METRICS_ENABLED", "DD_TRACE_HTTPLIB_ENABLED", + "DD_TRACE_HTTPX2_ENABLED", "DD_TRACE_HTTPX_ENABLED", "DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING", "DD_TRACE_HTTP_SERVER_ERROR_STATUSES", @@ -898,6 +902,7 @@ "DD_GRPC_SERVER_SERVICE": ["DD_GRPC_SERVER_SERVICE_NAME"], "DD_GRPC_SERVICE": ["DD_GRPC_SERVICE_NAME"], "DD_HTTPLIB_SERVICE": ["DD_HTTPLIB_SERVICE_NAME"], + "DD_HTTPX2_SERVICE": ["DD_HTTPX2_SERVICE_NAME"], "DD_HTTPX_SERVICE": ["DD_HTTPX_SERVICE_NAME"], "DD_JINJA2_SERVICE": ["DD_JINJA2_SERVICE_NAME"], "DD_KAFKA_SERVICE": ["DD_KAFKA_SERVICE_NAME"], diff --git a/docs/index.rst b/docs/index.rst index 88a201854f2..5d8ae48edf9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,6 +130,8 @@ contacting support. +--------------------------------------------------+---------------+----------------+ | :ref:`httpx` | >= 0.25 | Yes | +--------------------------------------------------+---------------+----------------+ +| :ref:`httpx2` | >= 2.0 | Yes | ++--------------------------------------------------+---------------+----------------+ | :ref:`jinja2` | >= 2.10 | Yes | +--------------------------------------------------+---------------+----------------+ | :ref:`kombu` | >= 4.6 | No | diff --git a/docs/integrations.rst b/docs/integrations.rst index dfe93bbe59e..6da2d9ba649 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -311,6 +311,12 @@ httpx ^^^^^ .. automodule:: ddtrace.contrib.internal.httpx +.. _httpx2: + +httpx2 +^^^^^^ +.. automodule:: ddtrace.contrib.internal.httpx2 + .. _jinja2: diff --git a/releasenotes/notes/add-httpx2-integration-b72bee56ccc4e55c.yaml b/releasenotes/notes/add-httpx2-integration-b72bee56ccc4e55c.yaml new file mode 100644 index 00000000000..84d029f5450 --- /dev/null +++ b/releasenotes/notes/add-httpx2-integration-b72bee56ccc4e55c.yaml @@ -0,0 +1,4 @@ +features: + - | + httpx2: Adds automatic tracing for synchronous and asynchronous HTTP requests made with + ``httpx2>=2.0.0`` diff --git a/riotfile.py b/riotfile.py index 10c3c9e6eaf..6440a1d0abd 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2325,6 +2325,28 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), ], ), + Venv( + name="httpx2", + command="pytest {cmdargs} tests/contrib/httpx2", + pkgs={ + "pytest-asyncio": latest, + "pytest-randomly": latest, + }, + venvs=[ + Venv( + pys=["3.9"], + pkgs={ + "httpx2": "~=2.0.0", + }, + ), + Venv( + pys=select_pys(min_version="3.10"), + pkgs={ + "httpx2": ["~=2.0.0", latest], + }, + ), + ], + ), Venv( name="urllib3", command="pytest -n auto --dist=worksteal {cmdargs} tests/contrib/urllib3", diff --git a/scripts/integration_registry/registry.yaml b/scripts/integration_registry/registry.yaml index 989a77edc13..6b64572a0f9 100644 --- a/scripts/integration_registry/registry.yaml +++ b/scripts/integration_registry/registry.yaml @@ -509,6 +509,16 @@ integrations: min: 0.25.2 max: 0.28.1 +- integration_name: httpx2 + is_external_package: true + is_tested: true + dependency_names: + - httpx2 + tested_versions_by_dependency: + httpx2: + min: 2.0.0 + max: 2.12.0 + - integration_name: jinja2 is_external_package: true is_tested: true diff --git a/supported-configurations.json b/supported-configurations.json index 144a579eeb0..a957af1421f 100644 --- a/supported-configurations.json +++ b/supported-configurations.json @@ -2043,6 +2043,30 @@ ] } ], + "DD_HTTPX2_DISTRIBUTED_TRACING": [ + { + "implementation": "A", + "type": "boolean", + "default": "true" + } + ], + "DD_HTTPX2_SERVICE": [ + { + "implementation": "A", + "type": "string", + "default": null, + "aliases": [ + "DD_HTTPX2_SERVICE_NAME" + ] + } + ], + "DD_HTTPX2_SPLIT_BY_DOMAIN": [ + { + "implementation": "A", + "type": "boolean", + "default": "false" + } + ], "DD_HTTPX_DISTRIBUTED_TRACING": [ { "implementation": "A", @@ -4346,6 +4370,13 @@ "default": "false" } ], + "DD_TRACE_HTTPX2_ENABLED": [ + { + "implementation": "A", + "type": "boolean", + "default": "true" + } + ], "DD_TRACE_HTTPX_ENABLED": [ { "implementation": "A", diff --git a/supported_versions.json b/supported_versions.json index e279d6268e9..8de5b68259f 100644 --- a/supported_versions.json +++ b/supported_versions.json @@ -2682,6 +2682,60 @@ } } }, + { + "dependency": "httpx2", + "integration": "httpx2", + "auto-instrumented": true, + "python_versions": { + "3.9": { + "minimum_package_version": "2.0.0", + "maximum_package_version": "2.0.0", + "tested_versions": [ + "2.0.0" + ] + }, + "3.10": { + "minimum_package_version": "2.0.0", + "maximum_package_version": "2.12.0", + "tested_versions": [ + "2.0.0", + "2.12.0" + ] + }, + "3.11": { + "minimum_package_version": "2.0.0", + "maximum_package_version": "2.12.0", + "tested_versions": [ + "2.0.0", + "2.12.0" + ] + }, + "3.12": { + "minimum_package_version": "2.0.0", + "maximum_package_version": "2.12.0", + "tested_versions": [ + "2.0.0", + "2.12.0" + ] + }, + "3.13": { + "minimum_package_version": "2.0.0", + "maximum_package_version": "2.12.0", + "tested_versions": [ + "2.0.0", + "2.12.0" + ] + }, + "3.14": { + "minimum_package_version": "2.0.0", + "maximum_package_version": "2.12.0", + "tested_versions": [ + "2.0.0", + "2.12.0" + ] + } + } + }, { "dependency": "jinja2", "integration": "jinja2", diff --git a/tests/contrib/httpx2/__init__.py b/tests/contrib/httpx2/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/contrib/httpx2/test_httpx2.py b/tests/contrib/httpx2/test_httpx2.py new file mode 100644 index 00000000000..db57ae97580 --- /dev/null +++ b/tests/contrib/httpx2/test_httpx2.py @@ -0,0 +1,511 @@ +import httpx2 +import pytest + +from ddtrace import config +from ddtrace.contrib.internal.httpx2.patch import patch +from ddtrace.contrib.internal.httpx2.patch import unpatch +from ddtrace.internal.compat import is_wrapted +from ddtrace.internal.settings.http import HttpConfig +from tests.utils import override_config +from tests.utils import override_http_config + + +# host:port of httpbin container +HOST = "localhost" +PORT = 8001 + +DEFAULT_HEADERS = { + "User-Agent": "python-httpx2/x.xx.x", +} + + +def get_url(path): + # type: (str) -> str + return "http://{}:{}{}".format(HOST, PORT, path) + + +@pytest.fixture(autouse=True) +def patch_httpx2(): + patch() + try: + yield + finally: + unpatch() + + +def test_patching(): + """ + When patching httpx2 library + We wrap the correct methods + When unpatching httpx2 library + We unwrap the correct methods + """ + assert is_wrapted(httpx2.Client.send) + assert is_wrapted(httpx2.AsyncClient.send) + assert is_wrapted(httpx2.Client._send_single_request) + assert is_wrapted(httpx2.AsyncClient._send_single_request) + + unpatch() + assert not is_wrapted(httpx2.Client.send) + assert not is_wrapted(httpx2.AsyncClient.send) + assert not is_wrapted(httpx2.Client._send_single_request) + assert not is_wrapted(httpx2.AsyncClient._send_single_request) + + +@pytest.mark.skipif(not hasattr(httpx2, "alias_httpx"), reason="httpx2.alias_httpx requires httpx2>=2.9.0") +@pytest.mark.subprocess() +@pytest.mark.snapshot() +def test_alias_httpx(): + import httpx2 + + httpx2.alias_httpx() + + import httpx + + from ddtrace import patch_all + + patch_all() + + response = httpx.get("http://localhost:8001/status/200") + assert response.status_code == 200 + + +@pytest.mark.snapshot(ignores=["meta.http.useragent"]) +def test_httpx2_service_name(): + """ + When using split_by_domain + We set the span service name as a text type and not binary + """ + client = httpx2.Client() + + with override_config("httpx2", {"split_by_domain": True}): + resp = client.get(get_url("/status/200")) + assert resp.status_code == 200 + + +@pytest.mark.asyncio +@pytest.mark.snapshot() +async def test_get_200(): + url = get_url("/status/200") + + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + +@pytest.mark.asyncio +@pytest.mark.snapshot() +async def test_configure_service_name(): + """ + When setting ddtrace.config.httpx2.service_name directly + We use the value from ddtrace.config.httpx2.service_name + """ + url = get_url("/status/200") + + with override_config("httpx2", {"service_name": "test-httpx2-service-name"}): + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + +@pytest.mark.subprocess( + env=dict( + DD_HTTPX2_SERVICE="env-overridden-service-name", + DD_SERVICE="global-service-name", + ) +) +@pytest.mark.snapshot() +def test_configure_service_name_env(): + """ + When setting DD_HTTPX2_SERVICE env variable + When DD_SERVICE is also set + We use the value from DD_HTTPX2_SERVICE + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_SERVICE="global-service-name")) +@pytest.mark.snapshot() +def test_schematized_configure_global_service_name_env_default(): + """ + v0/default: When only setting DD_SERVICE + We use the value from DD_SERVICE for the service name + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_SERVICE="global-service-name", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) +@pytest.mark.snapshot() +def test_schematized_configure_global_service_name_env_v0(): + """ + v0/default: When only setting DD_SERVICE + We use the value from DD_SERVICE for the service name + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_SERVICE="global-service-name", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) +@pytest.mark.snapshot() +def test_schematized_configure_global_service_name_env_v1(): + """ + v1: When only setting DD_SERVICE + We use the value from DD_SERVICE for the service name + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess() +@pytest.mark.snapshot() +def test_schematized_unspecified_service_name_env_default(): + """ + v0/default: With no service name, we use httpx2 + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) +@pytest.mark.snapshot() +def test_schematized_unspecified_service_name_env_v0(): + """ + v0/default: With no service name, we use httpx2 + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) +@pytest.mark.snapshot() +def test_schematized_unspecified_service_name_env_v1(): + """ + v1: With no service name, we expect ddtrace.internal.DEFAULT_SPAN_SERVICE_NAME + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) +@pytest.mark.snapshot() +def test_schematized_operation_name_env_v0(): + """ + v0: Operation name is http.request + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.subprocess(env=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) +@pytest.mark.snapshot() +def test_schematized_operation_name_env_v1(): + """ + v1: Operation name is http.client.request + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/status/200") + httpx2.get(url, headers=DEFAULT_HEADERS) + + async def test(): + async with httpx2.AsyncClient() as client: + await client.get(url, headers=DEFAULT_HEADERS) + + asyncio.run(test()) + + +@pytest.mark.asyncio +@pytest.mark.snapshot() +async def test_get_500(): + """ + When the status code is 500 + We mark the span as an error + """ + url = get_url("/status/500") + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 500 + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 500 + + +@pytest.mark.snapshot(ignores=["meta.error.stack", "meta.http.useragent"]) +def test_connection_error(): + with pytest.raises(httpx2.ConnectError): + httpx2.get("http://127.0.0.1:1") + + +@pytest.mark.asyncio +@pytest.mark.snapshot() +async def test_split_by_domain(): + """ + When split_by_domain is configured + We set the service name to the : + """ + url = get_url("/status/200") + + with override_config("httpx2", {"split_by_domain": True}): + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + +@pytest.mark.asyncio +@pytest.mark.snapshot() +async def test_trace_query_string(): + """ + When trace_query_string is enabled + We include the query string as a tag on the span + """ + url = get_url("/status/200?some=query&string=args") + with override_http_config("httpx2", {"trace_query_string": True}): + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert resp.status_code == 200 + + +@pytest.mark.asyncio +@pytest.mark.snapshot() +async def test_request_headers(): + """ + When request headers are configured for this integration + We add the request headers as tags on the span + """ + url = get_url("/response-headers?Some-Response-Header=Response-Value") + + headers = { + "Some-Request-Header": "Request-Value", + "User-Agent": "python-httpx2/x.xx.x", + } + + try: + config.httpx2.http.trace_headers(["Some-Request-Header", "Some-Response-Header"]) + resp = httpx2.get(url, headers=headers) + assert resp.status_code == 200 + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=headers) + assert resp.status_code == 200 + finally: + config.httpx2.http = HttpConfig() + + +@pytest.mark.asyncio +async def test_distributed_tracing_headers(): + """ + By default + Distributed tracing headers are added to outbound requests + """ + url = get_url("/headers") + + def assert_request_headers(response): + data = response.json() + assert "X-Datadog-Trace-Id" in data["headers"] + assert "X-Datadog-Parent-Id" in data["headers"] + assert "X-Datadog-Sampling-Priority" in data["headers"] + + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert_request_headers(resp) + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert_request_headers(resp) + + +@pytest.mark.asyncio +async def test_distributed_tracing_disabled(): + """ + When distributed_tracing is disabled + We do not add distributed tracing headers to outbound requests + """ + url = get_url("/headers") + + def assert_request_headers(response): + data = response.json() + assert "X-Datadog-Trace-Id" not in data["headers"] + assert "X-Datadog-Parent-Id" not in data["headers"] + assert "X-Datadog-Sampling-Priority" not in data["headers"] + + with override_config("httpx2", {"distributed_tracing": False}): + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert_request_headers(resp) + + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert_request_headers(resp) + + +@pytest.mark.subprocess(env=dict(DD_HTTPX2_DISTRIBUTED_TRACING="false")) +def test_distributed_tracing_disabled_env(): + """ + When disabling distributed tracing via env variable + We do not add distributed tracing headers to outbound requests + """ + import asyncio + + import httpx2 + + from ddtrace.contrib.internal.httpx2.patch import patch + from tests.contrib.httpx2.test_httpx2 import DEFAULT_HEADERS + from tests.contrib.httpx2.test_httpx2 import get_url + + patch() + url = get_url("/headers") + + def assert_request_headers(response): + data = response.json() + assert "X-Datadog-Trace-Id" not in data["headers"] + assert "X-Datadog-Parent-Id" not in data["headers"] + assert "X-Datadog-Sampling-Priority" not in data["headers"] + + resp = httpx2.get(url, headers=DEFAULT_HEADERS) + assert_request_headers(resp) + + async def test(): + async with httpx2.AsyncClient() as client: + resp = await client.get(url, headers=DEFAULT_HEADERS) + assert_request_headers(resp) + + asyncio.run(test()) diff --git a/tests/contrib/httpx2/test_httpx2_patch.py b/tests/contrib/httpx2/test_httpx2_patch.py new file mode 100644 index 00000000000..74208589ba3 --- /dev/null +++ b/tests/contrib/httpx2/test_httpx2_patch.py @@ -0,0 +1,30 @@ +from ddtrace.contrib.internal.httpx2.patch import get_version +from ddtrace.contrib.internal.httpx2.patch import patch +from ddtrace.contrib.internal.httpx2.patch import unpatch +from tests.contrib.patch import PatchTestCase + + +class TestHttpx2Patch(PatchTestCase.Base): + __integration_name__ = "httpx2" + __module_name__ = "httpx2" + __patch_func__ = patch + __unpatch_func__ = unpatch + __get_version__ = get_version + + def assert_module_patched(self, httpx2): + self.assert_wrapped(httpx2.Client.send) + self.assert_wrapped(httpx2.AsyncClient.send) + self.assert_wrapped(httpx2.Client._send_single_request) + self.assert_wrapped(httpx2.AsyncClient._send_single_request) + + def assert_not_module_patched(self, httpx2): + self.assert_not_wrapped(httpx2.Client.send) + self.assert_not_wrapped(httpx2.AsyncClient.send) + self.assert_not_wrapped(httpx2.Client._send_single_request) + self.assert_not_wrapped(httpx2.AsyncClient._send_single_request) + + def assert_not_module_double_patched(self, httpx2): + self.assert_not_double_wrapped(httpx2.Client.send) + self.assert_not_double_wrapped(httpx2.AsyncClient.send) + self.assert_not_double_wrapped(httpx2.Client._send_single_request) + self.assert_not_double_wrapped(httpx2.AsyncClient._send_single_request) diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 6d724daafa4..9d34e6711f5 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -120,6 +120,8 @@ components: - ddtrace/contrib/internal/httplib/* httpx: - ddtrace/contrib/internal/httpx/* + httpx2: + - ddtrace/contrib/internal/httpx2/* integration_registry: - scripts/integration_registry/* jinja2: @@ -896,6 +898,20 @@ suites: services: - httpbin snapshot: true + httpx2: + parallelism: 2 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@httpx2' + - ddtrace/contrib/internal/httpx/utils.py + - tests/contrib/httpx2/* + - tests/snapshots/tests.contrib.httpx2.* + services: + - httpbin + snapshot: true integration_registry: paths: - '@bootstrap' diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_alias_httpx.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_alias_httpx.json new file mode 100644 index 00000000000..de93b2bb90f --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_alias_httpx.json @@ -0,0 +1,35 @@ +[[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8708f900000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpxf05smqd,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/2.12.0", + "language": "python", + "out.host": "localhost", + "runtime-id": "96e260e256e246d68e8ee177a5e32db7", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 530.0 + }, + "duration": 3697651, + "start": 1787234553797133377 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_configure_service_name.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_configure_service_name.json new file mode 100644 index 00000000000..9203b81c6aa --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_configure_service_name.json @@ -0,0 +1,72 @@ +[[ + { + "name": "http.request", + "service": "test-httpx2-service-name", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.httpx2", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b200000000", + "_dd.svc_src": "m", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 1780691, + "start": 1787233970555427664 + }], +[ + { + "name": "http.request", + "service": "test-httpx2-service-name", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.httpx2", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b200000000", + "_dd.svc_src": "m", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 3519678, + "start": 1787233970560839522 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_configure_service_name_env.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_configure_service_name_env.json new file mode 100644 index 00000000000..56e001a8945 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_configure_service_name_env.json @@ -0,0 +1,72 @@ +[[ + { + "name": "http.request", + "service": "env-overridden-service-name", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "global-service-name", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b500000000", + "_dd.svc_src": "m", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpg0tyertp,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "03491cf8b38e4145ba0797095ec2bd6a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 700.0 + }, + "duration": 2337470, + "start": 1787233973255905094 + }], +[ + { + "name": "http.request", + "service": "env-overridden-service-name", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "global-service-name", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b500000000", + "_dd.svc_src": "m", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpg0tyertp,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "03491cf8b38e4145ba0797095ec2bd6a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 700.0 + }, + "duration": 5121563, + "start": 1787233973262367978 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_connection_error.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_connection_error.json new file mode 100644 index 00000000000..4a0c5d5424a --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_connection_error.json @@ -0,0 +1,37 @@ +[[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 1, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a87095400000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "error.message": "[Errno 111] Connection refused", + "error.stack": "Traceback (most recent call last):\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_transports/default.py\", line 98, in map_httpcore_exceptions\n yield\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_transports/default.py\", line 245, in handle_request\n resp = self._pool.handle_request(req)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_sync/connection_pool.py\", line 242, in handle_request\n raise exc from None\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_sync/connection_pool.py\", line 224, in handle_request\n response = connection.handle_request(pool_request.request)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_sync/connection.py\", line 94, in handle_request\n raise exc\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_sync/connection.py\", line 74, in handle_request\n stream = self._connect(request)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_sync/connection.py\", line 117, in _connect\n stream = self._network_backend.connect_tcp(**kwargs)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_backends/sync.py\", line 203, in connect_tcp\n with map_exceptions(exc_map):\n ~~~~~~~~~~~~~~^^^^^^^^^\n File \"/home/bits/.pyenv/versions/3.14.5/lib/python3.14/contextlib.py\", line 162, in __exit__\n self.gen.throw(value)\n ~~~~~~~~~~~~~~^^^^^^^\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpcore2/_exceptions.py\", line 17, in map_exceptions\n raise to_exc(exc) from exc\nhttpcore2.ConnectError: [Errno 111] Connection refused\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/home/bits/project/ddtrace/contrib/internal/httpx2/patch.py\", line 151, in _wrapped_sync_send\n response = wrapped(*args, **kwargs)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_client.py\", line 980, in send\n response = self._send_handling_auth(\n request,\n ...<2 lines>...\n history=[],\n )\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_client.py\", line 1008, in _send_handling_auth\n response = self._send_handling_redirects(\n request,\n follow_redirects=follow_redirects,\n history=history,\n )\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_client.py\", line 1043, in _send_handling_redirects\n response = self._send_single_request(request)\n File \"/home/bits/project/ddtrace/contrib/internal/httpx2/patch.py\", line 60, in _wrapped_sync_send_single_request\n response = wrapped(*args, **kwargs)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_client.py\", line 1076, in _send_single_request\n response = transport.handle_request(request)\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_transports/default.py\", line 244, in handle_request\n with map_httpcore_exceptions():\n ~~~~~~~~~~~~~~~~~~~~~~~^^\n File \"/home/bits/.pyenv/versions/3.14.5/lib/python3.14/contextlib.py\", line 162, in __exit__\n self.gen.throw(value)\n ~~~~~~~~~~~~~~^^^^^^^\n File \"/home/bits/project/.riot/docker/venv_py3145_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_pytest-randomly_httpx2/lib/python3.14/site-packages/httpx2/_transports/default.py\", line 115, in map_httpcore_exceptions\n raise mapped_exc(message) from exc\nhttpx2.ConnectError: [Errno 111] Connection refused\n", + "error.type": "httpx2.ConnectError", + "http.method": "GET", + "http.url": "http://127.0.0.1:1/", + "http.useragent": "python-httpx2/2.12.0", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "b776be62e5db4976b89d6fe2b50ebc94", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 525.0 + }, + "duration": 7089742, + "start": 1787234644310047254 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_get_200.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_get_200.json new file mode 100644 index 00000000000..587f42e31c5 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_get_200.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 1354443, + "start": 1787233969132986688 + }], +[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 3832436, + "start": 1787233969138313719 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_get_500.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_get_500.json new file mode 100644 index 00000000000..f0797a4b6d9 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_get_500.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 1, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "500", + "http.url": "http://localhost:8001/status/500", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 1711322, + "start": 1787233969086698282 + }], +[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 1, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "500", + "http.url": "http://localhost:8001/status/500", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 3947174, + "start": 1787233969092283786 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_httpx2_service_name.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_httpx2_service_name.json new file mode 100644 index 00000000000..1d51e72ae19 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_httpx2_service_name.json @@ -0,0 +1,36 @@ +[[ + { + "name": "http.request", + "service": "localhost:8001", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.httpx2", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "opt.split_by_domain", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/2.0.0", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 1618165, + "start": 1787233969073742751 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_request_headers.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_request_headers.json new file mode 100644 index 00000000000..76d9d231f5b --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_request_headers.json @@ -0,0 +1,74 @@ +[[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.request.headers.some-request-header": "Request-Value", + "http.response.headers.some-response-header": "Response-Value", + "http.status_code": "200", + "http.url": "http://localhost:8001/response-headers?Some-Response-Header=Response-Value", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 2079534, + "start": 1787233969110830612 + }], +[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.request.headers.some-request-header": "Request-Value", + "http.response.headers.some-response-header": "Response-Value", + "http.status_code": "200", + "http.url": "http://localhost:8001/response-headers?Some-Response-Header=Response-Value", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 4475702, + "start": 1787233969116752414 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_default.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_default.json new file mode 100644 index 00000000000..04653ab5160 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_default.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "global-service-name", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b200000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpixk2vnxi,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "4093a7ad1fc1434697e3ebdd0a1a6c68", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 670.0 + }, + "duration": 2245352, + "start": 1787233970369263628 + }], +[ + { + "name": "http.request", + "service": "global-service-name", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b200000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpixk2vnxi,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "4093a7ad1fc1434697e3ebdd0a1a6c68", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 670.0 + }, + "duration": 4739969, + "start": 1787233970375532940 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_v0.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_v0.json new file mode 100644 index 00000000000..424ba6fbbb6 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_v0.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "global-service-name", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b300000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmplmfjnsw1,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "0ccf53ec33ce4f14bab5005af8f0e4e6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 686.0 + }, + "duration": 2202065, + "start": 1787233971830873044 + }], +[ + { + "name": "http.request", + "service": "global-service-name", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b300000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmplmfjnsw1,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "0ccf53ec33ce4f14bab5005af8f0e4e6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 686.0 + }, + "duration": 4994820, + "start": 1787233971837088156 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_v1.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_v1.json new file mode 100644 index 00000000000..41cf0ec5159 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_configure_global_service_name_env_v1.json @@ -0,0 +1,74 @@ +[[ + { + "name": "http.client.request", + "service": "global-service-name", + "resource": "http.client.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b300000000", + "_dd.peer.service.source": "out.host", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmp_16822ez,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "peer.service": "localhost", + "runtime-id": "a73450d0986748908e614780f3cd4f7c", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 679.0 + }, + "duration": 2360091, + "start": 1787233971155176386 + }], +[ + { + "name": "http.client.request", + "service": "global-service-name", + "resource": "http.client.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b300000000", + "_dd.peer.service.source": "out.host", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmp_16822ez,entrypoint.type:script,entrypoint.workdir:project,svc.user:true", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "peer.service": "localhost", + "runtime-id": "a73450d0986748908e614780f3cd4f7c", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 679.0 + }, + "duration": 5102226, + "start": 1787233971161670136 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_operation_name_env_v0.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_operation_name_env_v0.json new file mode 100644 index 00000000000..faff8a6df84 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_operation_name_env_v0.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b000000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpf4a819ky,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "ada3e646116e4d43b617edea9d8d15af", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 648.0 + }, + "duration": 2394917, + "start": 1787233968903405516 + }], +[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b000000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpf4a819ky,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "ada3e646116e4d43b617edea9d8d15af", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 648.0 + }, + "duration": 4849494, + "start": 1787233968909844803 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_operation_name_env_v1.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_operation_name_env_v1.json new file mode 100644 index 00000000000..653dac718ae --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_operation_name_env_v1.json @@ -0,0 +1,74 @@ +[[ + { + "name": "http.client.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.client.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b000000000", + "_dd.peer.service.source": "out.host", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmp8dji45bb,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "peer.service": "localhost", + "runtime-id": "c052c681d3d2464888a529380c680d11", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 639.0 + }, + "duration": 2407374, + "start": 1787233968133369854 + }], +[ + { + "name": "http.client.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.client.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b000000000", + "_dd.peer.service.source": "out.host", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmp8dji45bb,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "peer.service": "localhost", + "runtime-id": "c052c681d3d2464888a529380c680d11", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 639.0 + }, + "duration": 5179252, + "start": 1787233968140233846 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_default.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_default.json new file mode 100644 index 00000000000..f9a2cf3369f --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_default.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpfv3v_lqs,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "fb59ed34b5c2495583d571ad7894c5d7", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 661.0 + }, + "duration": 2315801, + "start": 1787233969652131414 + }], +[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b100000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpfv3v_lqs,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "fb59ed34b5c2495583d571ad7894c5d7", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 661.0 + }, + "duration": 4970109, + "start": 1787233969658484130 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_v0.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_v0.json new file mode 100644 index 00000000000..b0e9f7bdd1d --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_v0.json @@ -0,0 +1,70 @@ +[[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b500000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpag634cb9,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "3dc1a1db581941df91adc74ea9c62523", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 707.0 + }, + "duration": 2257562, + "start": 1787233973899094807 + }], +[ + { + "name": "http.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b500000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmpag634cb9,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "3dc1a1db581941df91adc74ea9c62523", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 707.0 + }, + "duration": 4591409, + "start": 1787233973905362753 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_v1.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_v1.json new file mode 100644 index 00000000000..1edc38559d9 --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_schematized_unspecified_service_name_env_v1.json @@ -0,0 +1,74 @@ +[[ + { + "name": "http.client.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.client.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b400000000", + "_dd.peer.service.source": "out.host", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmphmh2_56k,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "peer.service": "localhost", + "runtime-id": "a6ded5cc1a374be7a5a31912ccb6fe39", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 693.0 + }, + "duration": 2331637, + "start": 1787233972538944453 + }], +[ + { + "name": "http.client.request", + "service": "ddtrace_subprocess_dir", + "resource": "http.client.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706b400000000", + "_dd.peer.service.source": "out.host", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.basedir:ddtrace_subprocess_dir,entrypoint.name:tmphmh2_56k,entrypoint.type:script,entrypoint.workdir:project,svc.auto:ddtrace_subprocess_dir", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "peer.service": "localhost", + "runtime-id": "a6ded5cc1a374be7a5a31912ccb6fe39", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 693.0 + }, + "duration": 5037861, + "start": 1787233972545473369 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_split_by_domain.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_split_by_domain.json new file mode 100644 index 00000000000..11b800de33f --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_split_by_domain.json @@ -0,0 +1,72 @@ +[[ + { + "name": "http.request", + "service": "localhost:8001", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.httpx2", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706ae00000000", + "_dd.svc_src": "opt.split_by_domain", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 1703446, + "start": 1787233966793223023 + }], +[ + { + "name": "http.request", + "service": "localhost:8001", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.httpx2", + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706ae00000000", + "_dd.svc_src": "opt.split_by_domain", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 4142605, + "start": 1787233966798913424 + }]] diff --git a/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_trace_query_string.json b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_trace_query_string.json new file mode 100644 index 00000000000..f8db1e1f40c --- /dev/null +++ b/tests/snapshots/tests.contrib.httpx2.test_httpx2.test_trace_query_string.json @@ -0,0 +1,72 @@ +[[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706ae00000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.query.string": "some=query&string=args", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200?some=query&string=args", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 3460094, + "start": 1787233966765053824 + }], +[ + { + "name": "http.request", + "service": "tests.contrib.httpx2", + "resource": "http.request", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6a8706ae00000000", + "_dd.svc_src": "httpx2", + "_dd.tags.process": "entrypoint.name:pytest,entrypoint.type:script,entrypoint.workdir:project,svc.auto:tests.contrib.httpx2", + "component": "httpx2", + "http.method": "GET", + "http.query.string": "some=query&string=args", + "http.status_code": "200", + "http.url": "http://localhost:8001/status/200?some=query&string=args", + "http.useragent": "python-httpx2/x.xx.x", + "language": "python", + "out.host": "localhost", + "runtime-id": "dc6e9e56b4b14e64890b9b51b4817643", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1.0, + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 522.0 + }, + "duration": 6934811, + "start": 1787233966773248961 + }]]