HTTP semantic convention stability migration for fastapi (#2682)
This commit is contained in:
		
							parent
							
								
									5a27946920
								
							
						
					
					
						commit
						6293d6a991
					
				|  | @ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
|   ([#2638](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2638)) | ||||
| - `opentelemetry-instrumentation-asgi` Implement new semantic convention opt-in with stable http semantic conventions | ||||
|   ([#2610](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2610)) | ||||
| - `opentelemetry-instrumentation-fastapi` Implement new semantic convention opt-in with stable http semantic conventions | ||||
|   ([#2682](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2682)) | ||||
| - `opentelemetry-instrumentation-httpx` Implement new semantic convention opt-in migration with stable http semantic conventions | ||||
|   ([#2631](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2631)) | ||||
| - `opentelemetry-instrumentation-system-metrics` Permit to use psutil 6.0+. | ||||
|  | @ -32,9 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 
 | ||||
| - `opentelemetry-instrumentation-asgi`, `opentelemetry-instrumentation-fastapi`, `opentelemetry-instrumentation-starlette` Use `tracer` and `meter` of originating components instead of one from `asgi` middleware | ||||
|   ([#2580](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2580)) | ||||
| - Populate `{method}` as `HTTP` on `_OTHER` methods from scope | ||||
| - Populate `{method}` as `HTTP` on `_OTHER` methods from scope for `asgi` middleware | ||||
|   ([#2610](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2610)) | ||||
| 
 | ||||
|   - Populate `{method}` as `HTTP` on `_OTHER` methods from scope for `fastapi` middleware | ||||
|   ([#2682](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2682)) | ||||
| 
 | ||||
| ### Fixed | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ | |||
| | [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | Yes | experimental | ||||
| | [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 6.0 | No | experimental | ||||
| | [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 4.0.0 | Yes | experimental | ||||
| | [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | experimental | ||||
| | [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | migration | ||||
| | [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | Yes | migration | ||||
| | [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio ~= 1.27 | No | experimental | ||||
| | [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0 | No | migration | ||||
|  |  | |||
|  | @ -177,6 +177,12 @@ from typing import Collection | |||
| import fastapi | ||||
| from starlette.routing import Match | ||||
| 
 | ||||
| from opentelemetry.instrumentation._semconv import ( | ||||
|     _get_schema_url, | ||||
|     _HTTPStabilityMode, | ||||
|     _OpenTelemetrySemanticConventionStability, | ||||
|     _OpenTelemetryStabilitySignalType, | ||||
| ) | ||||
| from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware | ||||
| from opentelemetry.instrumentation.asgi.types import ( | ||||
|     ClientRequestHook, | ||||
|  | @ -189,7 +195,11 @@ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor | |||
| from opentelemetry.metrics import get_meter | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.trace import get_tracer | ||||
| from opentelemetry.util.http import get_excluded_urls, parse_excluded_urls | ||||
| from opentelemetry.util.http import ( | ||||
|     get_excluded_urls, | ||||
|     parse_excluded_urls, | ||||
|     sanitize_method, | ||||
| ) | ||||
| 
 | ||||
| _excluded_urls_from_env = get_excluded_urls("FASTAPI") | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | @ -218,6 +228,11 @@ class FastAPIInstrumentor(BaseInstrumentor): | |||
|             app._is_instrumented_by_opentelemetry = False | ||||
| 
 | ||||
|         if not getattr(app, "_is_instrumented_by_opentelemetry", False): | ||||
|             # initialize semantic conventions opt-in if needed | ||||
|             _OpenTelemetrySemanticConventionStability._initialize() | ||||
|             sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( | ||||
|                 _OpenTelemetryStabilitySignalType.HTTP, | ||||
|             ) | ||||
|             if excluded_urls is None: | ||||
|                 excluded_urls = _excluded_urls_from_env | ||||
|             else: | ||||
|  | @ -226,13 +241,13 @@ class FastAPIInstrumentor(BaseInstrumentor): | |||
|                 __name__, | ||||
|                 __version__, | ||||
|                 tracer_provider, | ||||
|                 schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|                 schema_url=_get_schema_url(sem_conv_opt_in_mode), | ||||
|             ) | ||||
|             meter = get_meter( | ||||
|                 __name__, | ||||
|                 __version__, | ||||
|                 meter_provider, | ||||
|                 schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|                 schema_url=_get_schema_url(sem_conv_opt_in_mode), | ||||
|             ) | ||||
| 
 | ||||
|             app.add_middleware( | ||||
|  | @ -303,6 +318,7 @@ class _InstrumentedFastAPI(fastapi.FastAPI): | |||
|     _client_request_hook: ClientRequestHook = None | ||||
|     _client_response_hook: ClientResponseHook = None | ||||
|     _instrumented_fastapi_apps = set() | ||||
|     _sem_conv_opt_in_mode = _HTTPStabilityMode.DEFAULT | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | @ -310,13 +326,17 @@ class _InstrumentedFastAPI(fastapi.FastAPI): | |||
|             __name__, | ||||
|             __version__, | ||||
|             _InstrumentedFastAPI._tracer_provider, | ||||
|             schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|             schema_url=_get_schema_url( | ||||
|                 _InstrumentedFastAPI._sem_conv_opt_in_mode | ||||
|             ), | ||||
|         ) | ||||
|         meter = get_meter( | ||||
|             __name__, | ||||
|             __version__, | ||||
|             _InstrumentedFastAPI._meter_provider, | ||||
|             schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|             schema_url=_get_schema_url( | ||||
|                 _InstrumentedFastAPI._sem_conv_opt_in_mode | ||||
|             ), | ||||
|         ) | ||||
|         self.add_middleware( | ||||
|             OpenTelemetryMiddleware, | ||||
|  | @ -373,8 +393,10 @@ def _get_default_span_details(scope): | |||
|         A tuple of span name and attributes | ||||
|     """ | ||||
|     route = _get_route_details(scope) | ||||
|     method = scope.get("method", "") | ||||
|     method = sanitize_method(scope.get("method", "").strip()) | ||||
|     attributes = {} | ||||
|     if method == "_OTHER": | ||||
|         method = "HTTP" | ||||
|     if route: | ||||
|         attributes[SpanAttributes.HTTP_ROUTE] = route | ||||
|     if method and route:  # http | ||||
|  |  | |||
|  | @ -16,3 +16,5 @@ | |||
| _instruments = ("fastapi ~= 0.58",) | ||||
| 
 | ||||
| _supports_metrics = True | ||||
| 
 | ||||
| _semconv_status = "migration" | ||||
|  |  | |||
|  | @ -11,6 +11,9 @@ | |||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| # pylint: disable=too-many-lines | ||||
| 
 | ||||
| import unittest | ||||
| from timeit import default_timer | ||||
| from unittest.mock import patch | ||||
|  | @ -20,39 +23,77 @@ from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware | |||
| from fastapi.testclient import TestClient | ||||
| 
 | ||||
| import opentelemetry.instrumentation.fastapi as otel_fastapi | ||||
| from opentelemetry.instrumentation._semconv import ( | ||||
|     OTEL_SEMCONV_STABILITY_OPT_IN, | ||||
|     _OpenTelemetrySemanticConventionStability, | ||||
|     _server_active_requests_count_attrs_new, | ||||
|     _server_active_requests_count_attrs_old, | ||||
|     _server_duration_attrs_new, | ||||
|     _server_duration_attrs_old, | ||||
| ) | ||||
| from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware | ||||
| from opentelemetry.sdk.metrics.export import ( | ||||
|     HistogramDataPoint, | ||||
|     NumberDataPoint, | ||||
| ) | ||||
| from opentelemetry.sdk.resources import Resource | ||||
| from opentelemetry.semconv.attributes.http_attributes import ( | ||||
|     HTTP_REQUEST_METHOD, | ||||
|     HTTP_RESPONSE_STATUS_CODE, | ||||
|     HTTP_ROUTE, | ||||
| ) | ||||
| from opentelemetry.semconv.attributes.network_attributes import ( | ||||
|     NETWORK_PROTOCOL_VERSION, | ||||
| ) | ||||
| from opentelemetry.semconv.attributes.url_attributes import URL_SCHEME | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.util.http import ( | ||||
|     _active_requests_count_attrs, | ||||
|     _duration_attrs, | ||||
|     get_excluded_urls, | ||||
| ) | ||||
| from opentelemetry.util.http import get_excluded_urls | ||||
| 
 | ||||
| _expected_metric_names = [ | ||||
| _expected_metric_names_old = [ | ||||
|     "http.server.active_requests", | ||||
|     "http.server.duration", | ||||
|     "http.server.response.size", | ||||
|     "http.server.request.size", | ||||
| ] | ||||
| _recommended_attrs = { | ||||
|     "http.server.active_requests": _active_requests_count_attrs, | ||||
|     "http.server.duration": {*_duration_attrs, SpanAttributes.HTTP_TARGET}, | ||||
| _expected_metric_names_new = [ | ||||
|     "http.server.active_requests", | ||||
|     "http.server.request.duration", | ||||
|     "http.server.response.body.size", | ||||
|     "http.server.request.body.size", | ||||
| ] | ||||
| _expected_metric_names_both = _expected_metric_names_old | ||||
| _expected_metric_names_both.extend(_expected_metric_names_new) | ||||
| 
 | ||||
| _recommended_attrs_old = { | ||||
|     "http.server.active_requests": _server_active_requests_count_attrs_old, | ||||
|     "http.server.duration": { | ||||
|         *_server_duration_attrs_old, | ||||
|         SpanAttributes.HTTP_TARGET, | ||||
|     }, | ||||
|     "http.server.response.size": { | ||||
|         *_duration_attrs, | ||||
|         *_server_duration_attrs_old, | ||||
|         SpanAttributes.HTTP_TARGET, | ||||
|     }, | ||||
|     "http.server.request.size": { | ||||
|         *_duration_attrs, | ||||
|         *_server_duration_attrs_old, | ||||
|         SpanAttributes.HTTP_TARGET, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| _recommended_attrs_new = { | ||||
|     "http.server.active_requests": _server_active_requests_count_attrs_new, | ||||
|     "http.server.request.duration": _server_duration_attrs_new, | ||||
|     "http.server.response.body.size": _server_duration_attrs_new, | ||||
|     "http.server.request.body.size": _server_duration_attrs_new, | ||||
| } | ||||
| 
 | ||||
| _recommended_attrs_both = _recommended_attrs_old.copy() | ||||
| _recommended_attrs_both.update(_recommended_attrs_new) | ||||
| _recommended_attrs_both["http.server.active_requests"].extend( | ||||
|     _server_active_requests_count_attrs_old | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class TestBaseFastAPI(TestBase): | ||||
|     def _create_app(self): | ||||
|  | @ -88,10 +129,23 @@ class TestBaseFastAPI(TestBase): | |||
| 
 | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|         test_name = "" | ||||
|         if hasattr(self, "_testMethodName"): | ||||
|             test_name = self._testMethodName | ||||
|         sem_conv_mode = "default" | ||||
|         if "new_semconv" in test_name: | ||||
|             sem_conv_mode = "http" | ||||
|         elif "both_semconv" in test_name: | ||||
|             sem_conv_mode = "http/dup" | ||||
|         self.env_patch = patch.dict( | ||||
|             "os.environ", | ||||
|             {"OTEL_PYTHON_FASTAPI_EXCLUDED_URLS": "/exclude/123,healthzz"}, | ||||
|             { | ||||
|                 "OTEL_PYTHON_FASTAPI_EXCLUDED_URLS": "/exclude/123,healthzz", | ||||
|                 OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode, | ||||
|             }, | ||||
|         ) | ||||
|         _OpenTelemetrySemanticConventionStability._initialized = False | ||||
|         self.env_patch.start() | ||||
|         self.exclude_patch = patch( | ||||
|             "opentelemetry.instrumentation.fastapi._excluded_urls_from_env", | ||||
|  | @ -142,7 +196,6 @@ class TestBaseFastAPI(TestBase): | |||
| 
 | ||||
| 
 | ||||
| class TestBaseManualFastAPI(TestBaseFastAPI): | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         if cls is TestBaseManualFastAPI: | ||||
|  | @ -196,7 +249,6 @@ class TestBaseManualFastAPI(TestBaseFastAPI): | |||
| 
 | ||||
| 
 | ||||
| class TestBaseAutoFastAPI(TestBaseFastAPI): | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         if cls is TestBaseAutoFastAPI: | ||||
|  | @ -259,6 +311,7 @@ class TestBaseAutoFastAPI(TestBaseFastAPI): | |||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| # pylint: disable=too-many-public-methods | ||||
| class TestFastAPIManualInstrumentation(TestBaseManualFastAPI): | ||||
|     def test_instrument_app_with_instrument(self): | ||||
|         if not isinstance(self, TestAutoInstrumentation): | ||||
|  | @ -358,7 +411,7 @@ class TestFastAPIManualInstrumentation(TestBaseManualFastAPI): | |||
|                 ) | ||||
|                 self.assertTrue(len(scope_metric.metrics) == 3) | ||||
|                 for metric in scope_metric.metrics: | ||||
|                     self.assertIn(metric.name, _expected_metric_names) | ||||
|                     self.assertIn(metric.name, _expected_metric_names_old) | ||||
|                     data_points = list(metric.data.data_points) | ||||
|                     self.assertEqual(len(data_points), 1) | ||||
|                     for point in data_points: | ||||
|  | @ -369,7 +422,71 @@ class TestFastAPIManualInstrumentation(TestBaseManualFastAPI): | |||
|                             number_data_point_seen = True | ||||
|                         for attr in point.attributes: | ||||
|                             self.assertIn( | ||||
|                                 attr, _recommended_attrs[metric.name] | ||||
|                                 attr, _recommended_attrs_old[metric.name] | ||||
|                             ) | ||||
|         self.assertTrue(number_data_point_seen and histogram_data_point_seen) | ||||
| 
 | ||||
|     def test_fastapi_metrics_new_semconv(self): | ||||
|         self._client.get("/foobar") | ||||
|         self._client.get("/foobar") | ||||
|         self._client.get("/foobar") | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         number_data_point_seen = False | ||||
|         histogram_data_point_seen = False | ||||
|         self.assertTrue(len(metrics_list.resource_metrics) == 1) | ||||
|         for resource_metric in metrics_list.resource_metrics: | ||||
|             self.assertTrue(len(resource_metric.scope_metrics) == 1) | ||||
|             for scope_metric in resource_metric.scope_metrics: | ||||
|                 self.assertEqual( | ||||
|                     scope_metric.scope.name, | ||||
|                     "opentelemetry.instrumentation.fastapi", | ||||
|                 ) | ||||
|                 self.assertTrue(len(scope_metric.metrics) == 3) | ||||
|                 for metric in scope_metric.metrics: | ||||
|                     self.assertIn(metric.name, _expected_metric_names_new) | ||||
|                     data_points = list(metric.data.data_points) | ||||
|                     self.assertEqual(len(data_points), 1) | ||||
|                     for point in data_points: | ||||
|                         if isinstance(point, HistogramDataPoint): | ||||
|                             self.assertEqual(point.count, 3) | ||||
|                             histogram_data_point_seen = True | ||||
|                         if isinstance(point, NumberDataPoint): | ||||
|                             number_data_point_seen = True | ||||
|                         for attr in point.attributes: | ||||
|                             self.assertIn( | ||||
|                                 attr, _recommended_attrs_new[metric.name] | ||||
|                             ) | ||||
|         self.assertTrue(number_data_point_seen and histogram_data_point_seen) | ||||
| 
 | ||||
|     def test_fastapi_metrics_both_semconv(self): | ||||
|         self._client.get("/foobar") | ||||
|         self._client.get("/foobar") | ||||
|         self._client.get("/foobar") | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         number_data_point_seen = False | ||||
|         histogram_data_point_seen = False | ||||
|         self.assertTrue(len(metrics_list.resource_metrics) == 1) | ||||
|         for resource_metric in metrics_list.resource_metrics: | ||||
|             self.assertTrue(len(resource_metric.scope_metrics) == 1) | ||||
|             for scope_metric in resource_metric.scope_metrics: | ||||
|                 self.assertEqual( | ||||
|                     scope_metric.scope.name, | ||||
|                     "opentelemetry.instrumentation.fastapi", | ||||
|                 ) | ||||
|                 self.assertTrue(len(scope_metric.metrics) == 5) | ||||
|                 for metric in scope_metric.metrics: | ||||
|                     self.assertIn(metric.name, _expected_metric_names_both) | ||||
|                     data_points = list(metric.data.data_points) | ||||
|                     self.assertEqual(len(data_points), 1) | ||||
|                     for point in data_points: | ||||
|                         if isinstance(point, HistogramDataPoint): | ||||
|                             self.assertEqual(point.count, 3) | ||||
|                             histogram_data_point_seen = True | ||||
|                         if isinstance(point, NumberDataPoint): | ||||
|                             number_data_point_seen = True | ||||
|                         for attr in point.attributes: | ||||
|                             self.assertIn( | ||||
|                                 attr, _recommended_attrs_both[metric.name] | ||||
|                             ) | ||||
|         self.assertTrue(number_data_point_seen and histogram_data_point_seen) | ||||
| 
 | ||||
|  | @ -378,21 +495,21 @@ class TestFastAPIManualInstrumentation(TestBaseManualFastAPI): | |||
|         self._client.get("/foobar") | ||||
|         duration = max(round((default_timer() - start) * 1000), 0) | ||||
|         expected_duration_attributes = { | ||||
|             "http.method": "GET", | ||||
|             "http.host": "testserver:443", | ||||
|             "http.scheme": "https", | ||||
|             "http.flavor": "1.1", | ||||
|             "http.server_name": "testserver", | ||||
|             "net.host.port": 443, | ||||
|             "http.status_code": 200, | ||||
|             "http.target": "/foobar", | ||||
|             SpanAttributes.HTTP_METHOD: "GET", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|             SpanAttributes.NET_HOST_PORT: 443, | ||||
|             SpanAttributes.HTTP_STATUS_CODE: 200, | ||||
|             SpanAttributes.HTTP_TARGET: "/foobar", | ||||
|         } | ||||
|         expected_requests_count_attributes = { | ||||
|             "http.method": "GET", | ||||
|             "http.host": "testserver:443", | ||||
|             "http.scheme": "https", | ||||
|             "http.flavor": "1.1", | ||||
|             "http.server_name": "testserver", | ||||
|             SpanAttributes.HTTP_METHOD: "GET", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|         } | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|  | @ -413,6 +530,288 @@ class TestFastAPIManualInstrumentation(TestBaseManualFastAPI): | |||
|                     ) | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_metric_success_new_semconv(self): | ||||
|         start = default_timer() | ||||
|         self._client.get("/foobar") | ||||
|         duration_s = max(default_timer() - start, 0) | ||||
|         expected_duration_attributes = { | ||||
|             HTTP_REQUEST_METHOD: "GET", | ||||
|             URL_SCHEME: "https", | ||||
|             NETWORK_PROTOCOL_VERSION: "1.1", | ||||
|             HTTP_RESPONSE_STATUS_CODE: 200, | ||||
|             HTTP_ROUTE: "/foobar", | ||||
|         } | ||||
|         expected_requests_count_attributes = { | ||||
|             HTTP_REQUEST_METHOD: "GET", | ||||
|             URL_SCHEME: "https", | ||||
|         } | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_duration_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     if metric.name == "http.server.request.duration": | ||||
|                         self.assertAlmostEqual(duration_s, point.sum, places=1) | ||||
|                     elif metric.name == "http.server.response.body.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                     elif metric.name == "http.server.request.body.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_requests_count_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_metric_success_both_semconv(self): | ||||
|         start = default_timer() | ||||
|         self._client.get("/foobar") | ||||
|         duration = max(round((default_timer() - start) * 1000), 0) | ||||
|         duration_s = max(default_timer() - start, 0) | ||||
|         expected_duration_attributes_old = { | ||||
|             SpanAttributes.HTTP_METHOD: "GET", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|             SpanAttributes.NET_HOST_PORT: 443, | ||||
|             SpanAttributes.HTTP_STATUS_CODE: 200, | ||||
|             SpanAttributes.HTTP_TARGET: "/foobar", | ||||
|         } | ||||
|         expected_duration_attributes_new = { | ||||
|             HTTP_REQUEST_METHOD: "GET", | ||||
|             URL_SCHEME: "https", | ||||
|             NETWORK_PROTOCOL_VERSION: "1.1", | ||||
|             HTTP_RESPONSE_STATUS_CODE: 200, | ||||
|             HTTP_ROUTE: "/foobar", | ||||
|         } | ||||
|         expected_requests_count_attributes = { | ||||
|             SpanAttributes.HTTP_METHOD: "GET", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|             HTTP_REQUEST_METHOD: "GET", | ||||
|             URL_SCHEME: "https", | ||||
|         } | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     self.assertAlmostEqual(duration, point.sum, delta=40) | ||||
|                     if metric.name == "http.server.request.duration": | ||||
|                         self.assertAlmostEqual(duration_s, point.sum, places=1) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_new, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.response.body.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_new, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.request.body.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_new, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.duration": | ||||
|                         self.assertAlmostEqual(duration, point.sum, delta=40) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_old, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.response.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_old, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.request.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_old, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_requests_count_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_metric_nonstandard_http_method_success(self): | ||||
|         start = default_timer() | ||||
|         self._client.request("NONSTANDARD", "/foobar") | ||||
|         duration = max(round((default_timer() - start) * 1000), 0) | ||||
|         expected_duration_attributes = { | ||||
|             SpanAttributes.HTTP_METHOD: "_OTHER", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|             SpanAttributes.NET_HOST_PORT: 443, | ||||
|             SpanAttributes.HTTP_STATUS_CODE: 405, | ||||
|             SpanAttributes.HTTP_TARGET: "/foobar", | ||||
|         } | ||||
|         expected_requests_count_attributes = { | ||||
|             SpanAttributes.HTTP_METHOD: "_OTHER", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|         } | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_duration_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     self.assertAlmostEqual(duration, point.sum, delta=40) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_requests_count_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_metric_nonstandard_http_method_success_new_semconv(self): | ||||
|         start = default_timer() | ||||
|         self._client.request("NONSTANDARD", "/foobar") | ||||
|         duration_s = max(default_timer() - start, 0) | ||||
|         expected_duration_attributes = { | ||||
|             HTTP_REQUEST_METHOD: "_OTHER", | ||||
|             URL_SCHEME: "https", | ||||
|             NETWORK_PROTOCOL_VERSION: "1.1", | ||||
|             HTTP_RESPONSE_STATUS_CODE: 405, | ||||
|             HTTP_ROUTE: "/foobar", | ||||
|         } | ||||
|         expected_requests_count_attributes = { | ||||
|             HTTP_REQUEST_METHOD: "_OTHER", | ||||
|             URL_SCHEME: "https", | ||||
|         } | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_duration_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     if metric.name == "http.server.request.duration": | ||||
|                         self.assertAlmostEqual(duration_s, point.sum, places=1) | ||||
|                     elif metric.name == "http.server.response.body.size": | ||||
|                         self.assertEqual(31, point.sum) | ||||
|                     elif metric.name == "http.server.request.body.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_requests_count_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_metric_nonstandard_http_method_success_both_semconv(self): | ||||
|         start = default_timer() | ||||
|         self._client.request("NONSTANDARD", "/foobar") | ||||
|         duration = max(round((default_timer() - start) * 1000), 0) | ||||
|         duration_s = max(default_timer() - start, 0) | ||||
|         expected_duration_attributes_old = { | ||||
|             SpanAttributes.HTTP_METHOD: "_OTHER", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|             SpanAttributes.NET_HOST_PORT: 443, | ||||
|             SpanAttributes.HTTP_STATUS_CODE: 405, | ||||
|             SpanAttributes.HTTP_TARGET: "/foobar", | ||||
|         } | ||||
|         expected_duration_attributes_new = { | ||||
|             HTTP_REQUEST_METHOD: "_OTHER", | ||||
|             URL_SCHEME: "https", | ||||
|             NETWORK_PROTOCOL_VERSION: "1.1", | ||||
|             HTTP_RESPONSE_STATUS_CODE: 405, | ||||
|             HTTP_ROUTE: "/foobar", | ||||
|         } | ||||
|         expected_requests_count_attributes = { | ||||
|             SpanAttributes.HTTP_METHOD: "_OTHER", | ||||
|             SpanAttributes.HTTP_HOST: "testserver:443", | ||||
|             SpanAttributes.HTTP_SCHEME: "https", | ||||
|             SpanAttributes.HTTP_FLAVOR: "1.1", | ||||
|             SpanAttributes.HTTP_SERVER_NAME: "testserver", | ||||
|             HTTP_REQUEST_METHOD: "_OTHER", | ||||
|             URL_SCHEME: "https", | ||||
|         } | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     self.assertAlmostEqual(duration, point.sum, delta=40) | ||||
|                     if metric.name == "http.server.request.duration": | ||||
|                         self.assertAlmostEqual(duration_s, point.sum, places=1) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_new, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.response.body.size": | ||||
|                         self.assertEqual(31, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_new, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.request.body.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_new, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.duration": | ||||
|                         self.assertAlmostEqual(duration, point.sum, delta=40) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_old, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.response.size": | ||||
|                         self.assertEqual(31, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_old, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                     elif metric.name == "http.server.request.size": | ||||
|                         self.assertEqual(25, point.sum) | ||||
|                         self.assertDictEqual( | ||||
|                             expected_duration_attributes_old, | ||||
|                             dict(point.attributes), | ||||
|                         ) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertDictEqual( | ||||
|                         expected_requests_count_attributes, | ||||
|                         dict(point.attributes), | ||||
|                     ) | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_post_request_metric_success(self): | ||||
|         start = default_timer() | ||||
|         response = self._client.post( | ||||
|  | @ -438,6 +837,63 @@ class TestFastAPIManualInstrumentation(TestBaseManualFastAPI): | |||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_post_request_metric_success_new_semconv(self): | ||||
|         start = default_timer() | ||||
|         response = self._client.post( | ||||
|             "/foobar", | ||||
|             json={"foo": "bar"}, | ||||
|         ) | ||||
|         duration_s = max(default_timer() - start, 0) | ||||
|         response_size = int(response.headers.get("content-length")) | ||||
|         request_size = int(response.request.headers.get("content-length")) | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     if metric.name == "http.server.request.duration": | ||||
|                         self.assertAlmostEqual(duration_s, point.sum, places=1) | ||||
|                     elif metric.name == "http.server.response.body.size": | ||||
|                         self.assertEqual(response_size, point.sum) | ||||
|                     elif metric.name == "http.server.request.body.size": | ||||
|                         self.assertEqual(request_size, point.sum) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_basic_post_request_metric_success_both_semconv(self): | ||||
|         start = default_timer() | ||||
|         response = self._client.post( | ||||
|             "/foobar", | ||||
|             json={"foo": "bar"}, | ||||
|         ) | ||||
|         duration = max(round((default_timer() - start) * 1000), 0) | ||||
|         duration_s = max(default_timer() - start, 0) | ||||
|         response_size = int(response.headers.get("content-length")) | ||||
|         request_size = int(response.request.headers.get("content-length")) | ||||
|         metrics_list = self.memory_metrics_reader.get_metrics_data() | ||||
|         for metric in ( | ||||
|             metrics_list.resource_metrics[0].scope_metrics[0].metrics | ||||
|         ): | ||||
|             for point in list(metric.data.data_points): | ||||
|                 if isinstance(point, HistogramDataPoint): | ||||
|                     self.assertEqual(point.count, 1) | ||||
|                     if metric.name == "http.server.request.duration": | ||||
|                         self.assertAlmostEqual(duration_s, point.sum, places=1) | ||||
|                     elif metric.name == "http.server.response.body.size": | ||||
|                         self.assertEqual(response_size, point.sum) | ||||
|                     elif metric.name == "http.server.request.body.size": | ||||
|                         self.assertEqual(request_size, point.sum) | ||||
|                     elif metric.name == "http.server.duration": | ||||
|                         self.assertAlmostEqual(duration, point.sum, delta=40) | ||||
|                     elif metric.name == "http.server.response.size": | ||||
|                         self.assertEqual(response_size, point.sum) | ||||
|                     elif metric.name == "http.server.request.size": | ||||
|                         self.assertEqual(request_size, point.sum) | ||||
|                 if isinstance(point, NumberDataPoint): | ||||
|                     self.assertEqual(point.value, 0) | ||||
| 
 | ||||
|     def test_metric_uninstrument_app(self): | ||||
|         self._client.get("/foobar") | ||||
|         self._instrumentor.uninstrument_app(self._app) | ||||
|  |  | |||
|  | @ -543,7 +543,9 @@ class _InstrumentedFlask(flask.Flask): | |||
|             __name__, | ||||
|             __version__, | ||||
|             _InstrumentedFlask._meter_provider, | ||||
|             schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|             schema_url=_get_schema_url( | ||||
|                 _InstrumentedFlask._sem_conv_opt_in_mode | ||||
|             ), | ||||
|         ) | ||||
|         duration_histogram_old = None | ||||
|         if _report_old(_InstrumentedFlask._sem_conv_opt_in_mode): | ||||
|  | @ -579,7 +581,9 @@ class _InstrumentedFlask(flask.Flask): | |||
|             __name__, | ||||
|             __version__, | ||||
|             _InstrumentedFlask._tracer_provider, | ||||
|             schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|             schema_url=_get_schema_url( | ||||
|                 _InstrumentedFlask._sem_conv_opt_in_mode | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         _before_request = _wrapped_before_request( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue