Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions A365_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ When `enable_a365=True` (and `enable_azure_monitor` is **not** set), the **web-f
| `fastapi` | disabled |
| `flask` | disabled |
| `httpx` | disabled |
| `httpx2` | disabled |
| `psycopg2` | disabled |
| `requests` | disabled |
| `urllib` | disabled |
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Release History
# 1.3.9 (Unreleased)
### Features Added
- - Update OpenTelemetry dependencies to latest versions, bump `langchain-core` minimum version to address S360, and support the new `httpx2` entry point exposed by `opentelemetry-instrumentation-httpx`.
([#254](https://github.com/microsoft/opentelemetry-distro-python/pull/254))

# 1.3.8 (2026-08-20)
### Features Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ Microsoft OpenTelemetry automatically instruments the following libraries when i
| `fastapi` | Web framework |
| `flask` | Web framework |
| `httpx` | HTTP client |
| `httpx2` | HTTP client |
| `psycopg2` | Database |
| `requests` | HTTP client |
| `urllib` | HTTP client |
Expand Down Expand Up @@ -245,6 +246,7 @@ following instrumentations by default** when `enable_a365=True`:
| `fastapi` | disabled |
| `flask` | disabled |
| `httpx` | disabled |
| `httpx2` | disabled |
| `psycopg2` | disabled |
| `requests` | disabled |
| `urllib` | disabled |
Expand Down
32 changes: 16 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,23 @@ classifiers = [
dependencies = [
"azure-core<2.0.0,>=1.28.0",
"azure-core-tracing-opentelemetry~=1.0.0b11",
"azure-monitor-opentelemetry-exporter~=1.0.0b56",
"opentelemetry-sdk~=1.43.0",
"opentelemetry-api~=1.43.0",
"opentelemetry-exporter-otlp-proto-http~=1.43.0",
"opentelemetry-instrumentation>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-django>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-fastapi>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-flask>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-httpx>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-psycopg2>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-requests>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-urllib>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-urllib3>=0.64b0,<0.65b0",
"opentelemetry-instrumentation-logging>=0.64b0,<0.65b0",
"azure-monitor-opentelemetry-exporter~=1.0.0b57",
"opentelemetry-sdk~=1.44.0",
"opentelemetry-api~=1.44.0",
"opentelemetry-exporter-otlp-proto-http~=1.44.0",
"opentelemetry-instrumentation>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-django>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-fastapi>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-flask>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-httpx>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-psycopg2>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-requests>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-urllib>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-urllib3>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-logging>=0.65b0,<0.66b0",
"opentelemetry-instrumentation-openai-agents-v2==0.1.0",
"opentelemetry-instrumentation-openai-v2==2.3b0",
"opentelemetry-resource-detector-azure<1.0.0,>=0.1.5",
"opentelemetry-resource-detector-azure<1.0.0,>=0.2.0",
"wrapt>=1.0.0,<2.0.0",
"opentelemetry-util-genai==0.3b0",
"aiohttp>=3.8.0",
Expand All @@ -49,7 +49,7 @@ dependencies = [

[project.optional-dependencies]
langchain = [
"langchain-core>=0.2.0",
"langchain-core>=1.6.0",
]
agent-framework = [
"agent-framework>=1.4.0",
Expand Down
2 changes: 2 additions & 0 deletions src/microsoft/opentelemetry/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"fastapi",
"flask",
"httpx",
"httpx2",
"psycopg2",
"requests",
"urllib",
Expand All @@ -52,6 +53,7 @@
"fastapi",
"flask",
"httpx",
"httpx2",
"psycopg2",
"requests",
"urllib",
Expand Down
11 changes: 11 additions & 0 deletions tests/azure_monitor/utils/test_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def test_get_configurations(self, resource_create_mock):
"fastapi": {"enabled": True},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": True},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -147,6 +148,7 @@ def test_get_configurations_defaults(self, resource_create_mock):
"fastapi": {"enabled": True},
"flask": {"enabled": True},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": True},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -201,6 +203,7 @@ def test_get_configurations_env_vars(self, resource_create_mock):
"fastapi": {"enabled": False},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": False},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -289,6 +292,7 @@ def test_merge_instrumentation_options_conflict(self, resource_create_mock):
"fastapi": {"enabled": True},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"previewlib1": {"enabled": True}, # Explicit configuration takes priority
"previewlib2": {"enabled": False},
Expand Down Expand Up @@ -332,6 +336,7 @@ def test_merge_instrumentation_options_extra_args(self, resource_create_mock):
"fastapi": {"enabled": True, "foo": "bar"},
"flask": {"enabled": False, "foo": "bar"},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True, "foo": "bar"},
"previewlib1": {"enabled": True, "foo": "bar"},
"previewlib2": {"enabled": False, "foo": "bar"},
Expand Down Expand Up @@ -485,6 +490,7 @@ def test_get_configurations_env_vars_rate_limited(self, resource_create_mock):
"fastapi": {"enabled": False},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": False},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -518,6 +524,7 @@ def test_get_configurations_rate_limited_sampler_param(self, resource_create_moc
"fastapi": {"enabled": True},
"flask": {"enabled": True},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": True},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -562,6 +569,7 @@ def test_get_configurations_env_vars_no_preference(self, resource_create_mock):
"fastapi": {"enabled": False},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": False},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -606,6 +614,7 @@ def test_get_configurations_env_vars_check_default(self, resource_create_mock):
"fastapi": {"enabled": False},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": False},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -651,6 +660,7 @@ def test_get_configurations_env_vars_fixed_percentage(self, resource_create_mock
"fastapi": {"enabled": False},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": False},
"urllib": {"enabled": True},
Expand Down Expand Up @@ -695,6 +705,7 @@ def test_get_configurations_env_vars_always_on(self, resource_create_mock):
"fastapi": {"enabled": False},
"flask": {"enabled": False},
"httpx": {"enabled": True},
"httpx2": {"enabled": True},
"psycopg2": {"enabled": True},
"requests": {"enabled": False},
"urllib": {"enabled": True},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_httpx_instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ class TestHttpxInstrumentationConfig(unittest.TestCase):

def test_httpx_in_supported_libraries(self):
self.assertIn("httpx", _SUPPORTED_INSTRUMENTED_LIBRARIES)
self.assertIn("httpx2", _SUPPORTED_INSTRUMENTED_LIBRARIES)

def test_httpx_in_a365_disabled_list(self):
self.assertIn("httpx", _A365_DISABLED_INSTRUMENTATIONS)
self.assertIn("httpx2", _A365_DISABLED_INSTRUMENTATIONS)


class TestHttpxInstrumentorLifecycle(unittest.TestCase):
Expand Down
64 changes: 64 additions & 0 deletions tests/test_instrumentation_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,17 @@ def test_httpx_forwards_kwargs(self):
self.assertEqual(call_kwargs["request_hook"], "req_hook")
self.assertEqual(call_kwargs["response_hook"], "resp_hook")

def test_httpx2_forwards_kwargs(self):
call_kwargs = self._run_with_options(
"httpx2",
{
"request_hook": "req_hook",
"response_hook": "resp_hook",
},
)
self.assertEqual(call_kwargs["request_hook"], "req_hook")
self.assertEqual(call_kwargs["response_hook"], "resp_hook")

def test_psycopg2_forwards_kwargs(self):
call_kwargs = self._run_with_options(
"psycopg2",
Expand Down Expand Up @@ -448,6 +459,59 @@ def test_multiple_libs_each_get_own_kwargs(self):
self.assertEqual(b_kwargs["request_hook"], "b_hook")
self.assertNotIn("excluded_urls", b_kwargs)

def test_httpx2_can_be_disabled_independently(self):
httpx_instrumentor = MagicMock()
httpx2_instrumentor = MagicMock()

httpx_entry_point = MagicMock(name="httpx_entry_point")
httpx_entry_point.name = "httpx"
httpx_entry_point.load.return_value = lambda: httpx_instrumentor

httpx2_entry_point = MagicMock(name="httpx2_entry_point")
httpx2_entry_point.name = "httpx2"
httpx2_entry_point.load.return_value = lambda: httpx2_instrumentor

with (
patch("microsoft.opentelemetry._distro.get_dist_dependency_conflicts", return_value=None),
patch(
"microsoft.opentelemetry._distro.entry_points",
return_value=[httpx_entry_point, httpx2_entry_point],
),
):
_setup_instrumentations(
{"instrumentation_options": {"httpx2": {"enabled": False}}}
)

httpx_instrumentor.instrument.assert_called_once()
httpx2_instrumentor.instrument.assert_not_called()

@patch("microsoft.opentelemetry._distro.set_sdkstats_instrumentation_by_name")
def test_httpx2_failure_does_not_affect_httpx(self, set_sdkstats):
httpx_instrumentor = MagicMock()
httpx2_instrumentor = MagicMock()
httpx2_instrumentor.instrument.side_effect = RuntimeError("httpx2 failed")

httpx_entry_point = MagicMock(name="httpx_entry_point")
httpx_entry_point.name = "httpx"
httpx_entry_point.load.return_value = lambda: httpx_instrumentor

httpx2_entry_point = MagicMock(name="httpx2_entry_point")
httpx2_entry_point.name = "httpx2"
httpx2_entry_point.load.return_value = lambda: httpx2_instrumentor

with (
patch("microsoft.opentelemetry._distro.get_dist_dependency_conflicts", return_value=None),
patch(
"microsoft.opentelemetry._distro.entry_points",
return_value=[httpx_entry_point, httpx2_entry_point],
),
):
_setup_instrumentations({})

httpx_instrumentor.instrument.assert_called_once()
httpx2_instrumentor.instrument.assert_called_once()
set_sdkstats.assert_called_once_with("httpx")

def test_kwargs_not_forwarded_to_disabled_lib(self):
"""A disabled library should not have instrument() called at all."""
instrumentor_instance = MagicMock()
Expand Down