diff --git a/CHANGELOG.md b/CHANGELOG.md index dc9c6e01c8..a14cc60ba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-[asynclick/click]` Add missing opentelemetry-instrumentation dep ([#3447](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3447)) - `opentelemetry-instrumentation-botocore` Capture server attributes for botocore API calls - ([#3448](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3448)) + ([#3448](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3448 +- `opentelemetry-instrumentation-starlette` Remove max version constraint on starlete, increase min version ([#3456](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3456)) ## Version 1.32.0/0.53b0 (2025-04-10) diff --git a/instrumentation/README.md b/instrumentation/README.md index 4c89015b78..45f84b2425 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -44,7 +44,7 @@ | [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 | Yes | migration | [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy >= 1.0.0, < 2.1.0 | Yes | development | [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | No | development -| [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette >= 0.13, <0.15 | Yes | development +| [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette >= 0.37.2 | Yes | development | [opentelemetry-instrumentation-system-metrics](./opentelemetry-instrumentation-system-metrics) | psutil >= 5 | No | development | [opentelemetry-instrumentation-threading](./opentelemetry-instrumentation-threading) | threading | No | development | [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 5.1.1 | Yes | development diff --git a/instrumentation/opentelemetry-instrumentation-starlette/pyproject.toml b/instrumentation/opentelemetry-instrumentation-starlette/pyproject.toml index 751cf10ee8..144d2b71a9 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/pyproject.toml +++ b/instrumentation/opentelemetry-instrumentation-starlette/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ ] [project.optional-dependencies] -instruments = ["starlette >= 0.13, <0.15"] +instruments = ["starlette >= 0.37.2"] [project.entry-points.opentelemetry_instrumentor] starlette = "opentelemetry.instrumentation.starlette:StarletteInstrumentor" diff --git a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py index 8df666c740..fa69fab5e3 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py @@ -332,7 +332,7 @@ def __init__(self, *args: Any, **kwargs: Any): _InstrumentedStarlette._instrumented_starlette_apps.add(self) def __del__(self): - _InstrumentedStarlette._instrumented_starlette_apps.remove(self) + _InstrumentedStarlette._instrumented_starlette_apps.discard(self) def _get_route_details(scope: dict[str, Any]) -> str | None: diff --git a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/package.py b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/package.py index ca95e754a8..6e5d06a60b 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/package.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/package.py @@ -13,6 +13,6 @@ # limitations under the License. -_instruments = ("starlette >= 0.13, <0.15",) +_instruments = ("starlette >= 0.37.2",) _supports_metrics = True diff --git a/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.latest.txt b/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.latest.txt index 7b6dd8cff4..a9b326ff6e 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.latest.txt +++ b/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.latest.txt @@ -17,9 +17,13 @@ # opentelemetry-instrumentation-asgi # opentelemetry-instrumentation-starlette anyio==4.5.2 ; python_full_version < '3.9' - # via httpx + # via + # httpx + # starlette anyio==4.8.0 ; python_full_version >= '3.9' - # via httpx + # via + # httpx + # starlette asgiref==3.8.1 # via opentelemetry-instrumentation-asgi certifi==2025.1.31 @@ -72,7 +76,9 @@ requests==2.32.3 # -r instrumentation/opentelemetry-instrumentation-starlette/test-requirements.in sniffio==1.3.1 # via anyio -starlette==0.14.2 +starlette==0.44.0 ; python_full_version < '3.9' + # via opentelemetry-instrumentation-starlette +starlette==0.46.2 ; python_full_version >= '3.9' # via opentelemetry-instrumentation-starlette tomli==2.2.1 ; python_full_version < '3.11' # via pytest @@ -80,6 +86,7 @@ typing-extensions==4.12.2 ; python_full_version < '3.13' # via # anyio # asgiref + # starlette urllib3==2.2.3 ; python_full_version < '3.9' # via requests urllib3==2.3.0 ; python_full_version >= '3.9' diff --git a/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.oldest.txt b/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.oldest.txt index 5f84cdf28f..f7e4c3afe1 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.oldest.txt +++ b/instrumentation/opentelemetry-instrumentation-starlette/test-requirements.oldest.txt @@ -17,21 +17,27 @@ # opentelemetry-instrumentation-asgi # opentelemetry-instrumentation-starlette anyio==4.5.2 ; python_full_version < '3.9' - # via httpx -anyio==4.8.0 ; python_full_version >= '3.9' - # via httpx -asgiref==3.8.1 + # via + # httpx + # starlette +anyio==4.9.0 ; python_full_version >= '3.9' + # via + # httpx + # starlette +asgiref==3.0.0 # via opentelemetry-instrumentation-asgi -certifi==2025.1.31 +async-timeout==3.0.1 + # via asgiref +certifi==2025.4.26 # via # httpcore # httpx # requests -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 # via requests colorama==0.4.6 ; sys_platform == 'win32' # via pytest -deprecated==1.2.14 +deprecated==1.2.18 # via # opentelemetry-api # opentelemetry-semantic-conventions @@ -39,9 +45,9 @@ exceptiongroup==1.2.2 ; python_full_version < '3.11' # via # anyio # pytest -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.7 +httpcore==1.0.9 # via httpx httpx==0.28.0 # via -r instrumentation/opentelemetry-instrumentation-starlette/test-requirements.in @@ -54,14 +60,18 @@ importlib-metadata==8.5.0 ; python_full_version < '3.9' # via opentelemetry-api importlib-metadata==8.6.1 ; python_full_version >= '3.9' # via opentelemetry-api -iniconfig==2.0.0 +iniconfig==2.1.0 # via pytest -packaging==24.0 +packaging==18.0 # via # opentelemetry-instrumentation # pytest pluggy==1.5.0 # via pytest +pyparsing==3.1.4 ; python_full_version < '3.9' + # via packaging +pyparsing==3.2.3 ; python_full_version >= '3.9' + # via packaging pytest==7.4.4 # via # -c dev-requirements.txt @@ -70,21 +80,23 @@ requests==2.32.3 # via # -c dev-requirements.txt # -r instrumentation/opentelemetry-instrumentation-starlette/test-requirements.in +six==1.17.0 + # via packaging sniffio==1.3.1 # via anyio -starlette==0.13.8 +starlette==0.37.2 # via opentelemetry-instrumentation-starlette tomli==2.2.1 ; python_full_version < '3.11' # via pytest -typing-extensions==4.12.2 ; python_full_version < '3.13' +typing-extensions==4.13.2 ; python_full_version < '3.13' # via # anyio - # asgiref + # starlette urllib3==2.2.3 ; python_full_version < '3.9' # via requests -urllib3==2.3.0 ; python_full_version >= '3.9' +urllib3==2.4.0 ; python_full_version >= '3.9' # via requests -wrapt==1.16.0 +wrapt==1.10.0 # via # deprecated # opentelemetry-instrumentation diff --git a/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py b/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py index 3f9f1c7b0f..faded2b756 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py @@ -18,7 +18,7 @@ from starlette import applications from starlette.responses import PlainTextResponse -from starlette.routing import Mount, Route +from starlette.routing import Mount, Route, WebSocketRoute from starlette.testclient import TestClient from starlette.websockets import WebSocket @@ -621,10 +621,7 @@ def tearDown(self) -> None: @staticmethod def create_starlette_app(): - app = applications.Starlette() - - @app.route("/foobar") - def _(request): + def foobar(request): return PlainTextResponse( content="hi", headers={ @@ -636,8 +633,7 @@ def _(request): }, ) - @app.websocket_route("/foobar_web") - async def _(websocket: WebSocket) -> None: + async def foobar_web(websocket: WebSocket) -> None: message = await websocket.receive() if message.get("type") == "websocket.connect": await websocket.send( @@ -663,20 +659,30 @@ async def _(websocket: WebSocket) -> None: if message.get("type") == "websocket.disconnect": pass - return app + return applications.Starlette( + routes=[ + Route("/foobar", foobar), + WebSocketRoute("/foobar_web", foobar_web), + ] + ) class TestHTTPAppWithCustomHeaders(TestBaseWithCustomHeaders): - @patch.dict( - "os.environ", - { - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: ".*my-secret.*", - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,Regex-Test-Header-.*,Regex-Invalid-Test-Header-.*,.*my-secret.*", - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,my-custom-regex-header-.*,invalid-regex-header-.*,.*my-secret.*", - }, - ) - def setUp(self) -> None: + def setUp(self): super().setUp() + self.test_env_patch = patch.dict( + "os.environ", + { + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: ".*my-secret.*", + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,Regex-Test-Header-.*,Regex-Invalid-Test-Header-.*,.*my-secret.*", + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,my-custom-regex-header-.*,invalid-regex-header-.*,.*my-secret.*", + }, + ) + self.test_env_patch.start() + + def tearDown(self): + self.test_env_patch.stop() + super().tearDown() def test_custom_request_headers_in_span_attributes(self): expected = { @@ -793,17 +799,22 @@ def test_custom_response_headers_not_in_span_attributes(self): class TestWebSocketAppWithCustomHeaders(TestBaseWithCustomHeaders): - @patch.dict( - "os.environ", - { - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: ".*my-secret.*", - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,Regex-Test-Header-.*,Regex-Invalid-Test-Header-.*,.*my-secret.*", - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,my-custom-regex-header-.*,invalid-regex-header-.*,.*my-secret.*", - }, - ) - def setUp(self) -> None: + def setUp(self): + self.test_env_patch = patch.dict( + "os.environ", + { + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: ".*my-secret.*", + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,Regex-Test-Header-.*,Regex-Invalid-Test-Header-.*,.*my-secret.*", + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,my-custom-regex-header-.*,invalid-regex-header-.*,.*my-secret.*", + }, + ) + self.test_env_patch.start() super().setUp() + def tearDown(self): + super().tearDown() + self.test_env_patch.stop() + def test_custom_request_headers_in_span_attributes(self): expected = { "http.request.header.custom_test_header_1": ( @@ -918,23 +929,29 @@ def test_custom_response_headers_not_in_span_attributes(self): self.assertNotIn(key, server_span.attributes) -@patch.dict( - "os.environ", - { - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: ".*my-secret.*", - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,Regex-Test-Header-.*,Regex-Invalid-Test-Header-.*,.*my-secret.*", - OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,my-custom-regex-header-.*,invalid-regex-header-.*,.*my-secret.*", - }, -) class TestNonRecordingSpanWithCustomHeaders(TestBaseWithCustomHeaders): def setUp(self): super().setUp() + self.test_env_patch = patch.dict( + "os.environ", + { + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: ".*my-secret.*", + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,Regex-Test-Header-.*,Regex-Invalid-Test-Header-.*,.*my-secret.*", + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "Custom-Test-Header-1,Custom-Test-Header-2,Custom-Test-Header-3,my-custom-regex-header-.*,invalid-regex-header-.*,.*my-secret.*", + }, + ) + self.test_env_patch.start() + reset_trace_globals() set_tracer_provider(tracer_provider=NoOpTracerProvider()) self._app = self.create_app() self._client = TestClient(self._app) + def tearDown(self): + self.test_env_patch.stop() + super().tearDown() + def test_custom_header_not_present_in_non_recording_span(self): resp = self._client.get( "/foobar", diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py index ef84df24c9..f25b7a49fc 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -189,7 +189,7 @@ "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.54b0.dev", }, { - "library": "starlette >= 0.13, <0.15", + "library": "starlette >= 0.37.2", "instrumentation": "opentelemetry-instrumentation-starlette==0.54b0.dev", }, { diff --git a/uv.lock b/uv.lock index bf2d9f8e73..de4891cbc6 100644 --- a/uv.lock +++ b/uv.lock @@ -1330,15 +1330,17 @@ wheels = [ [[package]] name = "fastapi" -version = "0.64.0" +version = "0.115.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, - { name = "starlette" }, + { name = "starlette", version = "0.44.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "starlette", version = "0.46.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/fe/064bdb5598ad9dfe942b58884296bcd7b81d1e13e5af48fd328e38b2de97/fastapi-0.64.0.tar.gz", hash = "sha256:9bbd7b7b9291bbc3bbd72cbc82f5d456369802dab0d142a85350b06c5c7e6379", size = 5481759 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/cc/929a628d250bc81ff39aa2a3f8d66b3ea2e07c3c93135e9b0303058ccd59/fastapi-0.64.0-py3-none-any.whl", hash = "sha256:62a438d0ff466640939414436339ce4e303964f3f823b7288e300baa869162e3", size = 50849 }, + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, ] [[package]] @@ -3830,7 +3832,8 @@ dependencies = [ [package.optional-dependencies] instruments = [ - { name = "starlette" }, + { name = "starlette", version = "0.44.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "starlette", version = "0.46.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] [package.metadata] @@ -3840,7 +3843,7 @@ requires-dist = [ { name = "opentelemetry-instrumentation-asgi", editable = "instrumentation/opentelemetry-instrumentation-asgi" }, { name = "opentelemetry-semantic-conventions", git = "https://github.com/open-telemetry/opentelemetry-python?subdirectory=opentelemetry-semantic-conventions&branch=main" }, { name = "opentelemetry-util-http", editable = "util/opentelemetry-util-http" }, - { name = "starlette", marker = "extra == 'instruments'", specifier = ">=0.13,<0.15" }, + { name = "starlette", marker = "extra == 'instruments'", specifier = ">=0.37.2" }, ] provides-extras = ["instruments"] @@ -5395,11 +5398,37 @@ wheels = [ [[package]] name = "starlette" -version = "0.13.6" +version = "0.44.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/b4/910f693584958b687b8f9c628f8217cfef19a42b64d2de7840814937365c/starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715", size = 2575579 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/c5/7ae467eeddb57260c8ce17a3a09f9f5edba35820fc022d7c55b7decd5d3a/starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea", size = 73412 }, +] + +[[package]] +name = "starlette" +version = "0.46.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/da/ddd95317478f8f4be8e5445c0c5acf928b1192953834a520afdacf13ddea/starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc", size = 49216 } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions", marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/a4/c9e228d7d47044ce4c83ba002f28ff479e542455f0499198a3f77c94f564/starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9", size = 59998 }, + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, ] [[package]]