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