From 20d9c23e5fa440134c98a958d65f724ee980f143 Mon Sep 17 00:00:00 2001 From: Jagerzhang Date: Mon, 23 Sep 2024 09:50:12 +0800 Subject: [PATCH 01/13] feat: Add Path Parameter Rendering Feature --- .../instrumentation/fastapi/__init__.py | 32 +++++++++++-- .../tests/test_fastapi_instrumentation.py | 47 +++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 7e4d0aac07..0522563f53 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -178,6 +178,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from __future__ import annotations +import re import logging from typing import Collection, Literal @@ -233,6 +234,7 @@ def instrument_app( http_capture_headers_server_response: list[str] | None = None, http_capture_headers_sanitize_fields: list[str] | None = None, exclude_spans: list[Literal["receive", "send"]] | None = None, + render_path_parameters: bool = True, ): """Instrument an uninstrumented FastAPI application. @@ -253,6 +255,7 @@ def instrument_app( http_capture_headers_server_response: Optional list of HTTP headers to capture from the response. http_capture_headers_sanitize_fields: Optional list of HTTP headers to sanitize. exclude_spans: Optionally exclude HTTP `send` and/or `receive` spans from the trace. + render_path_parameters: Optional boolean to enable or disable rendering path parameters in the span name. """ if not hasattr(app, "_is_instrumented_by_opentelemetry"): app._is_instrumented_by_opentelemetry = False @@ -283,7 +286,7 @@ def instrument_app( app.add_middleware( OpenTelemetryMiddleware, excluded_urls=excluded_urls, - default_span_details=_get_default_span_details, + default_span_details=create_span_details_function(render_path_parameters), server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook, @@ -345,6 +348,7 @@ def _instrument(self, **kwargs): ) _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") _InstrumentedFastAPI._exclude_spans = kwargs.get("exclude_spans") + _InstrumentedFastAPI._render_path_parameters = kwargs.get("render_path_parameters") fastapi.FastAPI = _InstrumentedFastAPI def _uninstrument(self, **kwargs): @@ -363,6 +367,7 @@ class _InstrumentedFastAPI(fastapi.FastAPI): _client_response_hook: ClientResponseHook = None _instrumented_fastapi_apps = set() _sem_conv_opt_in_mode = _HTTPStabilityMode.DEFAULT + _render_path_parameters = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -385,7 +390,7 @@ def __init__(self, *args, **kwargs): self.add_middleware( OpenTelemetryMiddleware, excluded_urls=_InstrumentedFastAPI._excluded_urls, - default_span_details=_get_default_span_details, + default_span_details=create_span_details_function(_InstrumentedFastAPI._render_path_parameters), server_request_hook=_InstrumentedFastAPI._server_request_hook, client_request_hook=_InstrumentedFastAPI._client_request_hook, client_response_hook=_InstrumentedFastAPI._client_response_hook, @@ -431,26 +436,47 @@ def _get_route_details(scope): return route -def _get_default_span_details(scope): +def create_span_details_function(render_path_parameters): + + def span_details(scope): + return _get_default_span_details(scope, render_path_parameters) + + return span_details + + +def _get_default_span_details(scope, render_path_parameters: bool = False): """ Callback to retrieve span name and attributes from scope. Args: scope: A Starlette scope + render_path_parameters: A boolean flag to indicate whether to render path parameters. Returns: A tuple of span name and attributes """ route = _get_route_details(scope) method = sanitize_method(scope.get("method", "").strip()) attributes = {} + if method == "_OTHER": method = "HTTP" + if route: + # Here we can replace path parameters with actual values + if render_path_parameters: + path_params = scope.get("path_params", {}) + pattern = re.compile(r"\{(\w+)\}") + route = pattern.sub( + lambda match: str( + path_params.get(match.group(1), match.group(0))), route) + attributes[SpanAttributes.HTTP_ROUTE] = route + if method and route: # http span_name = f"{method} {route}" elif route: # websocket span_name = route else: # fallback span_name = method + return span_name, attributes diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index b8a6ef010e..18c368a067 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -1839,3 +1839,50 @@ def test_custom_header_not_present_in_non_recording_span(self): self.assertEqual(200, resp.status_code) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) + + +class TestFastAPIRenderPathParameters(TestBaseFastAPI): + + def test_render_path_parameters(self): + """Test that path parameters are rendered correctly in spans.""" + + # Create a FastAPI app with a path parameter + app = fastapi.FastAPI() + + @app.get("/user/{username}") + async def read_user(username: str): + return {"username": username} + + # Instrument the app + otel_fastapi.FastAPIInstrumentor().instrument_app( + app, render_path_parameters=True) + client = TestClient(app) + + # Make a request to the endpoint with a path parameter + response = client.get("/user/johndoe") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"username": "johndoe"}) + + # Retrieve the spans generated + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) # Adjust based on expected spans + + # Check that the span for the request contains the expected attributes + server_span = [ + span for span in spans if span.kind == trace.SpanKind.SERVER + ][0] + + # Verify that the path parameter is rendered correctly + self.assertIn("http.route", server_span.attributes) + self.assertEqual(server_span.attributes["http.route"], + "/user/{username}") + + # Optionally, check if the username is also included in the span attributes + self.assertIn("http.path_parameters.username", server_span.attributes) + self.assertEqual( + server_span.attributes["http.path_parameters.username"], "johndoe") + + def tearDown(self): + super().tearDown() + with self.disable_logging(): + otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app) From aa3ba8a2aa0391f0ddbbfaec7998913f91c54bdd Mon Sep 17 00:00:00 2001 From: Jagerzhang Date: Mon, 23 Sep 2024 10:11:45 +0800 Subject: [PATCH 02/13] doc: update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa1ab2ba3..88ffd39c34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#2860](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2860)) - `opentelemetry-instrumentation-aiokafka` Add instrumentor and auto instrumentation support for aiokafka ([#2082](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2082)) +- `opentelemetry-instrumentation-fastapi` Add path parameter rendering feature. + ([#2879](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2879)) ## Version 1.27.0/0.48b0 () From 8220231dade08003af640ec50e125770d45371ff Mon Sep 17 00:00:00 2001 From: Jagerzhang Date: Wed, 25 Sep 2024 10:03:55 +0800 Subject: [PATCH 03/13] test: Adjusting test cases --- .../tests/test_fastapi_instrumentation.py | 47 ---------------- .../test_fastapi_instrumentation_wrapped.py | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 18c368a067..b8a6ef010e 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -1839,50 +1839,3 @@ def test_custom_header_not_present_in_non_recording_span(self): self.assertEqual(200, resp.status_code) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) - - -class TestFastAPIRenderPathParameters(TestBaseFastAPI): - - def test_render_path_parameters(self): - """Test that path parameters are rendered correctly in spans.""" - - # Create a FastAPI app with a path parameter - app = fastapi.FastAPI() - - @app.get("/user/{username}") - async def read_user(username: str): - return {"username": username} - - # Instrument the app - otel_fastapi.FastAPIInstrumentor().instrument_app( - app, render_path_parameters=True) - client = TestClient(app) - - # Make a request to the endpoint with a path parameter - response = client.get("/user/johndoe") - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), {"username": "johndoe"}) - - # Retrieve the spans generated - spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 3) # Adjust based on expected spans - - # Check that the span for the request contains the expected attributes - server_span = [ - span for span in spans if span.kind == trace.SpanKind.SERVER - ][0] - - # Verify that the path parameter is rendered correctly - self.assertIn("http.route", server_span.attributes) - self.assertEqual(server_span.attributes["http.route"], - "/user/{username}") - - # Optionally, check if the username is also included in the span attributes - self.assertIn("http.path_parameters.username", server_span.attributes) - self.assertEqual( - server_span.attributes["http.path_parameters.username"], "johndoe") - - def tearDown(self): - super().tearDown() - with self.disable_logging(): - otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py index 0b17173ac6..6d39bd4ce1 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py @@ -62,3 +62,59 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self): self.assertEqual( parent_span.context.span_id, span_list[3].context.span_id ) + + +class TestFastAPIRenderPathParameters(TestWrappedApplication): + + def setUp(self): + super().setUp() + # Create a FastAPI app with a path parameter + @self.app.get("/user/{username}") + async def read_user(username: str): + return {"username": username} + + # Instrument the app + otel_fastapi.FastAPIInstrumentor().instrument_app( + self.app, render_path_parameters=True) + + def test_render_path_parameters(self): + """Test that path parameters are rendered correctly in spans.""" + + # Make a request to the endpoint with a path parameter + response = self.client.get("/user/johndoe") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"username": "johndoe"}) + + # Retrieve the spans generated + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) # Adjust based on expected spans + + # Check that the span for the request contains the expected attributes + server_span = [ + span for span in spans if span.kind == trace.SpanKind.SERVER + ][0] + + # Verify that the path parameter is rendered correctly + self.assertIn("http.route", server_span.attributes) + self.assertEqual(server_span.attributes["http.route"], + "/user/{username}") + + # Optionally, check if the username is also included in the span attributes + self.assertIn("http.path_parameters.username", server_span.attributes) + self.assertEqual( + server_span.attributes["http.path_parameters.username"], "johndoe") + + # Retrieve the spans generated + spans = self.memory_exporter.get_finished_spans() + + # Assert that at least one span was created + self.assertGreater(len(spans), 0, "No spans were generated.") + + # Assert that the span name is as expected + expected_span_name = "GET /user/johndoe" # Adjust this based on your implementation + self.assertEqual(spans[0].name, expected_span_name, f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'") + + def tearDown(self): + super().tearDown() + with self.disable_logging(): + otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app) From 12285644a9714fee2f56e77194af7e208a5a27b2 Mon Sep 17 00:00:00 2001 From: Jagerzhang Date: Mon, 23 Sep 2024 04:44:07 -0300 Subject: [PATCH 04/13] tests: fix docker-tests mssql (#2878) * try new image * try new image with new pyodbc verrsion --- tests/opentelemetry-docker-tests/tests/docker-compose.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/opentelemetry-docker-tests/tests/docker-compose.yml b/tests/opentelemetry-docker-tests/tests/docker-compose.yml index 2f89e3388e..59f0e42d3d 100644 --- a/tests/opentelemetry-docker-tests/tests/docker-compose.yml +++ b/tests/opentelemetry-docker-tests/tests/docker-compose.yml @@ -51,7 +51,7 @@ services: - "14268:14268" - "9411:9411" otmssql: - image: mcr.microsoft.com/mssql/server:2017-CU23-ubuntu-16.04 + image: mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04 ports: - "1433:1433" environment: diff --git a/tox.ini b/tox.ini index f7e4ce55b3..976e5968b0 100644 --- a/tox.ini +++ b/tox.ini @@ -1257,7 +1257,7 @@ deps = PyMySQL==0.10.1 PyNaCl==1.5.0 # prerequisite: install unixodbc - pyodbc==4.0.39 + pyodbc==5.0.1 pyrsistent==0.20.0 pytest==8.0.2 pytest-celery==0.0.0 From c386b997eb48c9ced9b4eec912d8843ecab7ff88 Mon Sep 17 00:00:00 2001 From: heidi229 Date: Tue, 24 Sep 2024 01:09:06 +0800 Subject: [PATCH 05/13] update editable install link in readme.md (#2881) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a2aaf4f8a..a04c4b7c7b 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ pip install opentelemetry-instrumentation-{integration} To install the development versions of these packages instead, clone or fork this repo and do an [editable -install](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs): +install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs): ```sh pip install -e ./instrumentation/opentelemetry-instrumentation-{integration} From e5529d8b30cce63311dbeead1d5abd7a577599f2 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 23 Sep 2024 22:56:37 +0200 Subject: [PATCH 06/13] opentelemetry-instrumentation-flask: a bit more room on metrics tests (#2884) --- .../tests/test_programmatic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py index 4458daae21..e6bc8202df 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py @@ -520,7 +520,7 @@ def test_flask_metrics_new_semconv(self): if isinstance(point, HistogramDataPoint): self.assertEqual(point.count, 3) self.assertAlmostEqual( - duration_s, point.sum, places=2 + duration_s, point.sum, places=1 ) histogram_data_point_seen = True if isinstance(point, NumberDataPoint): From 6aa9e8df4bebc9d938a2b128e15e327e1a025254 Mon Sep 17 00:00:00 2001 From: Jagerzhang Date: Wed, 25 Sep 2024 10:11:32 +0800 Subject: [PATCH 07/13] feat: arg render_path_parameters default False --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 0522563f53..c5b85854aa 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -234,7 +234,7 @@ def instrument_app( http_capture_headers_server_response: list[str] | None = None, http_capture_headers_sanitize_fields: list[str] | None = None, exclude_spans: list[Literal["receive", "send"]] | None = None, - render_path_parameters: bool = True, + render_path_parameters: bool = False, ): """Instrument an uninstrumented FastAPI application. From 775e04993ffcd15d4f24520df12bed1f055c163c Mon Sep 17 00:00:00 2001 From: Jager Date: Wed, 25 Sep 2024 20:20:57 +0800 Subject: [PATCH 08/13] sync-fork.yml sync fork --- .github/workflows/main.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..38984494c3 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,25 @@ +name: Sync Fork + +on: + schedule: + - cron: '*/10 * * * *' + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + repository: jagerzhang/opentelemetry-python-contrib + token: ${{ secrets.GITHUB_TOKEN }} + path: your-fork + + - name: Sync with upstream + run: | + git remote add upstream https://github.com/open-telemetry/opentelemetry-python-contrib.git + git fetch upstream + git checkout main + git merge upstream/main + git push origin main From 80f661337effa13a88812ae6cea4243fc831d799 Mon Sep 17 00:00:00 2001 From: Jager Date: Wed, 25 Sep 2024 20:21:41 +0800 Subject: [PATCH 09/13] Delete .github/workflows/main.yml --- .github/workflows/main.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 38984494c3..0000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Sync Fork - -on: - schedule: - - cron: '*/10 * * * *' - workflow_dispatch: - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - repository: jagerzhang/opentelemetry-python-contrib - token: ${{ secrets.GITHUB_TOKEN }} - path: your-fork - - - name: Sync with upstream - run: | - git remote add upstream https://github.com/open-telemetry/opentelemetry-python-contrib.git - git fetch upstream - git checkout main - git merge upstream/main - git push origin main From 92c4ef643a97d8451bc9c9bc9c2da0b8cddb8f79 Mon Sep 17 00:00:00 2001 From: Jagerzhang Date: Thu, 26 Sep 2024 09:53:06 +0800 Subject: [PATCH 10/13] test: Merge test cases --- .../test_fastapi_instrumentation_wrapped.py | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py index 6d39bd4ce1..7ccf1e3c90 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py @@ -20,6 +20,7 @@ class TestWrappedApplication(TestBase): + def setUp(self): super().setUp() @@ -29,7 +30,12 @@ def setUp(self): async def _(): return {"message": "hello world"} - otel_fastapi.FastAPIInstrumentor().instrument_app(self.app) + @self.app.get("/user/{username}") + async def _(username: str): + return {"username": username} + + otel_fastapi.FastAPIInstrumentor().instrument_app( + self.app, render_path_parameters=True) self.client = TestClient(self.app) self.tracer = self.tracer_provider.get_tracer(__name__) @@ -40,8 +46,7 @@ def tearDown(self) -> None: def test_mark_span_internal_in_presence_of_span_from_other_framework(self): with self.tracer.start_as_current_span( - "test", kind=trace.SpanKind.SERVER - ) as parent_span: + "test", kind=trace.SpanKind.SERVER) as parent_span: resp = self.client.get("/foobar") self.assertEqual(200, resp.status_code) @@ -54,36 +59,30 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self): self.assertEqual(trace.SpanKind.INTERNAL, span_list[1].kind) # main INTERNAL span - child of test self.assertEqual(trace.SpanKind.INTERNAL, span_list[2].kind) - self.assertEqual( - parent_span.context.span_id, span_list[2].parent.span_id - ) + self.assertEqual(parent_span.context.span_id, + span_list[2].parent.span_id) # SERVER "test" self.assertEqual(trace.SpanKind.SERVER, span_list[3].kind) - self.assertEqual( - parent_span.context.span_id, span_list[3].context.span_id - ) - - -class TestFastAPIRenderPathParameters(TestWrappedApplication): - - def setUp(self): - super().setUp() - # Create a FastAPI app with a path parameter - @self.app.get("/user/{username}") - async def read_user(username: str): - return {"username": username} - - # Instrument the app - otel_fastapi.FastAPIInstrumentor().instrument_app( - self.app, render_path_parameters=True) + self.assertEqual(parent_span.context.span_id, + span_list[3].context.span_id) def test_render_path_parameters(self): """Test that path parameters are rendered correctly in spans.""" + # Make sure non-path parameters are not affected + resp = self.client.get("/foobar") + self.assertEqual(resp.status_code, 200) + spans = self.memory_exporter.get_finished_spans() + expected_span_name = "GET /foobar" + self.assertEqual( + spans[0].name, 'GET /foobar', + f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'" + ) + # Make a request to the endpoint with a path parameter - response = self.client.get("/user/johndoe") - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), {"username": "johndoe"}) + resp = self.client.get("/user/johndoe") + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.json(), {"username": "johndoe"}) # Retrieve the spans generated spans = self.memory_exporter.get_finished_spans() @@ -103,7 +102,7 @@ def test_render_path_parameters(self): self.assertIn("http.path_parameters.username", server_span.attributes) self.assertEqual( server_span.attributes["http.path_parameters.username"], "johndoe") - + # Retrieve the spans generated spans = self.memory_exporter.get_finished_spans() @@ -112,9 +111,7 @@ def test_render_path_parameters(self): # Assert that the span name is as expected expected_span_name = "GET /user/johndoe" # Adjust this based on your implementation - self.assertEqual(spans[0].name, expected_span_name, f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'") - - def tearDown(self): - super().tearDown() - with self.disable_logging(): - otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app) + self.assertEqual( + spans[0].name, expected_span_name, + f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'" + ) From 8c9eb2788ccb080be33caeee98cf0230f792e2a5 Mon Sep 17 00:00:00 2001 From: Jager Date: Fri, 18 Oct 2024 09:59:31 +0800 Subject: [PATCH 11/13] style: fix black warning --- .../test_fastapi_instrumentation_wrapped.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py index 7ccf1e3c90..b1d9a8a6e3 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py @@ -35,7 +35,8 @@ async def _(username: str): return {"username": username} otel_fastapi.FastAPIInstrumentor().instrument_app( - self.app, render_path_parameters=True) + self.app, render_path_parameters=True + ) self.client = TestClient(self.app) self.tracer = self.tracer_provider.get_tracer(__name__) @@ -46,7 +47,8 @@ def tearDown(self) -> None: def test_mark_span_internal_in_presence_of_span_from_other_framework(self): with self.tracer.start_as_current_span( - "test", kind=trace.SpanKind.SERVER) as parent_span: + "test", kind=trace.SpanKind.SERVER + ) as parent_span: resp = self.client.get("/foobar") self.assertEqual(200, resp.status_code) @@ -59,12 +61,14 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self): self.assertEqual(trace.SpanKind.INTERNAL, span_list[1].kind) # main INTERNAL span - child of test self.assertEqual(trace.SpanKind.INTERNAL, span_list[2].kind) - self.assertEqual(parent_span.context.span_id, - span_list[2].parent.span_id) + self.assertEqual( + parent_span.context.span_id, span_list[2].parent.span_id + ) # SERVER "test" self.assertEqual(trace.SpanKind.SERVER, span_list[3].kind) - self.assertEqual(parent_span.context.span_id, - span_list[3].context.span_id) + self.assertEqual( + parent_span.context.span_id, span_list[3].context.span_id + ) def test_render_path_parameters(self): """Test that path parameters are rendered correctly in spans.""" From 9022641fbec64102cb7f89c0fc1eb3428fd1763d Mon Sep 17 00:00:00 2001 From: Jager Date: Fri, 18 Oct 2024 10:06:21 +0800 Subject: [PATCH 12/13] style: black format --- .../test_fastapi_instrumentation_wrapped.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py index b1d9a8a6e3..59355f479d 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py @@ -79,8 +79,9 @@ def test_render_path_parameters(self): spans = self.memory_exporter.get_finished_spans() expected_span_name = "GET /foobar" self.assertEqual( - spans[0].name, 'GET /foobar', - f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'" + spans[0].name, + "GET /foobar", + f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'", ) # Make a request to the endpoint with a path parameter @@ -99,13 +100,15 @@ def test_render_path_parameters(self): # Verify that the path parameter is rendered correctly self.assertIn("http.route", server_span.attributes) - self.assertEqual(server_span.attributes["http.route"], - "/user/{username}") + self.assertEqual( + server_span.attributes["http.route"], "/user/{username}" + ) # Optionally, check if the username is also included in the span attributes self.assertIn("http.path_parameters.username", server_span.attributes) self.assertEqual( - server_span.attributes["http.path_parameters.username"], "johndoe") + server_span.attributes["http.path_parameters.username"], "johndoe" + ) # Retrieve the spans generated spans = self.memory_exporter.get_finished_spans() @@ -114,8 +117,11 @@ def test_render_path_parameters(self): self.assertGreater(len(spans), 0, "No spans were generated.") # Assert that the span name is as expected - expected_span_name = "GET /user/johndoe" # Adjust this based on your implementation + expected_span_name = ( + "GET /user/johndoe" # Adjust this based on your implementation + ) self.assertEqual( - spans[0].name, expected_span_name, - f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'" + spans[0].name, + expected_span_name, + f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'", ) From 4c36774411081ad93097737b6bf9ff75179b1ed0 Mon Sep 17 00:00:00 2001 From: Jager Date: Fri, 25 Oct 2024 17:14:40 +0800 Subject: [PATCH 13/13] fix: fastapi test_fastapi_instrumentation_wrapped run failed --- .../tests/test_fastapi_instrumentation_wrapped.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py index 59355f479d..76f8b547d2 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation_wrapped.py @@ -77,10 +77,10 @@ def test_render_path_parameters(self): resp = self.client.get("/foobar") self.assertEqual(resp.status_code, 200) spans = self.memory_exporter.get_finished_spans() - expected_span_name = "GET /foobar" + expected_span_name = "GET /foobar http send" self.assertEqual( spans[0].name, - "GET /foobar", + expected_span_name, f"Expected span name to be '{expected_span_name}', but got '{spans[0].name}'", )