diff --git a/elasticapm/instrumentation/packages/httpx/async/httpcore.py b/elasticapm/instrumentation/packages/httpx/async/httpcore.py index 061f2d87e..c7e39dbee 100644 --- a/elasticapm/instrumentation/packages/httpx/async/httpcore.py +++ b/elasticapm/instrumentation/packages/httpx/async/httpcore.py @@ -57,12 +57,12 @@ async def call(self, module, method, wrapped, instance, args, kwargs): url, method, headers = utils.get_request_data(args, kwargs) scheme, host, port, target = url - if port != default_ports.get(scheme): + if port is not None and port != default_ports.get(scheme): host += ":" + str(port) signature = "%s %s" % (method.upper(), host) - url = "%s://%s%s" % (scheme, host, url) + url = "%s://%s%s" % (scheme, host, target) transaction = execution_context.get_transaction() diff --git a/elasticapm/instrumentation/packages/httpx/sync/httpcore.py b/elasticapm/instrumentation/packages/httpx/sync/httpcore.py index 6530f703e..d03ebef2d 100644 --- a/elasticapm/instrumentation/packages/httpx/sync/httpcore.py +++ b/elasticapm/instrumentation/packages/httpx/sync/httpcore.py @@ -47,7 +47,7 @@ class HTTPCoreInstrumentation(AbstractInstrumentedModule): def call(self, module, method, wrapped, instance, args, kwargs): url, method, headers = utils.get_request_data(args, kwargs) scheme, host, port, target = url - if port != default_ports.get(scheme): + if port is not None and port != default_ports.get(scheme): host += ":" + str(port) signature = "%s %s" % (method.upper(), host) diff --git a/tests/instrumentation/asyncio_tests/httpx_tests.py b/tests/instrumentation/asyncio_tests/httpx_tests.py index 2ab017c71..01f05e938 100644 --- a/tests/instrumentation/asyncio_tests/httpx_tests.py +++ b/tests/instrumentation/asyncio_tests/httpx_tests.py @@ -31,6 +31,8 @@ import pytest # isort:skip httpx = pytest.importorskip("httpx") # isort:skip +httpcore = pytest.importorskip("httpcore") # isort:skip + import urllib.parse from elasticapm.conf import constants @@ -40,6 +42,7 @@ pytestmark = [pytest.mark.httpx, pytest.mark.asyncio] +httpcore_version = tuple(map(int, httpcore.__version__.split(".")[:3])) httpx_version = tuple(map(int, httpx.__version__.split(".")[:3])) if httpx_version < (0, 20): @@ -194,3 +197,28 @@ async def test_httpx_streaming(instrument, elasticapm_client, waiting_httpserver span = elasticapm_client.spans_for_transaction(transactions[0])[0] assert span["type"] == "external" assert span["subtype"] == "http" + + +@pytest.mark.skipif(httpcore_version < (0, 17, 3), reason="AsyncMockBackend not available on older versions") +async def test_default_port_handling(instrument, elasticapm_client): + url = "https://example.com/" + + elasticapm_client.begin_transaction("transaction") + network_backend = httpcore.AsyncMockBackend( + buffer=[ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, world!", + ] + ) + async with httpcore.AsyncConnectionPool(network_backend=network_backend) as pool: + await pool.request("GET", url) + elasticapm_client.end_transaction("transaction") + + transactions = elasticapm_client.events[TRANSACTION] + span = elasticapm_client.spans_for_transaction(transactions[0])[0] + + assert span["name"] == "GET example.com" + assert span["context"]["http"]["url"] == url diff --git a/tests/instrumentation/httpx_tests.py b/tests/instrumentation/httpx_tests.py index 3c1b8bf15..283ee2e44 100644 --- a/tests/instrumentation/httpx_tests.py +++ b/tests/instrumentation/httpx_tests.py @@ -31,6 +31,8 @@ import pytest # isort:skip httpx = pytest.importorskip("httpx") # isort:skip +httpcore = pytest.importorskip("httpcore") # isort:skip + import urllib.parse from elasticapm.conf import constants @@ -40,6 +42,7 @@ pytestmark = pytest.mark.httpx +httpcore_version = tuple(map(int, httpcore.__version__.split(".")[:3])) httpx_version = tuple(map(int, httpx.__version__.split(".")[:3])) if httpx_version < (0, 20): @@ -136,6 +139,29 @@ def test_httpx_instrumentation_malformed_path(instrument, elasticapm_client): httpx.get("http://") +@pytest.mark.skipif(httpcore_version < (0, 17, 3), reason="MockBackend not available on older versions") +def test_default_port_handling(instrument, elasticapm_client): + url = "https://example.com/" + elasticapm_client.begin_transaction("transaction") + network_backend = httpcore.MockBackend( + buffer=[ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, world!", + ] + ) + with httpcore.ConnectionPool(network_backend=network_backend) as pool: + pool.request("GET", url) + elasticapm_client.end_transaction("transaction") + transactions = elasticapm_client.events[TRANSACTION] + span = elasticapm_client.spans_for_transaction(transactions[0])[0] + + assert span["name"] == "GET example.com" + assert span["context"]["http"]["url"] == url + + def test_url_sanitization(instrument, elasticapm_client, waiting_httpserver): waiting_httpserver.serve_content("") url = waiting_httpserver.url + "/hello_world"