Add metric instrumentation for WSGI (#1128)
This commit is contained in:
		
							parent
							
								
									6503cdf2fe
								
							
						
					
					
						commit
						0007c9046c
					
				|  | @ -6,7 +6,7 @@ on: | |||
|     - 'release/*' | ||||
|   pull_request: | ||||
| env: | ||||
|   CORE_REPO_SHA: c82829283d3e99aa2e089d1774ee509619650617  | ||||
|   CORE_REPO_SHA: d4d7c67663cc22615748d632e1c8c5799e8eacae | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
|  | @ -42,7 +42,7 @@ jobs: | |||
|           path: | | ||||
|             .tox | ||||
|             ~/.cache/pip | ||||
|           key: v5-build-tox-cache-${{ env.RUN_MATRIX_COMBINATION }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }} | ||||
|           key: v6-build-tox-cache-${{ env.RUN_MATRIX_COMBINATION }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }} | ||||
|       - name: run tox | ||||
|         run: tox -f ${{ matrix.python-version }}-${{ matrix.package }} -- --benchmark-json=${{ env.RUN_MATRIX_COMBINATION }}-benchmark.json | ||||
|   #     - name: Find and merge ${{ matrix.package }} benchmarks | ||||
|  | @ -118,7 +118,7 @@ jobs: | |||
|           path: | | ||||
|             .tox | ||||
|             ~/.cache/pip | ||||
|           key: v5-misc-tox-cache-${{ matrix.tox-environment }}-${{ hashFiles('tox.ini', 'dev-requirements.txt', 'gen-requirements.txt', 'docs-requirements.txt') }} | ||||
|           key: v6-misc-tox-cache-${{ matrix.tox-environment }}-${{ hashFiles('tox.ini', 'dev-requirements.txt', 'gen-requirements.txt', 'docs-requirements.txt') }} | ||||
|       - name: run tox | ||||
|         run: tox -e ${{ matrix.tox-environment }} | ||||
|       - name: Ensure generated code is up to date | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
|   ([#1111](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1111)) | ||||
| - Set otlp-proto-grpc as the default metrics exporter for auto-instrumentation | ||||
|   ([#1127](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1127)) | ||||
| - Add metric instrumentation for WSGI | ||||
|   ([#1128](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1128)) | ||||
| 
 | ||||
| 
 | ||||
| ## [1.12.0rc1-0.31b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc1-0.31b0) - 2022-05-17 | ||||
|  |  | |||
|  | @ -1,43 +1,43 @@ | |||
| 
 | ||||
| | Instrumentation | Supported Packages | | ||||
| | --------------- | ------------------ | | ||||
| | [opentelemetry-instrumentation-aiohttp-client](./opentelemetry-instrumentation-aiohttp-client) | aiohttp ~= 3.0 | | ||||
| | [opentelemetry-instrumentation-aiopg](./opentelemetry-instrumentation-aiopg) | aiopg >= 0.13.0, < 1.3.0 | | ||||
| | [opentelemetry-instrumentation-asgi](./opentelemetry-instrumentation-asgi) | asgiref ~= 3.0 | | ||||
| | [opentelemetry-instrumentation-asyncpg](./opentelemetry-instrumentation-asyncpg) | asyncpg >= 0.12.0 | | ||||
| | [opentelemetry-instrumentation-aws-lambda](./opentelemetry-instrumentation-aws-lambda) | aws_lambda | | ||||
| | [opentelemetry-instrumentation-boto](./opentelemetry-instrumentation-boto) | boto~=2.0 | | ||||
| | [opentelemetry-instrumentation-boto3sqs](./opentelemetry-instrumentation-boto3sqs) | boto3 ~= 1.0 | | ||||
| | [opentelemetry-instrumentation-botocore](./opentelemetry-instrumentation-botocore) | botocore ~= 1.0 | | ||||
| | [opentelemetry-instrumentation-celery](./opentelemetry-instrumentation-celery) | celery >= 4.0, < 6.0 | | ||||
| | [opentelemetry-instrumentation-confluent-kafka](./opentelemetry-instrumentation-confluent-kafka) | confluent-kafka ~= 1.8.2 | | ||||
| | [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | | ||||
| | [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | | ||||
| | [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 2.0 | | ||||
| | [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 4.0.0 | | ||||
| | [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | | ||||
| | [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0, < 3.0 | | ||||
| | [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio ~= 1.27 | | ||||
| | [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0 | | ||||
| | [opentelemetry-instrumentation-jinja2](./opentelemetry-instrumentation-jinja2) | jinja2 >= 2.7, < 4.0 | | ||||
| | [opentelemetry-instrumentation-kafka-python](./opentelemetry-instrumentation-kafka-python) | kafka-python >= 2.0 | | ||||
| | [opentelemetry-instrumentation-logging](./opentelemetry-instrumentation-logging) | logging | | ||||
| | [opentelemetry-instrumentation-mysql](./opentelemetry-instrumentation-mysql) | mysql-connector-python ~= 8.0 | | ||||
| | [opentelemetry-instrumentation-pika](./opentelemetry-instrumentation-pika) | pika >= 0.12.0 | | ||||
| | [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1 | | ||||
| | [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 4 | | ||||
| | [opentelemetry-instrumentation-pymongo](./opentelemetry-instrumentation-pymongo) | pymongo >= 3.1, < 5.0 | | ||||
| | [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | | ||||
| | [opentelemetry-instrumentation-pyramid](./opentelemetry-instrumentation-pyramid) | pyramid >= 1.7 | | ||||
| | [opentelemetry-instrumentation-redis](./opentelemetry-instrumentation-redis) | redis >= 2.6 | | ||||
| | [opentelemetry-instrumentation-remoulade](./opentelemetry-instrumentation-remoulade) | remoulade >= 0.50 | | ||||
| | [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 | | ||||
| | [opentelemetry-instrumentation-sklearn](./opentelemetry-instrumentation-sklearn) | scikit-learn ~= 0.24.0 | | ||||
| | [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy | | ||||
| | [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | | ||||
| | [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette ~= 0.13.0 | | ||||
| | [opentelemetry-instrumentation-system-metrics](./opentelemetry-instrumentation-system-metrics) | psutil >= 5 | | ||||
| | [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 5.1.1 | | ||||
| | [opentelemetry-instrumentation-urllib](./opentelemetry-instrumentation-urllib) | urllib | | ||||
| | [opentelemetry-instrumentation-urllib3](./opentelemetry-instrumentation-urllib3) | urllib3 >= 1.0.0, < 2.0.0 | | ||||
| | [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | | ||||
| | Instrumentation | Supported Packages | Metrics support | | ||||
| | --------------- | ------------------ | --------------- | | ||||
| | [opentelemetry-instrumentation-aiohttp-client](./opentelemetry-instrumentation-aiohttp-client) | aiohttp ~= 3.0 | No | ||||
| | [opentelemetry-instrumentation-aiopg](./opentelemetry-instrumentation-aiopg) | aiopg >= 0.13.0, < 1.3.0 | No | ||||
| | [opentelemetry-instrumentation-asgi](./opentelemetry-instrumentation-asgi) | asgiref ~= 3.0 | No | ||||
| | [opentelemetry-instrumentation-asyncpg](./opentelemetry-instrumentation-asyncpg) | asyncpg >= 0.12.0 | No | ||||
| | [opentelemetry-instrumentation-aws-lambda](./opentelemetry-instrumentation-aws-lambda) | aws_lambda | No | ||||
| | [opentelemetry-instrumentation-boto](./opentelemetry-instrumentation-boto) | boto~=2.0 | No | ||||
| | [opentelemetry-instrumentation-boto3sqs](./opentelemetry-instrumentation-boto3sqs) | boto3 ~= 1.0 | No | ||||
| | [opentelemetry-instrumentation-botocore](./opentelemetry-instrumentation-botocore) | botocore ~= 1.0 | No | ||||
| | [opentelemetry-instrumentation-celery](./opentelemetry-instrumentation-celery) | celery >= 4.0, < 6.0 | No | ||||
| | [opentelemetry-instrumentation-confluent-kafka](./opentelemetry-instrumentation-confluent-kafka) | confluent-kafka ~= 1.8.2 | No | ||||
| | [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | ||||
| | [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | No | ||||
| | [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 2.0 | No | ||||
| | [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 4.0.0 | No | ||||
| | [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | No | ||||
| | [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0, < 3.0 | No | ||||
| | [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio ~= 1.27 | No | ||||
| | [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0 | No | ||||
| | [opentelemetry-instrumentation-jinja2](./opentelemetry-instrumentation-jinja2) | jinja2 >= 2.7, < 4.0 | No | ||||
| | [opentelemetry-instrumentation-kafka-python](./opentelemetry-instrumentation-kafka-python) | kafka-python >= 2.0 | No | ||||
| | [opentelemetry-instrumentation-logging](./opentelemetry-instrumentation-logging) | logging | No | ||||
| | [opentelemetry-instrumentation-mysql](./opentelemetry-instrumentation-mysql) | mysql-connector-python ~= 8.0 | No | ||||
| | [opentelemetry-instrumentation-pika](./opentelemetry-instrumentation-pika) | pika >= 0.12.0 | No | ||||
| | [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1 | No | ||||
| | [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 4 | No | ||||
| | [opentelemetry-instrumentation-pymongo](./opentelemetry-instrumentation-pymongo) | pymongo >= 3.1, < 5.0 | No | ||||
| | [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | No | ||||
| | [opentelemetry-instrumentation-pyramid](./opentelemetry-instrumentation-pyramid) | pyramid >= 1.7 | No | ||||
| | [opentelemetry-instrumentation-redis](./opentelemetry-instrumentation-redis) | redis >= 2.6 | No | ||||
| | [opentelemetry-instrumentation-remoulade](./opentelemetry-instrumentation-remoulade) | remoulade >= 0.50 | No | ||||
| | [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 | No | ||||
| | [opentelemetry-instrumentation-sklearn](./opentelemetry-instrumentation-sklearn) | scikit-learn ~= 0.24.0 | No | ||||
| | [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy | No | ||||
| | [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | No | ||||
| | [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette ~= 0.13.0 | No | ||||
| | [opentelemetry-instrumentation-system-metrics](./opentelemetry-instrumentation-system-metrics) | psutil >= 5 | No | ||||
| | [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 5.1.1 | No | ||||
| | [opentelemetry-instrumentation-urllib](./opentelemetry-instrumentation-urllib) | urllib | No | ||||
| | [opentelemetry-instrumentation-urllib3](./opentelemetry-instrumentation-urllib3) | urllib3 >= 1.0.0, < 2.0.0 | No | ||||
| | [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | Yes | ||||
|  | @ -35,7 +35,6 @@ from opentelemetry.sdk import resources | |||
| from opentelemetry.sdk.trace import Span | ||||
| from opentelemetry.sdk.trace.id_generator import RandomIdGenerator | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.test.wsgitestutil import WsgiTestBase | ||||
| from opentelemetry.trace import ( | ||||
|     SpanKind, | ||||
|  | @ -84,7 +83,7 @@ urlpatterns = [ | |||
| _django_instrumentor = DjangoInstrumentor() | ||||
| 
 | ||||
| 
 | ||||
| class TestMiddleware(TestBase, WsgiTestBase): | ||||
| class TestMiddleware(WsgiTestBase): | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         conf.settings.configure(ROOT_URLCONF=modules[__name__]) | ||||
|  | @ -402,7 +401,7 @@ class TestMiddleware(TestBase, WsgiTestBase): | |||
|         self.memory_exporter.clear() | ||||
| 
 | ||||
| 
 | ||||
| class TestMiddlewareWithTracerProvider(TestBase, WsgiTestBase): | ||||
| class TestMiddlewareWithTracerProvider(WsgiTestBase): | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         conf.settings.configure(ROOT_URLCONF=modules[__name__]) | ||||
|  | @ -460,7 +459,7 @@ class TestMiddlewareWithTracerProvider(TestBase, WsgiTestBase): | |||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| class TestMiddlewareWsgiWithCustomHeaders(TestBase, WsgiTestBase): | ||||
| class TestMiddlewareWsgiWithCustomHeaders(WsgiTestBase): | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         conf.settings.configure(ROOT_URLCONF=modules[__name__]) | ||||
|  |  | |||
|  | @ -17,14 +17,13 @@ from werkzeug.test import Client | |||
| from werkzeug.wrappers import Response | ||||
| 
 | ||||
| from opentelemetry.instrumentation.flask import FlaskInstrumentor | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.test.wsgitestutil import WsgiTestBase | ||||
| 
 | ||||
| # pylint: disable=import-error | ||||
| from .base_test import InstrumentationTest | ||||
| 
 | ||||
| 
 | ||||
| class TestAutomatic(InstrumentationTest, TestBase, WsgiTestBase): | ||||
| class TestAutomatic(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ from opentelemetry.instrumentation.propagators import ( | |||
| from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware | ||||
| from opentelemetry.sdk.resources import Resource | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.test.wsgitestutil import WsgiTestBase | ||||
| from opentelemetry.util.http import get_excluded_urls | ||||
| 
 | ||||
|  | @ -50,7 +49,7 @@ def expected_attributes(override_attributes): | |||
|     return default_attributes | ||||
| 
 | ||||
| 
 | ||||
| class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase): | ||||
| class TestProgrammatic(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|  | @ -252,7 +251,7 @@ class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase): | |||
|         self.assertEqual(len(span_list), 1) | ||||
| 
 | ||||
| 
 | ||||
| class TestProgrammaticHooks(InstrumentationTest, TestBase, WsgiTestBase): | ||||
| class TestProgrammaticHooks(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|  | @ -300,9 +299,7 @@ class TestProgrammaticHooks(InstrumentationTest, TestBase, WsgiTestBase): | |||
|         self.assertEqual(resp.headers["hook_attr"], "hello otel") | ||||
| 
 | ||||
| 
 | ||||
| class TestProgrammaticHooksWithoutApp( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
| ): | ||||
| class TestProgrammaticHooksWithoutApp(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|  | @ -350,9 +347,7 @@ class TestProgrammaticHooksWithoutApp( | |||
|         self.assertEqual(resp.headers["hook_attr"], "hello otel without app") | ||||
| 
 | ||||
| 
 | ||||
| class TestProgrammaticCustomTracerProvider( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
| ): | ||||
| class TestProgrammaticCustomTracerProvider(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         resource = Resource.create({"service.name": "flask-api"}) | ||||
|  | @ -383,7 +378,7 @@ class TestProgrammaticCustomTracerProvider( | |||
| 
 | ||||
| 
 | ||||
| class TestProgrammaticCustomTracerProviderWithoutApp( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
|     InstrumentationTest, WsgiTestBase | ||||
| ): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | @ -417,7 +412,7 @@ class TestProgrammaticCustomTracerProviderWithoutApp( | |||
| 
 | ||||
| 
 | ||||
| class TestProgrammaticWrappedWithOtherFramework( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
|     InstrumentationTest, WsgiTestBase | ||||
| ): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | @ -444,9 +439,7 @@ class TestProgrammaticWrappedWithOtherFramework( | |||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class TestCustomRequestResponseHeaders( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
| ): | ||||
| class TestCustomRequestResponseHeaders(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ from pyramid.config import Configurator | |||
| from opentelemetry import trace | ||||
| from opentelemetry.instrumentation.pyramid import PyramidInstrumentor | ||||
| from opentelemetry.test.globals_test import reset_trace_globals | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.test.wsgitestutil import WsgiTestBase | ||||
| from opentelemetry.trace import SpanKind | ||||
| from opentelemetry.trace.status import StatusCode | ||||
|  | @ -32,7 +31,7 @@ from opentelemetry.util.http import ( | |||
| from .pyramid_base_test import InstrumentationTest | ||||
| 
 | ||||
| 
 | ||||
| class TestAutomatic(InstrumentationTest, TestBase, WsgiTestBase): | ||||
| class TestAutomatic(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
| 
 | ||||
|  | @ -158,9 +157,7 @@ class TestAutomatic(InstrumentationTest, TestBase, WsgiTestBase): | |||
|         self.assertEqual(len(span_list), 1) | ||||
| 
 | ||||
| 
 | ||||
| class TestWrappedWithOtherFramework( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
| ): | ||||
| class TestWrappedWithOtherFramework(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         PyramidInstrumentor().instrument() | ||||
|  | @ -189,9 +186,7 @@ class TestWrappedWithOtherFramework( | |||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| class TestCustomRequestResponseHeaders( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
| ): | ||||
| class TestCustomRequestResponseHeaders(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         PyramidInstrumentor().instrument() | ||||
|  | @ -296,9 +291,7 @@ class TestCustomRequestResponseHeaders( | |||
|                 self.assertNotIn(key, span.attributes) | ||||
| 
 | ||||
| 
 | ||||
| class TestCustomHeadersNonRecordingSpan( | ||||
|     InstrumentationTest, TestBase, WsgiTestBase | ||||
| ): | ||||
| class TestCustomHeadersNonRecordingSpan(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         # This is done because set_tracer_provider cannot override the | ||||
|  |  | |||
|  | @ -24,7 +24,6 @@ from opentelemetry.instrumentation.propagators import ( | |||
| ) | ||||
| from opentelemetry.instrumentation.pyramid import PyramidInstrumentor | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.test.wsgitestutil import WsgiTestBase | ||||
| from opentelemetry.util.http import get_excluded_urls | ||||
| 
 | ||||
|  | @ -48,7 +47,7 @@ def expected_attributes(override_attributes): | |||
|     return default_attributes | ||||
| 
 | ||||
| 
 | ||||
| class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase): | ||||
| class TestProgrammatic(InstrumentationTest, WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         config = Configurator() | ||||
|  |  | |||
|  | @ -158,6 +158,7 @@ API | |||
| import functools | ||||
| import typing | ||||
| import wsgiref.util as wsgiref_util | ||||
| from timeit import default_timer | ||||
| 
 | ||||
| from opentelemetry import context, trace | ||||
| from opentelemetry.instrumentation.utils import ( | ||||
|  | @ -165,6 +166,7 @@ from opentelemetry.instrumentation.utils import ( | |||
|     http_status_to_status_code, | ||||
| ) | ||||
| from opentelemetry.instrumentation.wsgi.version import __version__ | ||||
| from opentelemetry.metrics import get_meter | ||||
| from opentelemetry.propagators.textmap import Getter | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.trace.status import Status, StatusCode | ||||
|  | @ -181,6 +183,26 @@ _HTTP_VERSION_PREFIX = "HTTP/" | |||
| _CARRIER_KEY_PREFIX = "HTTP_" | ||||
| _CARRIER_KEY_PREFIX_LEN = len(_CARRIER_KEY_PREFIX) | ||||
| 
 | ||||
| # List of recommended attributes | ||||
| _duration_attrs = [ | ||||
|     SpanAttributes.HTTP_METHOD, | ||||
|     SpanAttributes.HTTP_HOST, | ||||
|     SpanAttributes.HTTP_SCHEME, | ||||
|     SpanAttributes.HTTP_STATUS_CODE, | ||||
|     SpanAttributes.HTTP_FLAVOR, | ||||
|     SpanAttributes.HTTP_SERVER_NAME, | ||||
|     SpanAttributes.NET_HOST_NAME, | ||||
|     SpanAttributes.NET_HOST_PORT, | ||||
| ] | ||||
| 
 | ||||
| _active_requests_count_attrs = [ | ||||
|     SpanAttributes.HTTP_METHOD, | ||||
|     SpanAttributes.HTTP_HOST, | ||||
|     SpanAttributes.HTTP_SCHEME, | ||||
|     SpanAttributes.HTTP_FLAVOR, | ||||
|     SpanAttributes.HTTP_SERVER_NAME, | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| class WSGIGetter(Getter[dict]): | ||||
|     def get( | ||||
|  | @ -304,6 +326,14 @@ def collect_custom_response_headers_attributes(response_headers): | |||
|     return attributes | ||||
| 
 | ||||
| 
 | ||||
| def _parse_status_code(resp_status): | ||||
|     status_code, _ = resp_status.split(" ", 1) | ||||
|     try: | ||||
|         return int(status_code) | ||||
|     except ValueError: | ||||
|         return None | ||||
| 
 | ||||
| 
 | ||||
| def add_response_attributes( | ||||
|     span, start_response_status, response_headers | ||||
| ):  # pylint: disable=unused-argument | ||||
|  | @ -352,18 +382,39 @@ class OpenTelemetryMiddleware: | |||
|     """ | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, wsgi, request_hook=None, response_hook=None, tracer_provider=None | ||||
|         self, | ||||
|         wsgi, | ||||
|         request_hook=None, | ||||
|         response_hook=None, | ||||
|         tracer_provider=None, | ||||
|         meter_provider=None, | ||||
|     ): | ||||
|         self.wsgi = wsgi | ||||
|         self.tracer = trace.get_tracer(__name__, __version__, tracer_provider) | ||||
|         self.meter = get_meter(__name__, __version__, meter_provider) | ||||
|         self.duration_histogram = self.meter.create_histogram( | ||||
|             name="http.server.duration", | ||||
|             unit="ms", | ||||
|             description="measures the duration of the inbound HTTP request", | ||||
|         ) | ||||
|         self.active_requests_counter = self.meter.create_up_down_counter( | ||||
|             name="http.server.active_requests", | ||||
|             unit="requests", | ||||
|             description="measures the number of concurrent HTTP requests that are currently in-flight", | ||||
|         ) | ||||
|         self.request_hook = request_hook | ||||
|         self.response_hook = response_hook | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _create_start_response(span, start_response, response_hook): | ||||
|     def _create_start_response( | ||||
|         span, start_response, response_hook, duration_attrs | ||||
|     ): | ||||
|         @functools.wraps(start_response) | ||||
|         def _start_response(status, response_headers, *args, **kwargs): | ||||
|             add_response_attributes(span, status, response_headers) | ||||
|             status_code = _parse_status_code(status) | ||||
|             if status_code is not None: | ||||
|                 duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = status_code | ||||
|             if span.is_recording() and span.kind == trace.SpanKind.SERVER: | ||||
|                 custom_attributes = collect_custom_response_headers_attributes( | ||||
|                     response_headers | ||||
|  | @ -376,6 +427,7 @@ class OpenTelemetryMiddleware: | |||
| 
 | ||||
|         return _start_response | ||||
| 
 | ||||
|     # pylint: disable=too-many-branches | ||||
|     def __call__(self, environ, start_response): | ||||
|         """The WSGI application | ||||
| 
 | ||||
|  | @ -383,13 +435,24 @@ class OpenTelemetryMiddleware: | |||
|             environ: A WSGI environment. | ||||
|             start_response: The WSGI start_response callable. | ||||
|         """ | ||||
|         req_attrs = collect_request_attributes(environ) | ||||
|         active_requests_count_attrs = {} | ||||
|         for attr_key in _active_requests_count_attrs: | ||||
|             if req_attrs.get(attr_key) is not None: | ||||
|                 active_requests_count_attrs[attr_key] = req_attrs[attr_key] | ||||
| 
 | ||||
|         duration_attrs = {} | ||||
|         for attr_key in _duration_attrs: | ||||
|             if req_attrs.get(attr_key) is not None: | ||||
|                 duration_attrs[attr_key] = req_attrs[attr_key] | ||||
| 
 | ||||
|         span, token = _start_internal_or_server_span( | ||||
|             tracer=self.tracer, | ||||
|             span_name=get_default_span_name(environ), | ||||
|             start_time=None, | ||||
|             context_carrier=environ, | ||||
|             context_getter=wsgi_getter, | ||||
|             attributes=collect_request_attributes(environ), | ||||
|             attributes=req_attrs, | ||||
|         ) | ||||
|         if span.is_recording() and span.kind == trace.SpanKind.SERVER: | ||||
|             custom_attributes = collect_custom_request_headers_attributes( | ||||
|  | @ -405,15 +468,15 @@ class OpenTelemetryMiddleware: | |||
|         if response_hook: | ||||
|             response_hook = functools.partial(response_hook, span, environ) | ||||
| 
 | ||||
|         start = default_timer() | ||||
|         self.active_requests_counter.add(1, active_requests_count_attrs) | ||||
|         try: | ||||
|             with trace.use_span(span): | ||||
|                 start_response = self._create_start_response( | ||||
|                     span, start_response, response_hook | ||||
|                     span, start_response, response_hook, duration_attrs | ||||
|                 ) | ||||
|                 iterable = self.wsgi(environ, start_response) | ||||
|                 return _end_span_after_iterating( | ||||
|                     iterable, span, self.tracer, token | ||||
|                 ) | ||||
|                 return _end_span_after_iterating(iterable, span, token) | ||||
|         except Exception as ex: | ||||
|             if span.is_recording(): | ||||
|                 span.set_status(Status(StatusCode.ERROR, str(ex))) | ||||
|  | @ -421,12 +484,16 @@ class OpenTelemetryMiddleware: | |||
|             if token is not None: | ||||
|                 context.detach(token) | ||||
|             raise | ||||
|         finally: | ||||
|             duration = max(round((default_timer() - start) * 1000), 0) | ||||
|             self.duration_histogram.record(duration, duration_attrs) | ||||
|             self.active_requests_counter.add(-1, active_requests_count_attrs) | ||||
| 
 | ||||
| 
 | ||||
| # Put this in a subfunction to not delay the call to the wrapped | ||||
| # WSGI application (instrumentation should change the application | ||||
| # behavior as little as possible). | ||||
| def _end_span_after_iterating(iterable, span, tracer, token): | ||||
| def _end_span_after_iterating(iterable, span, token): | ||||
|     try: | ||||
|         with trace.use_span(span): | ||||
|             yield from iterable | ||||
|  |  | |||
|  | @ -14,3 +14,5 @@ | |||
| 
 | ||||
| 
 | ||||
| _instruments = tuple() | ||||
| 
 | ||||
| _supports_metrics = True | ||||
|  |  | |||
|  | @ -20,6 +20,10 @@ from urllib.parse import urlsplit | |||
| 
 | ||||
| import opentelemetry.instrumentation.wsgi as otel_wsgi | ||||
| from opentelemetry import trace as trace_api | ||||
| from opentelemetry.sdk.metrics.export import ( | ||||
|     HistogramDataPoint, | ||||
|     NumberDataPoint, | ||||
| ) | ||||
| from opentelemetry.sdk.resources import Resource | ||||
| from opentelemetry.semconv.trace import SpanAttributes | ||||
| from opentelemetry.test.test_base import TestBase | ||||
|  | @ -99,6 +103,16 @@ def wsgi_with_custom_response_headers(environ, start_response): | |||
|     return [b"*"] | ||||
| 
 | ||||
| 
 | ||||
| _expected_metric_names = [ | ||||
|     "http.server.active_requests", | ||||
|     "http.server.duration", | ||||
| ] | ||||
| _recommended_attrs = { | ||||
|     "http.server.active_requests": otel_wsgi._active_requests_count_attrs, | ||||
|     "http.server.duration": otel_wsgi._duration_attrs, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class TestWsgiApplication(WsgiTestBase): | ||||
|     def validate_response( | ||||
|         self, | ||||
|  | @ -230,6 +244,36 @@ class TestWsgiApplication(WsgiTestBase): | |||
|             StatusCode.ERROR, | ||||
|         ) | ||||
| 
 | ||||
|     def test_wsgi_metrics(self): | ||||
|         app = otel_wsgi.OpenTelemetryMiddleware(error_wsgi_unhandled) | ||||
|         self.assertRaises(ValueError, app, self.environ, self.start_response) | ||||
|         self.assertRaises(ValueError, app, self.environ, self.start_response) | ||||
|         self.assertRaises(ValueError, app, self.environ, self.start_response) | ||||
|         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) != 0) | ||||
|         for resource_metric in metrics_list.resource_metrics: | ||||
|             self.assertTrue(len(resource_metric.scope_metrics) != 0) | ||||
|             for scope_metric in resource_metric.scope_metrics: | ||||
|                 self.assertTrue(len(scope_metric.metrics) != 0) | ||||
|                 for metric in scope_metric.metrics: | ||||
|                     self.assertIn(metric.name, _expected_metric_names) | ||||
|                     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[metric.name] | ||||
|                             ) | ||||
|         self.assertTrue(number_data_point_seen and histogram_data_point_seen) | ||||
| 
 | ||||
|     def test_default_span_name_missing_request_method(self): | ||||
|         """Test that default span_names with missing request method.""" | ||||
|         self.environ.pop("REQUEST_METHOD") | ||||
|  | @ -461,7 +505,7 @@ class TestWsgiMiddlewareWrappedWithAnotherFramework(WsgiTestBase): | |||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| class TestAdditionOfCustomRequestResponseHeaders(WsgiTestBase, TestBase): | ||||
| class TestAdditionOfCustomRequestResponseHeaders(WsgiTestBase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         tracer_provider, _ = TestBase.create_tracer_provider() | ||||
|  |  | |||
|  | @ -23,8 +23,8 @@ logger = logging.getLogger("instrumentation_readme_generator") | |||
| _prefix = "opentelemetry-instrumentation-" | ||||
| 
 | ||||
| header = """ | ||||
| | Instrumentation | Supported Packages | | ||||
| | --------------- | ------------------ |""" | ||||
| | Instrumentation | Supported Packages | Metrics support | | ||||
| | --------------- | ------------------ | --------------- |""" | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|  | @ -62,11 +62,14 @@ def main(): | |||
|             exec(fh.read(), pkg_info) | ||||
| 
 | ||||
|         instruments = pkg_info["_instruments"] | ||||
|         supports_metrics = pkg_info.get("_supports_metrics") | ||||
|         if not instruments: | ||||
|             instruments = (name,) | ||||
| 
 | ||||
|         metric_column = "Yes" if supports_metrics else "No" | ||||
| 
 | ||||
|         table.append( | ||||
|             f"| [{instrumentation}](./{instrumentation}) | {','.join(instruments)} |" | ||||
|             f"| [{instrumentation}](./{instrumentation}) | {','.join(instruments)} | {metric_column}" | ||||
|         ) | ||||
| 
 | ||||
|     with open( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue